skara-brae-common/scripts/openup/add-design-package.ps1
Jan-Peter von Hunnius c0226df62d
All checks were successful
build / build-test (push) Successful in 16s
openup: EA model built + enriched - baseline, associations, joint design package
Windows/EA session per BUILD-CHECKLIST 0-6 (the planned human-gate path,
executed on the EA machine). Baseline built from EABase.eapx via
build-model.ps1 and verified per checklist 4: 3 actors, 12 UCs with
scenarios, 13 associations, 0 include connectors, 7 SS requirements,
12 realization packages / 36 elements; UC-08 MSS (9 steps) and UC-11
learn spell level (3 steps) match use-case-model.md verbatim.

Script fixes (defects 1-3 of the session log): PS 5.1 hashtable parse
error; realization UC lookup now accepts short and full use-case names.

Enrichment after a manual layout pass - model changes are surgical from
now on, re-running build-model.ps1 is forbidden (checklist 6):
- add-analysis-associations.ps1: 24 analysis associations per mapping
  2a (boundary-controller-entity; UC-04 boundary-entity) rendered via
  DiagramLinks without touching manual layout; 4 uses-dependencies to
  DomainError (UC-05/06/08/12 error paths, model-level)
- add-design-package.ps1: Analysis-Design/Design - joint component
  model (skara-brae-client, sb-api, sb-auth, sb-db, sb-core, sb-shared,
  sb-common; 10 dependencies, rule inward). Joint-model decision
  recorded in mapping 1: one model for server+client+common; client-
  side UC realizations are a future work item.

Markdown sources updated first (drift rule): mapping 1 tree + 2a
interactions + 3 design component model; checklist 6. Exports
regenerated from the model: use-case-model.json new,
uc-realizations.json re-exported (useCase full names, notes merged,
crossCutting added; element content verified equal to the
markdown-to-json generator output).

Open: XMI backup - ExportPackageXMI fails via COM (DISP_E_TYPEMISMATCH,
PS 5.1 + cscript); manual EA UI export per model/README.md option 2.
2026-10-03 19:47:59 +02:00

180 lines
8.7 KiB
PowerShell

# 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 }
}