# Skara Brae — EA Design package builder (post-WI-2 enrichment). # SURGICAL: creates/rebuilds ONLY Analysis-Design / Design (agent-owned, no manual # edits). Requirements, realization packages and Architecture Overview are untouched; # never re-run build-model.ps1 after a manual layout session. # # Source of truth: docs/openup/analysis-design/uc-realization-mapping.md §3 # (joint component model: server crates + skara-brae-client + skara-brae-common). $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 } $components = @( @{ Name = 'skara-brae-client'; Notes = 'Presentation tier. Vue 3.5 SPA (TypeScript, Pinia, Vite); monolithic App.vue @ 34dc02f; JWT bearer auth; REST via Vite proxy to :8080. Serves UC-01..UC-12 as the player-facing boundary. Repo: skara-brae-client.' }, @{ Name = 'sb-api'; Notes = 'Boundary/adapter crate (Axum). Routes/handlers, domain_status status mapping, responses (wire-format freeze, NFR-19). May import sb-core, sb-shared, sb-common, sb-db, sb-auth (design note section 3). Carries the boundaries of all UCs per mapping section 2. Known debt: ShopService/HealingService/parts of MagicService still handler-side.' }, @{ Name = 'sb-auth'; Notes = 'Adapter crate. argon2 password hashing + JWT creation/verification. Serves UC-01 (register), UC-02 (authenticate); auth_middleware guards UC-03..UC-12. Imports sb-shared.' }, @{ Name = 'sb-db'; Notes = 'Adapter crate. Persistence via sb-db::Database. Serves persistence of UC-01..UC-03, UC-05..UC-12 state. Imports sb-shared.' }, @{ Name = 'sb-core'; Notes = 'Domain crate (game engine). Controllers/entities: adventure (UC-08), services train/equip/unequip (UC-05/06), character create/level-up/regen (UC-03/12), combat, spells (UC-11), songs (UC-10), data/items catalogue (UC-04). Must not reference axum/http/StatusCode/serde_json json!/tokio/sb-db/sb-api (design note section 3). DomainError error taxonomy (UC-05/06/08/12 error paths).' }, @{ Name = 'sb-shared'; Notes = 'Domain types crate. User, Character, Item, response types. Zero HTTP imports. Entities of all UCs (mapping section 2).' }, @{ Name = 'sb-common'; Notes = 'Bilingual data package (repo skara-brae-common). data/ JSON (items, enemies, spells, crit tables, zones), rust/ crate (include_str + serde), ts/ package, codegen/. UC-04 data source; consumed by sb-core and sb-api; future client type codegen.' } ) $dependencyRows = @( @{ From = 'skara-brae-client'; To = 'sb-api' }, @{ From = 'sb-api'; To = 'sb-core' }, @{ From = 'sb-api'; To = 'sb-shared' }, @{ From = 'sb-api'; To = 'sb-db' }, @{ From = 'sb-api'; To = 'sb-auth' }, @{ From = 'sb-api'; To = 'sb-common' }, @{ From = 'sb-core'; To = 'sb-shared' }, @{ From = 'sb-core'; To = 'sb-common' }, @{ From = 'sb-db'; To = 'sb-shared' }, @{ From = 'sb-auth'; To = 'sb-shared' } ) $positions = @{ 'skara-brae-client' = @(40, 340, 40, 140) 'sb-api' = @(440, 740, 40, 140) 'sb-core' = @(840, 1140, 40, 140) 'sb-shared' = @(1240, 1540, 40, 140) 'sb-auth' = @(440, 740, 260, 360) 'sb-db' = @(840, 1140, 260, 360) 'sb-common' = @(1240, 1540, 260, 360) } $step = 'start' $app = $null $eaBefore = @(Get-Process -Name EA -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id) try { $app = New-Object -ComObject EA.App $app.Visible = $false $repo = $app.Repository $null = $repo.OpenFile($modelPath) $root = $repo.Models.GetAt([int]0) $step = 'resolve Analysis-Design' $analysisDesign = $root.Packages.GetByName('Analysis-Design') $step = 'rebuild Design package (agent-owned only)' for ($i = 0; $i -lt $analysisDesign.Packages.Count; $i++) { if (($analysisDesign.Packages.GetAt([int]$i)).Name -eq 'Design') { $null = $analysisDesign.Packages.DeleteAt([int]$i, $false) $analysisDesign.Packages.Refresh() break } } $design = $analysisDesign.Packages.AddNew('Design', 'Package') $design.Notes = 'Joint component model of the Skara Brae architecture: server crates, skara-brae-client, skara-brae-common. Dependency rule: inward toward sb-core/sb-shared (domain); adapters (sb-api, sb-db, sb-auth) and the client at the edge. Source: uc-realization-mapping.md section 3. Added 2026-10-03; joint-model decision recorded there (client-side UC realization packages = future work item).' $null = $design.Update() $analysisDesign.Packages.Refresh() $step = 'create components' $elements = @{} foreach ($component in $components) { $element = $design.Elements.AddNew($component.Name, 'Component') $element.Notes = $component.Notes $null = $element.Update() $elements[$component.Name] = $element } $design.Elements.Refresh() $step = 'create dependencies' foreach ($row in $dependencyRows) { $client = $elements[$row.From] $supplier = $elements[$row.To] $connector = $client.Connectors.AddNew('', 'Dependency') $connector.ClientID = $client.ElementID $connector.SupplierID = $supplier.ElementID $null = $connector.Update() $client.Connectors.Refresh() } $step = 'create component diagram' $diagram = $design.Diagrams.AddNew('Component Architecture', 'Component') $diagram.Notes = 'Layered joint architecture: client (edge) -> sb-api (boundary) -> domain (sb-core, sb-shared) with sb-common data; adapters sb-auth, sb-db at the edge of the server.' $null = $diagram.Update() $design.Diagrams.Refresh() $diagram = $design.Diagrams.GetByName('Component Architecture') foreach ($component in $components) { $coords = $positions[$component.Name] $diagramObject = $diagram.DiagramObjects.AddNew('', '') $diagramObject.ElementID = ($elements[$component.Name]).ElementID $diagramObject.left = $coords[0]; $diagramObject.right = $coords[1] $diagramObject.top = $coords[2]; $diagramObject.bottom = $coords[3] $null = $diagramObject.Update() } $step = 'create diagram links' $analysisDesign.Packages.Refresh() $design = $analysisDesign.Packages.GetByName('Design') $design.Elements.Refresh() $elements = @{} for ($e = 0; $e -lt $design.Elements.Count; $e++) { $element = $design.Elements.GetAt([int]$e) if ($element.Type -eq 'Component') { $elements[$element.Name] = $element } } $diagram = $design.Diagrams.GetByName('Component Architecture') foreach ($row in $dependencyRows) { $client = $elements[$row.From] $client.Connectors.Refresh() for ($c = 0; $c -lt $client.Connectors.Count; $c++) { $connector = $client.Connectors.GetAt([int]$c) if ($connector.Type -eq 'Dependency') { $supplier = $elements[$row.To] if ($connector.SupplierID -eq $supplier.ElementID) { $diagram.DiagramLinks.Refresh() $link = $diagram.DiagramLinks.AddNew('', '') $link.ConnectorID = $connector.ConnectorID $null = $link.Update() } } } } $null = $diagram.Update() $step = 'verify' $componentCount = 0; $dependencyCount = 0 $design = $analysisDesign.Packages.GetByName('Design') $design.Elements.Refresh() for ($e = 0; $e -lt $design.Elements.Count; $e++) { $element = $design.Elements.GetAt([int]$e) if ($element.Type -eq 'Component') { $componentCount++ } } foreach ($name in $elements.Keys) { $element = $elements[$name] $element.Connectors.Refresh() for ($c = 0; $c -lt $element.Connectors.Count; $c++) { $connector = $element.Connectors.GetAt([int]$c) if ($connector.Type -eq 'Dependency' -and $connector.ClientID -eq $element.ElementID) { $dependencyCount++ } } } Write-Output "VERIFY components=$componentCount (expect 7), dependencies=$dependencyCount (expect 10)" if ($componentCount -ne 7 -or $dependencyCount -ne 10) { Write-Output 'VERIFY FAILED'; exit 1 } $null = $repo.CloseFile() Write-Output 'ALL STEPS OK - Design package created; nothing else touched' } 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 } }