@startuml Shared_Class

' ── Skin ─────────────────────────────────────────────────────────────────────

skinparam linetype ortho
skinparam nodesep 35
skinparam ranksep 45

skinparam ClassBackgroundColor        #F8F7FF
skinparam ClassBorderColor            #AFA9EC
skinparam ClassBorderThickness        1.5
skinparam ClassFontName               Helvetica
skinparam ClassFontSize               11
skinparam ClassFontColor              #26215C
skinparam ClassHeaderBackgroundColor  #EDE9F7
skinparam ClassArrowColor             #7F77DD
skinparam ClassArrowThickness         1.5

skinparam InterfaceBackgroundColor    #EDE9F7
skinparam InterfaceBorderColor        #7F77DD
skinparam InterfaceFontColor          #26215C
skinparam InterfaceFontName           Helvetica
skinparam InterfaceFontSize           11

skinparam PackageBackgroundColor      #F9F8FC
skinparam PackageBorderColor          #C5C1EB
skinparam PackageFontColor            #534AB7
skinparam PackageFontSize             11
skinparam PackageFontStyle            bold

skinparam NoteBackgroundColor         #FAEEDA
skinparam NoteBorderColor             #EF9F27
skinparam ShadowingEnabled            false
skinparam RoundCorner                 8

skinparam ClassBackgroundColor<<model>> #F8F7FF
skinparam ClassBorderColor<<model>>     #AFA9EC
skinparam ClassBackgroundColor<<enum>>  #FFF8F0
skinparam ClassBorderColor<<enum>>      #D4B896

' ── shared/ports ─────────────────────────────────────────────────────────────

package "shared/ports" {

  together {
    interface OrchestratorPort {
      + run(SessionRequest) : Result<Vec<SessionOutcome>>
    }
    interface LlmEnginePort {
      + run(RoundSignal) : Result<LlmSignal>
    }
    interface FuzzerEnginePort {
      + run(Vec<RoundSignal>) : Result<Vec<FuzzReport>>
    }
  }

  together {
    interface ExecutorPort {
      + execute(ExecutorInput) : Result<()>
    }
    interface ReaderPort {
      + get_contract_context(path, comments) : Result<ContractContext>
      + get_fuzz_output(path) : Result<Option<String>>
      + get_coverage_context(path) : Result<Option<CoverageContext>>
      + get_existing_bodies(path) : Result<Option<BodiesJson>>
      + get_existing_config(path) : Result<Option<FuzzerConfigArtifact>>
    }
    interface ReporterPort {
      + emit(SessionOutcome) : Result<()>
      + emit_round_usage(RoundUsage) : Result<()>
      + emit_stage_event(StageEvent) : Result<()>
      + emit_compile_error(round, msg) : Result<()>
    }
  }

  interface SecurityAnalysisPort {
    + analyze(SecurityAnalysisRequest) : Result<String>
  }

  class SecurityAnalysisRequest <<model>> {
    + contract_name : String
    + source_code : String
    + confirmed_bugs : Vec<BugInfo>
    + fuzz_output : Option<String>
    + rounds_completed : u32
    + previous_analysis : Option<String>
  }

}

' ── shared/requests ──────────────────────────────────────────────────────────

package "shared/requests" {

  class SessionRequest <<model>> {
    + target_paths : Vec<String>
    + max_rounds : u32
    + config : SessionConfig
  }

  class RoundSignal <<model>> {
    + round : u32
    + config : SessionConfig
    + contract_name : String
    + contract_path : String
    + source_code : String
    + source_pragma : String
    + fuzz_output : Option<String>
    + coverage_context : Option<CoverageContext>
    + existing_bodies : Option<BodiesJson>
    + existing_foundry_config : Option<FoundryConfig>
    + confirmed_bugs : Vec<BugInfo>
    + security_analysis : Option<String>
  }

}

' ── shared/responses ─────────────────────────────────────────────────────────

package "shared/responses" {

  together {
    class SessionOutcome <<model>> {
      + reason : TerminationReason
      + contract_name : String
      + rounds_completed : u32
      + bugs : Vec<BugInfo>
      + coverage_snapshots : Vec<String>
      + security_analysis : Option<String>
    }
    enum TerminationReason <<enum>> {
      Bug
      Exhausted
      FullCoverage
      DevTestFailed
      CompileError
    }
  }

  together {
    class LlmSignal <<model>> {
      + status : LlmStatus
      + result : Option<GenerationResult>
      + final_bodies : Option<BodiesJson>
      + stripped_invariant_codes : HashMap<String, String>
    }
    enum LlmStatus <<enum>> {
      Done
      Failed
    }
  }

  together {
    class FuzzReport <<model>> {
      + outcome : FuzzOutcome
      + bugs : Vec<BugInfo>
      + lcov_path : Option<PathBuf>
    }
    enum FuzzOutcome <<enum>> {
      Bug
      Pass
      FullCoverage
      CompileError
      DevTestFailed
    }
  }

  together {
    class RoundUsage <<model>> {
      + contract_name : String
      + round : u32
      + usage : GenerationUsage
    }
    class TerminationDecision <<model>> {
      + terminate : bool
      + reason : Option<TerminationReason>
    }
  }

  class StageEvent <<model>> {
    + contract_name : Option<String>
    + round : u32
    + stage : StageKind
    + status : StageStatus
    + fuzzer_summary : Option<FuzzerRoundSummary>
  }

  together {
    enum StageKind <<enum>> {
      Llm
      Executor
      Fuzzer
      ContractDone
      SessionDone
    }
    enum StageStatus <<enum>> {
      Started
      Finished
      Failed
    }
    class FuzzerRoundSummary <<model>> {
      + bugs : usize
      + passed : usize
      + compile_errors : usize
    }
  }

}

' ── shared/models ─────────────────────────────────────────────────────────────

package "shared/models" {

  together {
    class SessionState <<model>> {
      + rounds_remaining : u32
      + current_round : u32
      + config : SessionConfig
      + found_bugs : HashMap<String, Vec<BugInfo>>
      + full_coverage_streak : HashMap<String, u32>
      + coverage_snapshots : HashMap<String, Vec<String>>
      + llm_failures : HashMap<String, String>
      + security_analyses : HashMap<String, String>
    }
    class SessionConfig <<model>> {
      + model : String
      + language : Language
      + fuzzer : Fuzzer
      + workspace_root : PathBuf
      + max_tokens : Option<u32>
      + llm_timeout_secs : u64
      + full_coverage_rounds : u32
      + prompt_mode : PromptMode
    }
  }

  together {
    enum Language <<enum>> {
      Solidity
    }
    enum Fuzzer <<enum>> {
      Foundry
    }
    enum PromptMode <<enum>> {
      Concise
      Guided
    }
  }

  together {
    class AssembledPrompt <<model>> {
      + messages : Vec<Message>
      + round : u32
      + context_sections : Vec<String>
    }
    class Message <<model>> {
      + role : Role
      + content : String
    }
    enum Role <<enum>> {
      System
      User
      Assistant
    }
  }

  together {
    class BodiesJson <<model>> {
      + meta : BodiesMeta
      + handler : HandlerBodies
      + invariant_test : InvariantTestBodies
    }
    class BodiesMeta <<model>> {
      + contract : String
      + contract_path : String
      + solidity : String
      + generated_at : String
    }
  }

  together {
    class HandlerBodies <<model>> {
      + contract_name : String
      + imports : Vec<String>
      + state_vars : Vec<String>
      + ghost_vars : Vec<String>
      + functions : IndexMap<String, String>
      + target_selectors : String
    }
    class InvariantTestBodies <<model>> {
      + contract_name : String
      + imports : Vec<String>
      + state_vars : Vec<String>
      + set_up_body : Vec<String>
      + invariants : IndexMap<String, String>
    }
  }

  together {
    class FoundryConfig <<model>> {
      + depth : u32
      + runs : u32
      + seed : String
      + max_test_rejects : u32
      + dictionary_weight : u32
      + call_sequence_weights : HashMap<String, f64>
    }
    enum FuzzerConfigArtifact <<enum>> {
      Foundry(FoundryConfig)
    }
  }

  together {
    enum ExecutorInput <<enum>> {
      Full { bodies, fuzzer_config, source_pragma }
      Patch { existing_bodies, bodies_updates,\nexisting_config, config_updates, source_pragma }
    }
    class JsonBlockUpdate <<model>> {
      + op : JsonPatchOp
      + path : String
      + value : Value
      + reason : String
    }
    enum JsonPatchOp <<enum>> {
      Add
      Replace
      Remove
    }
  }

  class BugInfo <<model>> {
    + invariant_name : String
    + call_sequence : String
    + invariant_code : String
  }

  together {
    class ContractContext <<model>> {
      + source_code : String
    }
    class CoverageContext <<model>> {
      + gaps : Vec<CoverageGap>
      + line_found : u32
      + line_hit : u32
      + branch_found : u32
      + branch_hit : u32
      + function_found : u32
      + function_hit : u32
    }
    class CoverageGap <<model>> {
      + file : String
      + line : u32
      + gap_type : GapType
      + source_context : Vec<String>
    }
    enum GapType <<enum>> {
      Line
      Branch
      Function
    }
  }

  together {
    class RunnerResult <<model>> {
      + exit_code : i32
      + stdout : String
      + stderr : String
    }
    class CoverageResult <<model>> {
      + exit_code : i32
      + lcov_content : Option<String>
    }
    class GenerationUsage <<model>> {
      + calls : u64
      + prompt_tokens : u64
      + completion_tokens : u64
      + cached_prompt_tokens : u64
      + reasoning_tokens : u64
    }
  }

}

' ── Relationships ─────────────────────────────────────────────────────────────

' Ports referencing request/response types
OrchestratorPort     ..>  SessionRequest       : receives
OrchestratorPort     ..>  SessionOutcome       : returns
LlmEnginePort        ..>  RoundSignal          : receives
LlmEnginePort        ..>  LlmSignal            : returns
FuzzerEnginePort     ..>  RoundSignal          : receives
FuzzerEnginePort     ..>  FuzzReport           : returns
ExecutorPort         ..>  ExecutorInput        : receives
SecurityAnalysisPort ..>  SecurityAnalysisRequest : receives
SecurityAnalysisRequest *--> BugInfo

' Request composition
SessionRequest   *-->  SessionConfig
RoundSignal      *-->  SessionConfig
RoundSignal      ..>   CoverageContext
RoundSignal      ..>   BodiesJson
RoundSignal      ..>   FoundryConfig
RoundSignal      ..>   BugInfo

' Response composition
SessionOutcome   *-->  TerminationReason
SessionOutcome   *-->  BugInfo
LlmSignal        *-->  LlmStatus
FuzzReport       *-->  FuzzOutcome
FuzzReport       *-->  BugInfo
RoundUsage       *-->  GenerationUsage
StageEvent       *-->  StageKind
StageEvent       *-->  StageStatus
StageEvent       ..>   FuzzerRoundSummary
TerminationDecision *-->  TerminationReason

' Model composition
SessionState     *-->  SessionConfig
SessionState     ..>   BugInfo
SessionConfig    *-->  Language
SessionConfig    *-->  Fuzzer
SessionConfig    *-->  PromptMode
AssembledPrompt  *-->  Message
Message          *-->  Role
BodiesJson       *-->  BodiesMeta
BodiesJson       *-->  HandlerBodies
BodiesJson       *-->  InvariantTestBodies
FuzzerConfigArtifact *-->  FoundryConfig
ExecutorInput    ..>   BodiesJson
ExecutorInput    ..>   FuzzerConfigArtifact
ExecutorInput    ..>   JsonBlockUpdate
JsonBlockUpdate  *-->  JsonPatchOp
CoverageContext  *-->  CoverageGap
CoverageGap      *-->  GapType

legend bottom left
  **Legend**
  ----
  <color:#7F77DD>- - ▷</color>  **..|>**  implements / realizes interface
  <color:#7F77DD>◆ ──▶</color>  ***-->**  composition — struct owns a port or field
  <color:#7F77DD>- - ▶</color>  **..>**   calls · receives · produces
endlegend

@enduml
