diff --git a/docs/openup/analysis-design/architecture-notebook.md b/docs/openup/analysis-design/architecture-notebook.md new file mode 100644 index 0000000..1f13268 --- /dev/null +++ b/docs/openup/analysis-design/architecture-notebook.md @@ -0,0 +1,244 @@ +# Skara Brae — Architecture Notebook + +Version 1.0 (2026-10-05) · OpenUP Analysis & Design artifact: `analysis-design/architecture-notebook.md` +Summary work product in the keyfob-station template structure. Sources (all in-repo, normative): +`analysis-design/uc-realization-mapping.md` (§1–§3), `analysis-design/design-wi-2-architecture-cleanup.md` +(§2–§4, §11), `requirements/supplementary-specifications.md`, `requirements/vision.md`, and the EA model +`docs/openup/model/skara-brae.eapx` (exports under `docs/openup/model/exports/`). Detailed per-WI design +remains in the WI design notes; this notebook is the cross-WI architecture view. Drift rule applies: +a mismatch between this notebook and its sources is a defect fixed in the same cycle. + +## 1. Purpose + +This notebook describes the goals, decisions, constraints and significant elements that shape the design of +Skara Brae across work items. The analysis and design models live in the EA model (Requirements / Use Case +Model, Analysis-Design realizations and Design component model); this notebook summarizes them for review +and onboarding. One model covers the whole product — server, client and common — because the use cases are +player-facing and Requirement→realization traceability spans tiers (joint-model decision, 2026-10-03). + +## 2. Architectural Goals and Philosophy + +- **Dependency rule inward** (Clean Architecture): domain in `sb-core`/`sb-shared` with zero HTTP/framework + knowledge; adapters (`sb-api`, `sb-db`, `sb-auth`) and the client at the edge (D1, CON-05). +- **Behavior continuity**: refactors are wire-format and game-semantics identical (NFR-11, NFR-19). +- **Determinism by construction**: domain randomness is injected (WI-1); identical seeds produce identical + outcomes (NFR-05, D5). +- **One taxonomy of failure**: a single domain error enum with one central HTTP status mapping (D2). +- **Data as source of truth**: game content is language-neutral JSON in `skara-brae-common`, compiled into + both consumers (NFR-13, D6). +- **Zero-warning quality gate**: fmt + clippy + full test suite on every commit (NFR-08..NFR-10). +- **Docs are architecture**: OpenUP markdown is the content source; the EA model renders it; drift is a + defect (D7). + +## 3. Assumptions and Dependencies + +- Single-process Rust server (Axum 0.8, edition 2024); all game state in memory behind `tokio::sync::RwLock`, + optional write-through persistence to SQLite via `SB_DB_PATH` (NFR-01, NFR-14). +- Static game data ships with the binary via `include_str!` from `skara-brae-common` (NFR-04, NFR-13). +- Clients are web/PWA (Vue 3 SPA) polling the REST API; server behavior is poll-frequency-independent + (NFR-02, NFR-03). +- `sb-common` is consumed as a sibling absolute-path dependency locally; git-submodule migration planned + (CON-01). +- Self-hosted pipeline: Forgejo (git), Jenkins CI, systemd deployment (vision.md; topology canon in Joplin + "Tech Stack & Architecture" note). + +## 4. Architecturally Significant Requirements + +The full set is `requirements/supplementary-specifications.md`; the categories that shape the architecture: + +| Category | IDs | Architectural consequence | +|---|---|---| +| Performance / Scalability | NFR-01..NFR-04 | in-memory state, lazy regen (no timers), lock-free catalogue reads | +| Reliability / Correctness | NFR-05..NFR-07 | injected RNG, best-effort persistence, referential data integrity → `DomainError` | +| Quality Gates | NFR-08..NFR-12 | fmt/clippy/test gates; unwrap ledger; behavior-identical refactors | +| Data | NFR-13..NFR-15 | bilingual common package, SQLite format, i64 stat growth | +| Security | NFR-16..NFR-18 | argon2, JWT HS256 + auth middleware, role model (player/admin) | +| Constraints | CON-01..CON-06 | crate layout, Axum 0.8, rand 0.9 rule, one error enum, dependency rule, docs home | +| Usability / API | NFR-19..NFR-20 | wire-format freeze (lexicographic key order), status-code contract | + +## 5. Decisions, Constraints, and Justifications + +Full records in the WI design notes; the architecture-level decisions: + +- **D1 — Layered crate architecture with inward dependencies.** `sb-api` (boundary/adapter) may import + `sb-core`, `sb-shared`, `sb-common`, `sb-db`, `sb-auth`; `sb-core` must not reference axum/http/ + serde_json-json!/tokio/sb-db/sb-api (design note §3). Known debt: ShopService, HealingService, parts of + MagicService remain handler-side in sb-api — recorded, follow-up WI question Q3; the model must not paper + over them. +- **D2 — One domain error taxonomy.** `sb_core::error::DomainError` (thiserror 2) + single status mapping + `sb-api::routes::domain_status` realize the error paths of UC-05/06/08/12 (design note §4.1). +- **D3 — Constants centralized.** `sb_core::constants` is the single audit home for game-balance scalars + (NFR "no magic numbers"); static data tables stay in their modules (design note §2). +- **D4 — Response DTOs in sb-api, not sb-shared.** HTTP wire types never leak into the domain crate; + `AdventureOutcome` derives nothing (design note §2 placement decisions). +- **D5 — Injectable RNG with a single construction point.** `request_rng()` (`StdRng::from_os_rng`, WI-1); + domain functions take `rng: &mut impl Rng` (CON-03, NFR-05). +- **D6 — Bilingual shared data package.** `skara-brae-common`: `data/` JSON single source, `rust/` + `ts/` + thin consumers, `codegen/` planned (NFR-13; tech-stack canon). +- **D7 — Joint EA model, markdown source of truth, surgical builders.** Model changes after the manual + layout session go through surgical scripts only; `build-model.ps1` rebuilds and is forbidden on the laid- + out model (mapping §1, BUILD-CHECKLIST §6). + +## 6. Architectural Mechanisms + +- **Lazy resource regeneration** — resources recompute from elapsed wall-clock time at specific call sites; + no timers or background ticks (NFR-02; UC-05/UC-08 "Regenerate Resources" sub-flow). +- **Static catalogue via LazyLock** — game data parsed once at first access; lock-free reads (NFR-04, UC-04). +- **DomainError → status mapping** — domain rule failures flow through one enum to one mapping; handlers own + store misses/authz (D2, NFR-20). +- **Wire-format freeze** — JSON keys serialize in lexicographic order; typed responses declare fields in that + order (NFR-19). +- **Injectable RNG determinism** — seeded domain tests, single entropy entry point (D5, NFR-05). +- **Best-effort write-through persistence** — SQLite behind an in-memory-authoritative process (NFR-06, + NFR-14). + +## 7. Key Abstractions + +The analysis vocabulary (boundary/controller/entity) of the use-case realizations — full mapping incl. +design elements in `uc-realization-mapping.md` §2; classes, stereotypes and interactions per UC in the EA +model (§2a lists the 24 associations + 4 «uses» dependencies). + +| Class | Stereotype | Responsibility (design element) | +|---|---|---| +| AuthBoundary | «boundary» | `POST /api/auth/register`, `AuthResponse` (UC-01) | +| TokenService | «boundary» | `POST /api/auth/login`, `AuthResponse`, auth middleware (UC-02, guards UC-03…12) | +| CharacterCatalog | «boundary» | character routes (UC-03) | +| CatalogueBrowser | «boundary» | catalogue routes + `sb-shared::responses` (UC-04) | +| GymBoundary | «boundary» | gym routes + responses (UC-05) | +| EquipmentBoundary | «boundary» | equip/unequip routes (UC-06) | +| ShopBoundary | «boundary» | shop routes + responses (UC-07) | +| AdventureBoundary | «boundary» | adventure route + `AdventureRunResponse` (UC-08) | +| TempleBoundary | «boundary» | heal route + `HealResponse` (UC-09) | +| SongBoundary | «boundary» | song routes + responses (UC-10) | +| MagicBoundary | «boundary» | spell routes + responses (UC-11) | +| ReviewBoundary | «boundary» | level-up route + response (UC-12) | +| AccountRegistry | «controller» | `sb-api::routes::{register, seed_users}` (UC-01) | +| Authenticator | «controller» | `sb-api::routes::login` + sb-auth verify/create (UC-02) | +| CharacterFactory | «controller» | `sb-core::character::create_character` (UC-03) | +| TrainingService | «controller» | `sb-core::services::train_stat` (UC-05) | +| EquipmentService | «controller» | `sb-core::services::{equip_item, unequip_item}` (UC-06) | +| ShopService | «controller» | `sb-api::routes::{shop_action, buy_item, sell_item}` (UC-07; **debt**: handler-side, extraction candidate Q3) | +| AdventureService | «controller» | `sb-core::adventure::run_adventure` (UC-08; WI-2 extraction) | +| HealingService | «controller» | `sb-api::routes::heal_character` (UC-09; **debt**: handler-side, extraction candidate Q3) | +| SongService | «controller» | `sb-api::routes::set_song` + `available_songs` (UC-10) | +| MagicService | «controller» | spell routes + `sb-core::spells` helpers (UC-11; **debt**: parts handler-side) | +| ReviewBoard | «controller» | `sb-core::character::level_up` (UC-12) | +| UserAccount | «entity» | `sb-shared::user::{User, Role}` (UC-01/02) | +| Character | «entity» | `sb-shared::character::Character` + Race/Class/Stats (UC-03) | +| ResourceRegenerator | «entity» | `sb-core::character::apply_regen` + regen constants (UC-03/05/08 sub-flow) | +| GameData | «entity» | `sb-core::data::{EnemyDef, ZoneDef, EncounterEntry, LootDrop}` (UC-04) | +| TrainingGrounds | «entity» | `TRAINING_GROUNDS`, `training_cost` (UC-05) | +| Equipment/Inventory | «entity» | `Character.equipment/.inventory`, `sb-shared::item::Item` (UC-06) | +| ShopStock | «entity» | `AppState.shop_stock` (UC-07) | +| CombatEngine | «entity» | `sb-core::combat::{resolve_combat_full, resolve_attack, crit tables, open_ended_roll}` (UC-08) | +| Combatant | «entity» | `sb-core::combat::CombatStats` (UC-08) | +| SpellEffect / BardSong | «entity» | `SpellEffect`, `sb-core::spells`, `sb-core::songs` (UC-08/10/11) | +| BardSong | «entity» | `BARD_SONGS`, `Character.active_song` (UC-10) | +| Spell | «entity» | `SpellDef`, `Character.{known_spells, active_spell, spell_points}` (UC-11) | +| Progression | «entity» | `xp_for_level`, `level_up_cost`, `MAX_LEVEL` (UC-12) | + +Cross-cutting: `DomainError` (error paths UC-05/06/08/12), `Constants` (no-magic-numbers NFR), `Responses` +(wire-format freeze NFR-19) — carried in the model's Architecture Overview package. + +## 8. Layers + +The joint component model (mapping §3); dependencies point inward only. + +| Tier | Component | Role | +|---|---|---| +| Presentation | skara-brae-client | Vue 3.5 SPA (Pinia, Vite), JWT bearer, REST via proxy; player-facing boundary of UC-01…12 | +| Boundary / adapter | sb-api | Axum routes/handlers, `domain_status`, `responses`; boundaries of all UCs | +| Adapter | sb-auth | argon2 + JWT (UC-01/02; middleware guards UC-03…12) | +| Adapter | sb-db | persistence via `sb-db::Database` | +| Domain | sb-core | controllers/entities: adventure, services, character, combat, spells, songs, data/items | +| Domain types | sb-shared | `User`, `Character`, `Item`, response types; zero HTTP imports | +| Data | sb-common | bilingual data package (`data/` JSON, `rust/`, `ts/`, `codegen/`); UC-04 data source | + +![Component Architecture](component-architecture.png) + +*Figure 1: Joint component architecture — server crates, client, common (EA model render).* + +## 9. Architectural Views + +### 9.1 Use Case View + +Architecturally significant use cases: UC-08 (Run Adventure — WI-2 design driver, combat + data integrity), +UC-03/UC-12 (character lifecycle), UC-04 (shared-data consumption), UC-05/06/07 (rule-driven economy +actions with the DomainError error paths), UC-01/02 (auth, middleware guard). Full model: +`requirements/use-case-model.md` + overview figure there. + +| Realization | Use Case | Analysis classes | +|---|---|---| +| UC-01 Realization | Register Account | AuthBoundary, AccountRegistry, UserAccount | +| UC-02 Realization | Authenticate | TokenService, Authenticator | +| UC-03 Realization | Manage Characters | CharacterCatalog, CharacterFactory, Character, ResourceRegenerator | +| UC-04 Realization | Browse Game Catalogue | CatalogueBrowser, GameData | +| UC-05 Realization | Train Stat | GymBoundary, TrainingService, TrainingGrounds | +| UC-06 Realization | Manage Equipment | EquipmentBoundary, EquipmentService, Equipment/Inventory | +| UC-07 Realization | Trade at Shop | ShopBoundary, ShopService, ShopStock | +| UC-08 Realization | Run Adventure | AdventureBoundary, AdventureService, CombatEngine, Combatant, SpellEffect / BardSong | +| UC-09 Realization | Heal at Temple | TempleBoundary, HealingService | +| UC-10 Realization | Practice Bard Song | SongBoundary, SongService, BardSong | +| UC-11 Realization | Study Magic | MagicBoundary, MagicService, Spell | +| UC-12 Realization | Level Up at Review Board | ReviewBoundary, ReviewBoard, Progression | + +### 9.2 Analysis View (Use-Case Realizations) + +Each realization package in the EA model carries a class diagram (boundary/controller/entity with §2a +associations); renders in this folder, generated by `scripts/openup/export-diagram-images.ps1`: + +![UC-01 Realization](uc-01-realization.png) + +![UC-02 Realization](uc-02-realization.png) + +![UC-03 Realization](uc-03-realization.png) + +![UC-04 Realization](uc-04-realization.png) + +![UC-05 Realization](uc-05-realization.png) + +![UC-06 Realization](uc-06-realization.png) + +![UC-07 Realization](uc-07-realization.png) + +![UC-08 Realization](uc-08-realization.png) + +![UC-09 Realization](uc-09-realization.png) + +![UC-10 Realization](uc-10-realization.png) + +![UC-11 Realization](uc-11-realization.png) + +![UC-12 Realization](uc-12-realization.png) + +### 9.3 Interaction View + +Not yet modeled: no sequence diagrams exist for the Skara Brae MVP (the EA model carries scenarios with +per-step actors, not interactions). Candidate first sequences: UC-08 adventure request→combat→loot, and +UC-01/02 auth flows. Adding them is a future work item (PlantUML prototype → EA per the workflow canon). + +### 9.4 Operational View + +Single Rust process per deployment (systemd unit on the self-hosted boxes; Forgejo + Jenkins pipeline per +vision.md). All state in memory, optional write-through SQLite (NFR-01/06/14); no Redis for MVP. The Vue SPA +is served statically and talks to the API over HTTPS (Caddy, tech-stack canon). Clients poll; no push +channel in the MVP (WebSocket notifications are a later-phase feature, vision.md). + +### 9.5 Design Diagram Index + +| Diagram | File | Documents | +|---|---|---| +| Use Case Overview | `../requirements/use-case-overview.png` | actors, use cases, associations | +| UC-01…UC-12 Realization | `uc-01-realization.png` … `uc-12-realization.png` | analysis classes + associations per UC | +| Component Architecture | `component-architecture.png` | joint component model, dependency rule | + +All three sets are renders of the EA model (`docs/openup/model/skara-brae.eapx`) produced by +`scripts/openup/export-diagram-images.ps1`; regenerate after model changes (read-only, layout-preserving). + +## 10. References + +- `requirements/vision.md`, `requirements/use-case-model.md`, `requirements/supplementary-specifications.md` +- `analysis-design/uc-realization-mapping.md` (§1 structure, §2 mapping, §2a interactions, §3 component model) +- `analysis-design/design-wi-1-injectable-rng.md`, `design-wi-2-architecture-cleanup.md` (detailed WI designs) +- EA model: `docs/openup/model/skara-brae.eapx` (+ `exports/` JSON, `BUILD-CHECKLIST.md`) +- Client-side UC realizations: future work item (joint-model decision, mapping §1) diff --git a/docs/openup/analysis-design/component-architecture.png b/docs/openup/analysis-design/component-architecture.png new file mode 100644 index 0000000..7c41006 Binary files /dev/null and b/docs/openup/analysis-design/component-architecture.png differ diff --git a/docs/openup/analysis-design/uc-01-realization.png b/docs/openup/analysis-design/uc-01-realization.png new file mode 100644 index 0000000..c8c99af Binary files /dev/null and b/docs/openup/analysis-design/uc-01-realization.png differ diff --git a/docs/openup/analysis-design/uc-02-realization.png b/docs/openup/analysis-design/uc-02-realization.png new file mode 100644 index 0000000..70b8374 Binary files /dev/null and b/docs/openup/analysis-design/uc-02-realization.png differ diff --git a/docs/openup/analysis-design/uc-03-realization.png b/docs/openup/analysis-design/uc-03-realization.png new file mode 100644 index 0000000..94b5216 Binary files /dev/null and b/docs/openup/analysis-design/uc-03-realization.png differ diff --git a/docs/openup/analysis-design/uc-04-realization.png b/docs/openup/analysis-design/uc-04-realization.png new file mode 100644 index 0000000..9a4cfb6 Binary files /dev/null and b/docs/openup/analysis-design/uc-04-realization.png differ diff --git a/docs/openup/analysis-design/uc-05-realization.png b/docs/openup/analysis-design/uc-05-realization.png new file mode 100644 index 0000000..4ccace6 Binary files /dev/null and b/docs/openup/analysis-design/uc-05-realization.png differ diff --git a/docs/openup/analysis-design/uc-06-realization.png b/docs/openup/analysis-design/uc-06-realization.png new file mode 100644 index 0000000..0c53afe Binary files /dev/null and b/docs/openup/analysis-design/uc-06-realization.png differ diff --git a/docs/openup/analysis-design/uc-07-realization.png b/docs/openup/analysis-design/uc-07-realization.png new file mode 100644 index 0000000..91a60f6 Binary files /dev/null and b/docs/openup/analysis-design/uc-07-realization.png differ diff --git a/docs/openup/analysis-design/uc-08-realization.png b/docs/openup/analysis-design/uc-08-realization.png new file mode 100644 index 0000000..d3cc136 Binary files /dev/null and b/docs/openup/analysis-design/uc-08-realization.png differ diff --git a/docs/openup/analysis-design/uc-09-realization.png b/docs/openup/analysis-design/uc-09-realization.png new file mode 100644 index 0000000..e2528fb Binary files /dev/null and b/docs/openup/analysis-design/uc-09-realization.png differ diff --git a/docs/openup/analysis-design/uc-10-realization.png b/docs/openup/analysis-design/uc-10-realization.png new file mode 100644 index 0000000..a2520ff Binary files /dev/null and b/docs/openup/analysis-design/uc-10-realization.png differ diff --git a/docs/openup/analysis-design/uc-11-realization.png b/docs/openup/analysis-design/uc-11-realization.png new file mode 100644 index 0000000..16fd9ea Binary files /dev/null and b/docs/openup/analysis-design/uc-11-realization.png differ diff --git a/docs/openup/analysis-design/uc-12-realization.png b/docs/openup/analysis-design/uc-12-realization.png new file mode 100644 index 0000000..41bb1dc Binary files /dev/null and b/docs/openup/analysis-design/uc-12-realization.png differ diff --git a/docs/openup/analysis-design/uc-realization-mapping.md b/docs/openup/analysis-design/uc-realization-mapping.md index 1834100..3deabce 100644 --- a/docs/openup/analysis-design/uc-realization-mapping.md +++ b/docs/openup/analysis-design/uc-realization-mapping.md @@ -144,6 +144,10 @@ sb-api → {sb-core, sb-shared, sb-db, sb-auth, sb-common}; sb-core → {sb-shar sb-db → sb-shared; sb-auth → sb-shared. The dependency rule: inward toward sb-core/sb-shared (domain); adapters (sb-api/sb-db/sb-auth) and the client at the edge. +![Component Architecture](component-architecture.png) + +*Figure: joint component architecture (rendered from the EA model via `scripts/openup/export-diagram-images.ps1`). Per-UC realization figures: `uc-01-realization.png` … `uc-12-realization.png` in this folder; they are embedded in the Architecture Notebook (§9.2).* + ## 4. Research summary — keyfob-station EA pipeline (reference) Source: read-only clone `http://192.168.1.10:3000/jan/keyfob-station.git` (inspected 2026-10-03; no modifications). diff --git a/docs/openup/model/BUILD-CHECKLIST.md b/docs/openup/model/BUILD-CHECKLIST.md index e577f4e..98307ab 100644 --- a/docs/openup/model/BUILD-CHECKLIST.md +++ b/docs/openup/model/BUILD-CHECKLIST.md @@ -240,3 +240,12 @@ would destroy manual layout). Sources: `uc-realization-mapping.md` §2a (interac associations; Requirements tree untouched. 4. Manual layout preserved: the scripts add no/modify no DiagramObjects in existing diagrams (only the new `Design` diagram carries its own object layout). + +### 6.4 Diagram image export — `scripts/openup/export-diagram-images.ps1` + +Read-only on the model: renders 14 diagrams to PNG for the markdown documents +(`requirements/use-case-overview.png`, `analysis-design/uc-01..uc-12-realization.png`, +`analysis-design/component-architecture.png`). EA-15 findings baked into the script: +image methods live on **ProjectInterface** (not Repository); `PutDiagramImageToFile(id, +path, 0)` = PNG; the diagram must be `OpenDiagram`-ed first or a blank 241×151 stub is +written. Regenerate after model changes; renders reflect the manual layout. diff --git a/docs/openup/requirements/use-case-model.md b/docs/openup/requirements/use-case-model.md index 79d05fb..267adc1 100644 --- a/docs/openup/requirements/use-case-model.md +++ b/docs/openup/requirements/use-case-model.md @@ -28,6 +28,10 @@ Scope: the **existing implemented system** (server MVP as of `wi-2-architecture- | UC-11 | Study Magic | Player | — | | UC-12 | Level Up at Review Board | Player | — | +![Skara Brae Use Case Overview](use-case-overview.png) + +*Figure 1: Use case overview (rendered from the EA model, `docs/openup/model/skara-brae.eapx`, via `scripts/openup/export-diagram-images.ps1`).* + Shared sub-flow **Regenerate Resources**: the system recomputes time-based regeneration **at specific call sites only** — list characters, view character, run adventure, and train stat (routes/mod.rs:264–266, 331, 442, 642). Energy +1 per 180 s, nerve +1 per 300 s, HP +5% of max per 300 s (only if alive and injured), capped at maxima; `last_regen` advances to now. It is **not** applied by equip/unequip, gym view, shop, heal, songs, spells, or level-up. --- diff --git a/docs/openup/requirements/use-case-overview.png b/docs/openup/requirements/use-case-overview.png new file mode 100644 index 0000000..68fbcc5 Binary files /dev/null and b/docs/openup/requirements/use-case-overview.png differ diff --git a/scripts/openup/export-diagram-images.ps1 b/scripts/openup/export-diagram-images.ps1 new file mode 100644 index 0000000..75eabdd --- /dev/null +++ b/scripts/openup/export-diagram-images.ps1 @@ -0,0 +1,90 @@ +# Skara Brae — EA diagram image exporter. +# Renders the model's diagrams to PNG for embedding in the OpenUP markdown documents. +# READ-ONLY on the model (opens diagrams, renders, closes; no model mutation). +# +# EA-15 findings baked in (probe session 2026-10-05): +# - the image methods live on ProjectInterface, NOT on Repository +# (Repository.SaveImageToPath exists but always returns False); +# - PutDiagramImageToFile(id, path, type) with type 0 = PNG; +# - the diagram must be OPEN (repo.OpenDiagram) before export, else a blank +# 241x151 stub is written; +# - works with EA hidden (app.Visible = $false). +# +# Output (keyfob-station convention: requirements overview next to the UC docs, +# realization + design figures in analysis-design/): +# docs/openup/requirements/use-case-overview.png +# docs/openup/analysis-design/uc-01-realization.png .. uc-12-realization.png +# docs/openup/analysis-design/component-architecture.png + +$ErrorActionPreference = 'Stop' + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path +$modelPath = Join-Path $repoRoot 'docs\openup\model\skara-brae.eapx' +if (-not (Test-Path $modelPath)) { + Write-Output "MISSING $modelPath" + exit 1 +} +$reqDir = Join-Path $repoRoot 'docs\openup\requirements' +$adDir = Join-Path $repoRoot 'docs\openup\analysis-design' +$null = New-Item -ItemType Directory -Force -Path $reqDir, $adDir + +$step = 'start' +$app = $null +$eaBefore = @(Get-Process -Name EA -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id) + +function Export-Diagram([object]$project, [object]$repo, [int]$diagramId, [string]$outPath) { + if (Test-Path $outPath) { Remove-Item $outPath -Force } + $null = $repo.OpenDiagram($diagramId) + Start-Sleep -Seconds 2 + $null = $project.PutDiagramImageToFile($diagramId, $outPath, 0) + try { $null = $repo.CloseDiagram($diagramId) } catch { } + if (-not (Test-Path $outPath)) { throw "no image written: $outPath" } + $bytes = (Get-Item $outPath).Length + if ($bytes -lt 5000) { throw "suspiciously small render ($bytes bytes): $outPath" } + Write-Output "exported: $outPath ($bytes bytes)" +} + +try { + $app = New-Object -ComObject EA.App + $app.Visible = $false + $repo = $app.Repository + $null = $repo.OpenFile($modelPath) + $root = $repo.Models.GetAt([int]0) + $project = $repo.GetProjectInterface() + + $step = 'use case overview' + $ucModel = $root.Packages.GetByName('Requirements').Packages.GetByName('Use Case Model') + $ucModel.Diagrams.Refresh() + $overview = $ucModel.Diagrams.GetByName('Skara Brae Use Case Overview') + Export-Diagram $project $repo $overview.DiagramID (Join-Path $reqDir 'use-case-overview.png') + + $step = 'realization diagrams' + $analysisDesign = $root.Packages.GetByName('Analysis-Design') + $analysisDesign.Packages.Refresh() + for ($p = 0; $p -lt $analysisDesign.Packages.Count; $p++) { + $pkg = $analysisDesign.Packages.GetAt([int]$p) + if ($pkg.Name -notlike 'UC-?? Realization') { continue } + $pkg.Diagrams.Refresh() + if ($pkg.Diagrams.Count -lt 1) { throw "no diagram in $($pkg.Name)" } + $number = $pkg.Name.Substring(3, 2) + Export-Diagram $project $repo $pkg.Diagrams.GetAt([int]0).DiagramID (Join-Path $adDir ("uc-{0}-realization.png" -f $number.ToLower())) + } + + $step = 'component architecture' + $design = $analysisDesign.Packages.GetByName('Design') + $design.Diagrams.Refresh() + $componentDiagram = $design.Diagrams.GetByName('Component Architecture') + Export-Diagram $project $repo $componentDiagram.DiagramID (Join-Path $adDir 'component-architecture.png') + + $null = $repo.CloseFile() + Write-Output 'EXPORT OK - 14 diagrams rendered' +} +catch { + Write-Output "FAILED at [$step]: $($_.Exception.Message)" + exit 1 +} +finally { + Get-Process -Name EA -ErrorAction SilentlyContinue | + Where-Object { $eaBefore -notcontains $_.Id } | + ForEach-Object { Stop-Process -Id $_.Id -Force } +}