Skip to content

refactor(0.6.0): introduce immutable runtime component groups - #228

Merged
GionaGranchelli merged 4 commits into
masterfrom
refactor/0.6.0-engine-components
Aug 12, 2026
Merged

refactor(0.6.0): introduce immutable runtime component groups#228
GionaGranchelli merged 4 commits into
masterfrom
refactor/0.6.0-engine-components

Conversation

@GionaGranchelli

@GionaGranchelli GionaGranchelli commented Aug 12, 2026

Copy link
Copy Markdown
Owner

refactor(0.6.0): introduce immutable runtime component groups

Closes Epic 2.1 (Runtime Composition Model — Phase 2, first PR).

What & why

TramaiEngine had ~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

EngineComponents
├── ProviderComponents     providerRegistry
├── ToolComponents         toolRegistry, toolResultFilteringSettings
├── SecurityComponents     resolvedPolicyEngine (+isLegacyFallback), promptSanitizer,
│                          modelRegistry/settings, DLP interceptor + redaction audit,
│                          policy decision audit
├── ApprovalComponents     suspendedInvocationStore, approvalLifecycleAuditEmitter,
│                          capability: ApprovalCapability (Disabled | Enabled)
├── PersistenceComponents  responseCache, chatMemory, conversationIdProvider
├── ObservationComponents  operationObserver/interceptor, engineEventObserver,
│                          toolFailure + structured-output diagnostic observers
└── ExecutionComponents    structuredOutputHandler, circuitBreaker/retry/tokenBudget
                           settings, clock, job, scope
  • ApprovalCapability (sealed): the nullable continuation-store/digester/coordinator
    triple is replaced by an explicit Disabled/Enabled state — partial approval composition
    is unrepresentable in the runtime model.
  • EngineComponentFactory: the one boundary where validation + snapshot happens.
    Policy fallback (policyEngine ?: LegacyPermissivePolicyEngine) is resolved at
    construction, 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 JVM
    constructor descriptors are preserved byte-identical via a compatibility bridge
    (public ctor → EngineComponentFactory.create(...) → internal ctor). The engine and its
    central TramaiInvocationHandler coordinator both consume the snapshot directly and derive
    same-named locals from it — execution method bodies are untouched.
  • Tramai.Builder.build(): keeps its check() pre-flight (IllegalStateException) as a
    compatibility guard — Tramai materializes its engine lazily, so this keeps the public
    failure point at build(); the factory remains authoritative.

Behaviour change (intentional, per Epic 2.1)

Before After
Partial approval config accepted by TramaiEngine, fails lazily at tool execution Partial approval config rejected at construction (IllegalArgumentException)
Policy fallback resolved in the engine body Policy fallback resolved once at construction

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 at
    construction (legacy + custom); final-reference identity; all 8 approval states
    (2 valid + 6 partial, exact message asserted)
  • TramaiComponentCompositionTest (standalone): partial approval fails at build()
    (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 to
    construction 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-identical
  • verifyCancellationSafety ✅ (292 findings, no new critical/high)

MQ-0017 deviation (4 NEW_GLOBAL_STATE_FINDING in :tramai-engine): the scanner counts
the 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)

  1. No provider-routing redesign — Epic 2.2 (ProviderComponents wraps today's authoritative registry)
  2. No execution-pipeline extraction — Phase 3 (the snapshot-derived locals are intentionally
    kept; they dissolve naturally during coordinator extraction)
  3. No policy/approval/retry semantic changes
  4. No new public configuration API
  5. No constructor cleanup that breaks ABI
  6. No opportunistic TramaiEngine.kt cleanup

Fix Round 1 (review findings addressed)

Finding Fix
P2: ABI-only caller job/scope in ExecutionComponents look like real runtime dependencies Removed job/scope from ExecutionComponents and EngineComponentFactory.create(...); public ctor descriptors untouched (still accept job/scope); the dead handler scope param and dead engine locals removed. Runtime already parents all work to the internally owned lifecycleJob/lifecycleScope (PR #226) — verified no launch path ever used the compat scope
P3: Component KDocs blur caller vs engine ownership Reworded all 7 group KDocs: "Runtime snapshot of X; caller-supplied collaborators remain caller-owned"
P3: PR claimed Epic 2.1 complete without roadmap update docs/ROADMAP-0.6.0.md: Epic 2.1 marked ✅ Complete — PR #228 (tasks + acceptance criteria checked)
P3: "zero intentional behaviour change" contradicts the documented approval fail-fast Opening + CHANGELOG reworded: "zero public API change and no execution-semantic changes, except the intentional Epic 2.1 fail-fast rejection of invalid partial composition"

Fix Round 2 (review findings addressed)

Finding Fix
P2: TramaiInvocationHandler still received the flat 30-dependency explosion, so the composition boundary stopped one layer too early Handler now takes EngineComponents + 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 from ApprovalCapability
P3: PR body still listed job/scope in ExecutionComponents Body corrected (job/scope removed; engine/handler snapshot-consumption phrasing updated)
P3: stale [job]/[scope] KDoc symbol links Reworded to plain text: "legacy caller-supplied job / scope constructor parameters" (lifecycle + close() docs)
P3: recovered DLP named-arg indentation All 4 sanitizeToolText call blocks re-aligned to the original indentation

Gates on the new head: verifyPr -PchangeClass=runtime-behaviour ✅ · apiCheck (both) ✅ · verifyCancellationSafety ✅ (292/292) · engine + standalone suites green.

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.
Copilot AI lite review requested due to automatic review settings August 12, 2026 15:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) and EngineComponentFactory to validate and freeze engine runtime collaborators up front, including explicit ApprovalCapability state.
  • Updates TramaiEngine construction 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.

Comment thread CHANGELOG.md Outdated

### 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.
Comment on lines +197 to +204
) : 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.
@GionaGranchelli

Copy link
Copy Markdown
Owner Author

Fix Round 1 (review findings addressed)

Finding Fix
P2: ABI-only caller job/scope in ExecutionComponents look like real runtime dependencies Removed job/scope from ExecutionComponents and EngineComponentFactory.create(...); public constructor descriptors untouched (still accept the params). Removed the dead scope param from TramaiInvocationHandler and the dead engine locals. Verified no launch path ever used the compat scope — all engine work parents to the internally owned lifecycleJob/lifecycleScope (PR #226 model)
P3: Component KDocs blur caller vs engine ownership Reworded all 7 group KDocs: 'Runtime snapshot of X; caller-supplied collaborators remain caller-owned'
P3: PR claimed Epic 2.1 complete without roadmap update docs/ROADMAP-0.6.0.md: Epic 2.1 marked ✅ Complete — PR #228 (tasks + acceptance criteria checked)
P3: 'zero intentional behaviour change' contradicts the documented approval fail-fast Opening + CHANGELOG reworded: zero public API change and no execution-semantic changes, except the intentional Epic 2.1 fail-fast rejection of invalid partial composition

Gates re-run on the new head: verifyPr -PchangeClass=runtime-behaviour ✅ · apiCheck (both modules) ✅ — public descriptors byte-identical · engine + standalone suites green.

…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.
@GionaGranchelli

Copy link
Copy Markdown
Owner Author

Fix Round 2 (review findings addressed)

Finding Fix
P2: TramaiInvocationHandler still received the flat 30-dependency explosion Handler now takes EngineComponents + explicit runtime-created state (circuit breaker, retry policy, lifecycle job/scope, service definition, resume registry). The ~30 config deps are derived as same-named locals from the snapshot; approval triple derives from ApprovalCapability. Method bodies untouched. EngineComponents now survives all the way to the coordinator — no re-explosion
P3: PR body stale (job/scope listed, 'body untouched'/'30 locals' phrasing) Body corrected
P3: [job]/[scope] KDoc links Plain text: 'legacy caller-supplied job / scope constructor parameters'
P3: DLP call indentation All 4 sanitizeToolText blocks re-aligned

Gates on ca465d3c: verifyPr -PchangeClass=runtime-behaviour ✅ · apiCheck both ✅ (descriptors byte-identical) · verifyCancellationSafety ✅ (292/292) · engine + standalone suites green.

@GionaGranchelli
GionaGranchelli merged commit c591c35 into master Aug 12, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants