# Architecture Decision Records

## 철학
FESA의 architecture decision은 solver correctness, verification traceability, implementation testability를 우선한다. Harness는 제품이 아니라 C++/MSVC 기반 FEM 구조해석 솔버 개발을 통제하는 운영 인프라이다.

---

### ADR-001: FESA는 구조해석 솔버 프로젝트이고 Harness는 운영 인프라로 둔다
**결정**: 저장소의 주 목적은 유한요소법 기반 구조해석 솔버 개발이다. Harness는 승인된 Step 계획, 독립 세션 실행, PreToolUse guardrail, Stop MSVC build/test 검증을 제공하는 보조 계층으로 유지한다.

**이유**: 기존 문서가 Harness 중심이면 agent가 solver architecture, FEM verification, Abaqus/HDF5 계약보다 운영 스크립트에 과도하게 맞춰 행동한다.

**트레이드오프**: Harness 문서의 비중은 낮아지지만, `docs/HARNESS_WORKFLOW.md`의 실행 계약과 `.codex/hooks.json`의 검증 정책은 계속 필수 운영 규칙으로 유지한다.

### ADR-002: C++17/MSVC/CMake/CTest를 기본 구현 환경으로 둔다
**결정**: 기본 solver 구현과 validation은 C++17 이상, Visual Studio 17 2022 또는
Visual Studio 18 2026 generator, x64 platform, Debug config, CMake, CTest로 수행한다.
`.harness/config.json`이 없으면 CMake가 설치된 두 승인 generator 중 하나를 자동
선택할 수 있으며, build/test evidence에는 실제 generator와 compiler version을 기록한다.

**이유**: FESA의 목표 환경은 Windows/MSVC 기반 C++이다. CMake/CTest는 solver source tree가 추가되거나 확장될 때 가장 일관된 build/test entry point다. VS17과 VS18을
승인하면 현재 지원되는 MSVC 개발 환경에서 같은 C++17/x64/Debug 계약을 검증할 수 있다.

**트레이드오프**: FESA solver source는 CMake/CTest를 기본으로 유지한다. Generator별
compiler 차이는 각 build/test report에 명시하고, 두 generator 중 하나에서만 관찰되는
실패를 다른 generator의 성공으로 숨기지 않는다. Harness 자체는 `.harness/config.json`에
solution과 test command를 명시한 직접 MSBuild 프로젝트도 검증할 수 있지만, 이는 FESA
제품이 solution-only workflow를 지원한다는 의미가 아니다.

### ADR-003: Abaqus `.inp` full compatibility가 아니라 기능별 keyword subset을 지원한다
**결정**: FESA parser는 Abaqus keyword/data/comment line 규칙을 따르되, 기능별로 승인된 keyword subset만 지원한다. 미지원 keyword는 명확한 diagnostic을 남긴다.

**이유**: full Abaqus compatibility는 초기 solver 범위와 검증 비용을 과도하게 키운다. 기능별 subset은 요구조건, I/O contract, reference validation을 추적 가능하게 만든다.

**트레이드오프**: 사용자는 기존 Abaqus input file을 그대로 사용할 수 없을 수 있다. 대신 지원 범위와 실패 원인이 명확해진다.

### ADR-004: Domain, AnalysisModel, DofManager, AnalysisState를 분리한다
**결정**: `Domain`은 입력 모델 정의를 소유하고, `AnalysisModel`은 현재 step의 실행 view를 제공하며, `DofManager`는 equation numbering과 constrained/free mapping을 전담하고, `AnalysisState`는 해석 중 변하는 물리량을 소유한다.

**이유**: 모델 정의, step activation, equation system, transient/nonlinear state가 섞이면 parser, assembler, solver, result writer가 강하게 결합된다. 분리된 상태 모델은 선형 정적 해석에서 시작해 비선형, 동적, thermal coupling으로 확장하기 쉽다.

**트레이드오프**: 초기 class 수가 늘어난다. V0에서는 interface를 얇게 유지하고 실제 선형 정적 frame에 필요한 state만 구현한다.

### ADR-005: 공식 결과 파일은 HDF5로 하고 reference 결과는 Abaqus CSV로 둔다
**결정**: FESA solver의 authoritative result output은 `results.h5` HDF5이다. Abaqus reference results는 기능별 reference model contract가 지정한 `reference/<model-id>/` 아래 CSV 파일로 저장하며, verification은 FESA HDF5 rows와 Abaqus reference CSV rows를 documented IDs, components, units, coordinate system, step/frame identity, tolerance 기준으로 비교한다. 신규 reference는 canonical 파일명을 사용하고, 승인된 기존 bundle의 legacy alias는 해당 기능 계약에 정확한 경로를 기록한 경우에만 허용한다.

**이유**: 구조해석 결과는 step/frame, field/history, node/element/integration point location, units, coordinate system, schema version을 함께 가져야 한다. HDF5는 이 계층 구조와 metadata를 안정적으로 표현한다.

**트레이드오프**: reference comparison은 FESA HDF5 dataset identity와 Abaqus CSV row identity를 모두 관리해야 한다. FESA HDF5에서 추출한 deterministic CSV view는 디버깅/검토용 보조 artifact로 허용하지만, 공식 solver output이나 reference artifact로 취급하지 않는다.

### ADR-006: 해석 알고리즘과 수치 backend는 Strategy와 Adapter 경계 뒤에 둔다
**결정**: `Analysis`, `LinearSolver`, `TimeIntegrator`, `ConvergenceCriteria`는 Strategy로 구성하고, MKL, TBB, HDF5 API는 adapter 계층 뒤에 둔다. `Vector`는 contiguous `double`, `Matrix`는 row-major contiguous `double`을 소유하고 dense 연산은 MKL CBLAS를 사용한다. `SparseMatrix`는 이들과 상속 관계가 없는 0-based CSR 전용 타입이며, PARDISO factorization과 substitution은 `LinearSolver` 경계 뒤에서 분리한다.

**이유**: 선형 정적, 비선형 정적, 동적, frequency, heat transfer 해석은 공통 흐름을 공유하지만 알고리즘과 backend가 다르다. 외부 API를 core에 노출하면 테스트 double, backend 교체, dependency review가 어려워진다.

**트레이드오프**: 단일 기능만 구현할 때는 adapter가 다소 장황해 보일 수 있다. Row-major dense storage와 CSR sparse storage를 따로 유지해야 하지만 backend 의존성과 dense/sparse 의미가 core 모델에 섞이지 않는다.

### ADR-007: Analysis 실행 흐름은 Template Method로 고정한다
**결정**: `Analysis::run()`은 공통 lifecycle을 고정한다. 선형 정적 V0의 순서는 `parse input -> initialize Domain -> build AnalysisModel -> build DOF map/sparse pattern -> assemble stiffness -> partition constraints -> factorize Kff -> assemble load -> form effective RHS -> substitute -> reconstruct displacement -> recover results -> write HDF5`다. 강성행렬 factorization은 하중벡터 조립보다 먼저 수행하고, factorization과 substitution을 하나의 불투명한 solve 호출로 합치지 않는다.

**이유**: 해석 procedure가 늘어나도 공통 실행 순서가 유지되어야 검증, logging, result writing, failure classification이 일관된다. Factorization과 substitution을 분리하면 동일 강성행렬에 여러 RHS를 적용할 수 있고 각 실패 단계를 구조화된 diagnostic으로 분류할 수 있다.

**트레이드오프**: 특수 해석 절차가 공통 흐름에 맞지 않는 경우 hook point가 필요하다. 초기에는 선형 정적 해석을 기준으로 최소 hook만 둔다.

### ADR-008: Sparse assembly는 deterministic COO-to-CSR 경로로 시작한다
**결정**: 초기 assembly는 element-local contribution을 COO triplet으로 수집한 뒤 CSR로 finalize한다. MKL PARDISO backend는 CSR input contract를 받는다.

**이유**: CSR은 MKL PARDISO와 잘 맞고, COO-to-CSR 경로는 구현과 테스트가 명확하다. deterministic reduction은 reference comparison의 재현성을 지킨다.

**트레이드오프**: 대규모 모델에서는 메모리와 변환 비용이 생길 수 있다. 성능 문제가 실제로 확인되면 typed batch assembly 또는 kernel 분리를 추가한다.

### ADR-009: TBB 병렬화는 element-local computation부터 적용한다
**결정**: 첫 oneTBB 적용 지점은 element-local matrix/residual 계산이다. 전역 sparse write는 thread-local buffer 또는 deterministic reduction으로 제한한다.

**이유**: element-local 계산은 독립성이 높고 병렬화 효과가 명확하다. 전역 sparse matrix에 직접 병렬 write하면 재현성, race, ordering 문제가 생긴다.

**트레이드오프**: 초기 병렬화 범위가 제한된다. MKL 내부 thread와 TBB task arena의 oversubscription 정책을 별도로 문서화해야 한다.

### ADR-010: Abaqus reference artifact는 사람이 생성하거나 명시 승인된 절차로만 갱신한다
**결정**: Agent는 Abaqus, Nastran 또는 reference solver를 직접 실행하지 않는다. reference artifact 생성, 수정, 복원은 명시 승인된 phase에서만 수행한다. 신규 bundle의 provenance는 `metadata.json`에 기록한다. 승인된 `cantilever-beam-b33` legacy baseline은 source commit과 generator, units, coordinate system, schema, tolerance를 설계 및 Reference Model Contract가 대신 기록하므로 이 baseline에 한해서 `metadata.json`과 `README.md`가 N/A다.

**이유**: reference 결과는 solver correctness의 기준이다. 생성 절차가 불명확하면 구현 결함과 reference artifact 오류를 구분할 수 없다.

**트레이드오프**: reference 준비가 느려질 수 있다. Legacy 예외는 일반 artifact 규칙을 복잡하게 하지만 exact path와 source commit을 고정하고 read-only로 취급해 감사 가능성을 유지한다.

### ADR-011: 구형 단일 검증 진입점 계약을 폐기한다

**상태**: ADR-012로 대체됨.

**결정**: 삭제된 legacy validation entry point, legacy Python test discovery, 환경 변수 기반 command override를 Harness의 기본 검증 계약으로 사용하지 않는다.

### ADR-012: Harness는 계획, 실행, Hook 검증의 세 계층으로 운영한다

**결정**: 계획은 `.agents/skills/harness`, Step 실행과 Git 상태 관리는 `scripts/execute.py`, 도구 호출 전 정책과 종료 전 검증은 `.codex/hooks.json`에 연결된 `scripts/hooks/`가 담당한다. C++ production 변경은 관련 테스트 파일이 있어야 하며, 실제 RED 실패와 GREEN 성공은 같은 Step 안에서 확인한다. Stop은 `.harness/config.json` 또는 자동 감지 결과로 전체 MSVC build/test를 검증한다. Harness Python 변경은 `uv run --with pytest python -m pytest -v -rs`로 검증한다.

**이유**: 테스트 파일 존재 검사, TDD 실행 증거, 전체 build/test는 서로 다른 책임이다. 이를 분리하면 Hook이 보장하는 범위를 과장하지 않으면서 Step 종료 시 green 상태를 강제할 수 있다.

**트레이드오프**: PreToolUse만으로 구현 전 RED 실행을 증명할 수 없으므로 Implementation report에 RED/GREEN 명령과 결과를 기록해야 한다. C/C++가 없는 저장소는 Stop이 통과하므로 Harness Python 검증은 별도 acceptance command로 유지한다.

### ADR-013: V0 선형 정적 해석은 단일 step B33 Euler beam으로 제한한다

**결정**: 첫 end-to-end 제품 기능은 입력 파일당 하나의 `*STEP, *STATIC`을 해석하는 2절점 3D Euler–Bernoulli beam이다. Abaqus `TYPE=B33`만 이 요소로 매핑하고 B31은 `unsupported-element-formulation`으로 거부한다. `*PART/*ASSEMBLY/*INSTANCE`는 여러 identity instance와 stable source identity를 지원하지만 instance transform, nested assembly, 다중 step은 지원하지 않는다. Output-request keyword는 승인 allowlist에서 warning 후 no-op 처리하고 FESA 기본 HDF5 결과는 항상 생성한다.

**이유**: B31은 transverse shear deformation을 포함하므로 Euler–Bernoulli 정식화와 의미가 다르다. 단일 step과 identity instance로 범위를 제한하면 parser wrapper, source identity, assembly, 해법 및 결과 복구를 end-to-end로 검증하면서 의미를 왜곡하지 않는다.

**트레이드오프**: 기존 Abaqus 모델 중 B31, instance transform, 다중 step 또는 `*DLOAD`를 사용하는 입력은 거부된다. 이후 기능은 별도 요구조건과 formulation/reference gate를 거쳐 추가한다.

### ADR-014: B33 cantilever baseline은 component-scale 혼합 tolerance로 비교한다

**결정**: `reference/cantilever beam/`의 B33 input, displacement CSV, reaction CSV, elemental-force CSV를 V0 reference baseline으로 사용한다. Displacement와 reaction은 node identity로, section resultant는 CSV node station과 정규화된 FESA element endpoint로 비교한다. 같은 model, step/frame, quantity, component에 대해 `reference_scale = max(abs(reference rows))`를 계산하고 모든 row에 `absolute_floor + 1e-6 * reference_scale`을 적용한다. SI displacement/rotation absolute floor는 `1e-9`, force/moment floor는 `1e-3`이다. Beam stress는 출력하되 Abaqus stress reference comparison은 N/A다.

**이유**: 자유단 moment처럼 이론적으로 0인 값에는 행별 상대오차가 정의되지 않으며 Abaqus 결과에 작은 수치 잔차가 남을 수 있다. Component scale을 사용하면 전체 물리량 크기에 비해 작은 잔차를 허용하면서 각 row를 결정적으로 판정할 수 있다.

**트레이드오프**: 큰 component scale은 zero row의 허용폭을 넓힌다. 이를 완화하기 위해 quantity와 component를 섞지 않고 Abaqus 값만 scale에 사용하며, 모든 row의 absolute/normalized error와 worst row를 보고하고 누락·추가·nonfinite row를 tolerance 전에 실패시킨다.

### ADR-015: 외부 CMake target을 FESA 경계로 정규화하고 Windows runtime closure를 명시한다

**결정**: MKL, TBB, HDF5 CONFIG package가 제공하는 target은 dependency module에서
`Fesa::MKL`, `Fesa::TBB`, `Fesa::HDF5`로 정규화한다. Product target은 이 경계만
`PRIVATE` link하고 public header에 vendor/Win32 type을 노출하지 않는다. Approved Windows
환경에서는 packaged shared HDF5 C target을 우선하며, CLI와 test executable 옆에 TBB,
MKL, OpenMP, Intel math runtime, HDF5를 포함한 실제 runtime DLL closure를 POST_BUILD로
stage한다.

**이유**: Package version이나 shared/static variant에 따라 imported target 이름과 숨은
runtime dependency가 달라질 수 있다. Configure와 link 성공만 확인하면 GoogleTest
discovery 또는 배포된 CLI가 `0xc0000135`로 실패할 수 있다. FESA-owned target과 명시적
runtime closure는 module code와 실행 환경을 package 세부사항에서 분리한다.

**트레이드오프**: Windows staging CMake가 길어지고 새 dynamic dependency마다 runtime
audit가 필요하다. 대신 개인 설치 경로를 source에 하드코딩하거나 developer `PATH`에
의존하지 않고 build tree executable을 재현 가능하게 실행할 수 있다.

### ADR-016: 결정론과 failure atomicity를 correctness contract로 취급한다

**결정**: Parallel element 계산은 index별 독립 buffer만 쓰고, COO tuple ordering과
floating-point sum은 join 이후 고정된 serial 순서로 수행한다. Result recovery, solver
output, HDF5 writer는 candidate를 완성·검증한 뒤 caller-owned state 또는 final file에
commit한다. HDF5는 same-directory temporary file을 checked close/reopen한 후에만 atomic
finalization한다.

**이유**: Reference comparison과 물리 검토는 row identity와 작은 수치 차이에 민감하다.
Thread scheduling 또는 중간 실패가 sparse bytes, result order, 이전 output을 바꾸면 같은
입력에 대한 evidence를 신뢰할 수 없다.

**트레이드오프**: Serial reduction과 candidate copy가 throughput과 memory를 더 사용할 수
있다. 성능 최적화는 동일한 ordering, failure preservation, reference 결과를 증명할 때만
대체할 수 있으며, nondeterministic global sparse write는 허용하지 않는다.

### ADR-017: Essential constraint는 stable elimination으로 적용하고 reaction은 full residual로 정의한다

**결정**: V0는 DofManager의 stable free/constrained order로 full stiffness를
`Kff/Kfc/Kcf/Kcc`로 분할하고 `Kff * df = Ff - Kfc * dc`를 푼 뒤 full displacement를
복구한다. 모든 DOF가 constrained인 모델의 `0 x 0 Kff`는 유효한 trivial factorization과
empty substitution이다. Recovery의 full residual `r = K*d - F`를 reaction vector로
보존하며 constrained entry는 physical reaction, free entry는 equilibrium evidence다.

**이유**: Equation numbering과 prescribed displacement를 한 owner가 관리하면 nonzero
constraint, no/all/mixed constraint가 같은 수식과 stable order를 사용한다. Full residual은
element end action을 별도로 합산하는 경로보다 조립·하중·해법 전체의 equilibrium을 직접
검증한다.

**트레이드오프**: Full K와 네 partition을 유지하는 메모리 비용이 있고 reaction vector의
free entry가 정확한 0이라고 가정할 수 없다. V0에는 penalty, MPC, Lagrange multiplier를
혼합하지 않으며, 추가 constraint policy는 별도 formulation과 equation/output 계약을
요구한다.

### ADR-018: Kernel 존재와 제품 노출을 분리하고 계층형 검증을 요구한다

**결정**: Local element/load/recovery kernel이 존재해도 syntax, semantic Domain mapping,
analysis orchestration, mandatory HDF5와 diagnostic 계약이 연결되지 않으면 CLI 지원으로
간주하지 않는다. 검증은 unit, integration, reference comparison, physics sanity를 서로
대체할 수 없는 계층으로 유지한다. Reference가 가능하지 않은 quantity는 명시적 N/A와
analytical/schema/physics 대체 evidence를 요구한다.

**이유**: Unit test는 수식과 validation을, integration test는 lifetime과 호출 순서를,
reference comparison은 외부 solver와의 수치 일치를, physics sanity는 공통-mode 오류와
model adequacy를 검출한다. 한 계층의 성공만으로 parser exposure 또는 물리 correctness를
추론하면 지원 범위와 검증 강도가 과장된다.

**트레이드오프**: 작은 기능도 여러 계약과 evidence를 함께 준비해야 하므로 개발 속도가
느려진다. 대신 `*DLOAD`처럼 kernel은 있지만 입력에 노출되지 않은 기능, stress처럼
mandatory output이지만 Abaqus reference가 N/A인 기능을 정확하게 표현할 수 있다.
