refactor(0.6.0): introduce immutable runtime component groups - #228
Conversation
Epic 2.1: replace TramaiEngine's flat dependency wiring with an immutable, validated component snapshot. - EngineComponents with 7 cohesive groups (provider, tool, security, approval, persistence, observation, execution) - ApprovalCapability sealed interface: partial approval composition is unrepresentable; all 6 partial states rejected at construction - EngineComponentFactory: single authoritative composition boundary; policy fallback resolved at construction, not at runtime - TramaiEngine: canonical private ctor takes the snapshot; 5 public JVM ctor descriptors preserved byte-identical via the factory bridge - Tramai.Builder.build(): approval pre-flight restored as check() (IllegalStateException) — engine is lazy, so the compatibility guard keeps the public failure point at build(); factory stays authoritative - Runtime body untouched; same-named locals derived from components - MQ-0017 deviation: component snapshot refs counted as registry state by the global-state scanner (false positive) - Tests: EngineComponentsTest (8 approval states, default snapshot, policy resolution, identity), TramaiComponentCompositionTest (freeze, no-op, legacy policy, partial-at-build), ApprovalEngineEdgeCaseTest partial-config test now asserts construction rejection Zero public API change; api dumps byte-identical; verifyCancellationSafety clean.
There was a problem hiding this comment.
Pull request overview
Refactors TramaiEngine’s internal runtime composition into a single immutable, validated EngineComponents snapshot created at an explicit composition boundary (EngineComponentFactory), reducing scattered/lazy validation while preserving the existing public construction surface.
Changes:
- Introduces
EngineComponents(with grouped subcomponents) andEngineComponentFactoryto validate and freeze engine runtime collaborators up front, including explicitApprovalCapabilitystate. - Updates
TramaiEngineconstruction to bridge existing public constructors to the new snapshot while keeping the main runtime body intact. - Adds/updates tests to assert component snapshot behavior, policy resolution at construction, and early rejection of partial approval configuration; updates docs/changelog and records a temporary maintainability deviation.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tramai-standalone/src/test/kotlin/dev/tramai/standalone/TramaiComponentCompositionTest.kt | New standalone tests covering builder immutability/freeze behavior and partial approval failure at build(). |
| tramai-standalone/src/main/kotlin/dev/tramai/standalone/Tramai.kt | Adds compatibility pre-flight notes and preserves build() failure semantics for partial approval configuration. |
| tramai-engine/src/test/kotlin/dev/tramai/engine/EngineComponentsTest.kt | New tests for EngineComponentFactory snapshot creation, approval capability validation, and policy resolution. |
| tramai-engine/src/test/kotlin/dev/tramai/engine/ApprovalEngineEdgeCaseTest.kt | Updates an edge-case test to reflect construction-time rejection of partial approval composition. |
| tramai-engine/src/main/kotlin/dev/tramai/engine/TramaiEngine.kt | Refactors constructors to route through EngineComponents while leaving the core runtime logic largely unchanged. |
| tramai-engine/src/main/kotlin/dev/tramai/engine/components/EngineComponents.kt | Adds the immutable component snapshot model and grouped component types, including ApprovalCapability. |
| tramai-engine/src/main/kotlin/dev/tramai/engine/components/EngineComponentFactory.kt | Adds the single composition/validation boundary responsible for snapshot creation and policy fallback resolution. |
| docs/modules/tramai-engine.md | Notes that public construction is unchanged while internal construction now validates/freezes a snapshot. |
| config/quality/maintainability-deviations.yml | Adds MQ-0017 deviation entry for snapshot fields flagged by the maintainability scanner. |
| CHANGELOG.md | Records the internal composition refactor and the earlier failure for partial approval misconfiguration. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
|
||
| ### Added | ||
|
|
||
| - Refactored the engine's internal runtime composition into an immutable component snapshot; public API and runtime behaviour are unchanged. Partially configured approval suspension now fails at the engine component boundary. |
| ) : this(EngineComponentFactory.create( | ||
| providerRegistry, structuredOutputHandler, toolRegistry, operationObserver, operationInterceptor, responseCache, | ||
| modelRegistry, modelRegistrySettings, circuitBreakerSettings, retryPolicySettings, tokenBudgetSettings, promptSanitizer, | ||
| chatMemory, conversationIdProvider, job, scope, policyEngine, dlpInterceptor, dlpRedactionAuditEmitter, | ||
| toolResultFilteringSettings, engineEventObserver, toolFailureDiagnosticObserver, policyDecisionAuditEmitter, | ||
| suspendedInvocationStore, approvalContinuationStore, toolArgumentsDigester, approvalGateCoordinator, | ||
| approvalLifecycleAuditEmitter, clock, | ||
| )) |
…— review round 1 P2: ExecutionComponents no longer carries the caller-supplied job/scope compatibility parameters; public ctor descriptors preserved. Engine work parents exclusively to the internally owned lifecycleJob/lifecycleScope (PR #226); the compat scope was verified dead on every launch path. Also: group KDocs reworded (caller vs engine ownership), ROADMAP Epic 2.1 marked complete, CHANGELOG + PR wording tightened.
Fix Round 1 (review findings addressed)
Gates re-run on the new head: |
…hot — review round 2 P2: the central execution coordinator now takes EngineComponents plus explicit runtime-created state (circuit breaker, retry policy, lifecycle job/scope, service definition, resume registry) instead of the flat ~30-dependency list. Config dependencies are derived as same-named locals; method bodies untouched; approval triple derives from ApprovalCapability. Epic 2.1's 'cohesive component groups' acceptance criterion now holds through the coordinator boundary. Also: stale [job]/[scope] KDoc links -> plain text; DLP call indentation restored; test dummyHandler migrates to the factory-built snapshot.
Fix Round 2 (review findings addressed)
Gates on |
refactor(0.6.0): introduce immutable runtime component groups
Closes Epic 2.1 (Runtime Composition Model — Phase 2, first PR).
What & why
TramaiEnginehad ~30 flat constructor dependencies (registries, observers, caches, policy,DLP, approval stores, lifecycle objects, clock) with nullable capability clusters validated
scattered across builders and discovered lazily at runtime. This PR replaces that with an
immutable, validated runtime configuration snapshot — one authoritative composition
boundary. Pure architectural refactor: zero public API change and no execution-semantic changes,
except the intentional Epic 2.1 fail-fast rejection of invalid partial composition.
Component model
ApprovalCapability(sealed): the nullable continuation-store/digester/coordinatortriple is replaced by an explicit
Disabled/Enabledstate — partial approval compositionis unrepresentable in the runtime model.
EngineComponentFactory: the one boundary where validation + snapshot happens.Policy fallback (
policyEngine ?: LegacyPermissivePolicyEngine) is resolved atconstruction, not during execution. All 6 partial approval states are rejected with
IllegalArgumentException; 2 valid states accepted.TramaiEngine: canonical internal constructor takes the snapshot; the 5 published JVMconstructor descriptors are preserved byte-identical via a compatibility bridge
(
public ctor → EngineComponentFactory.create(...) → internal ctor). The engine and itscentral
TramaiInvocationHandlercoordinator both consume the snapshot directly and derivesame-named locals from it — execution method bodies are untouched.
Tramai.Builder.build(): keeps itscheck()pre-flight (IllegalStateException) as acompatibility guard —
Tramaimaterializes its engine lazily, so this keeps the publicfailure point at
build(); the factory remains authoritative.Behaviour change (intentional, per Epic 2.1)
TramaiEngine, fails lazily at tool executionIllegalArgumentException)Immutability contract
The composition snapshot is immutable after construction: TramAI does not dynamically
substitute dependencies or retain mutable builder configuration. Caller-supplied
collaborators (providers, stores, observers) retain their documented ownership/thread-safety
semantics — snapshot immutability is claimed, not deep immutability of caller-owned objects.
Tests
EngineComponentsTest(tramai-engine): default snapshot complete; policy resolution atconstruction (legacy + custom); final-reference identity; all 8 approval states
(2 valid + 6 partial, exact message asserted)
TramaiComponentCompositionTest(standalone): partial approval fails atbuild()(IllegalStateException, pre-refactor(0.6.0): introduce immutable runtime component groups #228 contract); builder mutation after build cannot mutate the
built runtime (provider + observer freeze); no-op defaults; legacy policy; complete
approval wires through the bridge
ApprovalEngineEdgeCaseTest.missing digester: adapted from lazy-execution failure toconstruction rejection (the old premise no longer exists — that is the epic's invariant)
Quality
verifyPr -PchangeClass=runtime-behaviour✅ (maintainability baseline PASSED via MQ-0017,change policy PASSED)
:tramai-engine:apiCheck/:tramai-standalone:apiCheck✅ — API dumps byte-identicalverifyCancellationSafety✅ (292 findings, no new critical/high)MQ-0017 deviation (4
NEW_GLOBAL_STATE_FINDINGin:tramai-engine): the scanner countsthe component snapshot's public data-class fields (typed
ProviderRegistry/ToolRegistry/ModelRegistry) as exposed registry state. They are immutable final snapshot references —the frozen configuration boundary. Resolves when the scanner distinguishes snapshot refs.
Non-goals (explicit)
kept; they dissolve naturally during coordinator extraction)
TramaiEngine.ktcleanupFix Round 1 (review findings addressed)
job/scopefromExecutionComponentsandEngineComponentFactory.create(...); public ctor descriptors untouched (still acceptjob/scope); the dead handlerscopeparam and dead engine locals removed. Runtime already parents all work to the internally ownedlifecycleJob/lifecycleScope(PR #226) — verified no launch path ever used the compat scopedocs/ROADMAP-0.6.0.md: Epic 2.1 marked ✅ Complete — PR #228 (tasks + acceptance criteria checked)Fix Round 2 (review findings addressed)
TramaiInvocationHandlerstill received the flat 30-dependency explosion, so the composition boundary stopped one layer too earlyEngineComponents+ explicit runtime-created state (circuit breaker, retry policy, lifecycle job/scope, service definition, resume registry); the ~30 config dependencies are derived as same-named locals from the snapshot. Method bodies untouched; the approval triple derives fromApprovalCapability[job]/[scope]KDoc symbol linkssanitizeToolTextcall blocks re-aligned to the original indentationGates on the new head:
verifyPr -PchangeClass=runtime-behaviour✅ ·apiCheck(both) ✅ ·verifyCancellationSafety✅ (292/292) · engine + standalone suites green.