fix(data-integrity): Replace unconditional commit with flush in LLMTraceRepository.save()
CI / push-validation (pull_request) Successful in 22s
CI / helm (pull_request) Successful in 31s
CI / lint (pull_request) Failing after 59s
CI / build (pull_request) Successful in 3m50s
CI / quality (pull_request) Successful in 4m20s
CI / typecheck (pull_request) Successful in 4m40s
CI / security (pull_request) Successful in 4m46s
CI / coverage (pull_request) Has been skipped
CI / unit_tests (pull_request) Failing after 5m44s
CI / docker (pull_request) Has been skipped
CI / integration_tests (pull_request) Successful in 6m52s
CI / e2e_tests (pull_request) Successful in 6m58s
CI / status-check (pull_request) Failing after 3s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Has been skipped
CI / push-validation (pull_request) Successful in 22s
CI / helm (pull_request) Successful in 31s
CI / lint (pull_request) Failing after 59s
CI / build (pull_request) Successful in 3m50s
CI / quality (pull_request) Successful in 4m20s
CI / typecheck (pull_request) Successful in 4m40s
CI / security (pull_request) Successful in 4m46s
CI / coverage (pull_request) Has been skipped
CI / unit_tests (pull_request) Failing after 5m44s
CI / docker (pull_request) Has been skipped
CI / integration_tests (pull_request) Successful in 6m52s
CI / e2e_tests (pull_request) Successful in 6m58s
CI / status-check (pull_request) Failing after 3s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Has been skipped
This commit is contained in:
@@ -10,6 +10,15 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
- **Git Worktree Sandbox Apply** (#4454): The `plan apply` command now merges
|
||||
LLM-generated changes via `git merge` from an isolated worktree branch
|
||||
instead of flat `shutil.copy2`. Displays spec-aligned Apply Summary
|
||||
- **LLM Trace Repository UnitOfWork Compliance** (#7505): Fixed data integrity issue
|
||||
in `LLMTraceRepository.save()` by replacing unconditional `session.commit()` with
|
||||
`session.flush()` and adding optional session injection. The repository now respects
|
||||
the UnitOfWork pattern: when called with an external session, it flushes changes
|
||||
without committing (allowing the caller to control transaction boundaries); when
|
||||
called without a session, it auto-commits for backward compatibility. This ensures
|
||||
traces are not silently lost when used within a UnitOfWork transaction, and allows
|
||||
proper rollback semantics for transactional consistency.
|
||||
|
||||
(plan ID, artifacts, insertions/deletions, project, timestamp), Sandbox
|
||||
Cleanup panel, and `✓ OK Changes applied` footer. Non-git projects fall
|
||||
back to the original flat file copy.
|
||||
|
||||
@@ -269,16 +269,16 @@ Feature: LLM trace observability
|
||||
Given LANGCHAIN_TRACING_V2 is set to "TRUE"
|
||||
Then the langsmith_enabled check should return True
|
||||
|
||||
Scenario: Repository save trace with no tool calls stores null
|
||||
Scenario: Repository save trace with no tool calls stores null
|
||||
Given a SQLAlchemy in-memory repository
|
||||
And a valid LLM trace
|
||||
When I save the trace via the repository
|
||||
Then the raw tool_calls_json in the database should be null
|
||||
|
||||
# --- UnitOfWork transaction boundary (Issue #7505) ----------------------
|
||||
# --- UnitOfWork transaction boundary (Issue #7505) ----------------------
|
||||
|
||||
@tdd_issue_7505
|
||||
Scenario: Repository save uses flush not commit within UnitOfWork
|
||||
@tdd_issue_7505
|
||||
Scenario: Repository save uses flush not commit within UnitOfWork
|
||||
Given a SQLAlchemy in-memory repository
|
||||
And a valid LLM trace
|
||||
When I save the trace via the repository within a UnitOfWork transaction
|
||||
|
||||
@@ -953,6 +953,8 @@ def step_raw_tool_calls_null(context: Context) -> None:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@when("I save the trace via the repository within a UnitOfWork transaction")
|
||||
|
||||
@when("I save the trace via the repository within a UnitOfWork transaction")
|
||||
def step_save_within_uow(context: Context) -> None:
|
||||
"""Save a trace within a UnitOfWork transaction to verify flush behavior."""
|
||||
@@ -960,9 +962,9 @@ def step_save_within_uow(context: Context) -> None:
|
||||
session = context.sqla_factory()
|
||||
context.uow_session = session
|
||||
|
||||
# Save the trace using the SQLAlchemy repository
|
||||
# The repository should use flush(), not commit()
|
||||
context.sqla_repo.save(context.trace)
|
||||
# Save the trace using the SQLAlchemy repository, passing the session
|
||||
# so the repository uses flush() without commit()
|
||||
context.sqla_repo.save(context.trace, session=session)
|
||||
|
||||
# At this point, the trace should be visible within the transaction
|
||||
# but not yet committed to the database
|
||||
|
||||
@@ -46,16 +46,24 @@ class LLMTraceRepository:
|
||||
return self._sf()
|
||||
|
||||
@database_retry
|
||||
def save(self, trace: LLMTrace) -> None:
|
||||
def save(self, trace: LLMTrace, session: Session | None = None) -> None:
|
||||
"""Persist a single ``LLMTrace`` row.
|
||||
|
||||
Args:
|
||||
trace: The trace to persist.
|
||||
session: Optional SQLAlchemy session. If provided, the caller is
|
||||
responsible for commit. If None, a session is obtained from
|
||||
the factory and auto-committed.
|
||||
|
||||
Raises:
|
||||
DatabaseError: On unrecoverable persistence failure.
|
||||
ValueError: If trace is None.
|
||||
"""
|
||||
session = self._session()
|
||||
if trace is None:
|
||||
raise ValueError("trace must not be None")
|
||||
|
||||
own_session = session is None
|
||||
s = session if session is not None else self._session()
|
||||
try:
|
||||
model = LLMTraceModel(
|
||||
trace_id=trace.trace_id,
|
||||
@@ -77,10 +85,13 @@ class LLMTraceRepository:
|
||||
error=trace.error,
|
||||
timestamp=trace.timestamp.isoformat(),
|
||||
)
|
||||
session.add(model)
|
||||
session.flush()
|
||||
s.add(model)
|
||||
s.flush()
|
||||
if own_session:
|
||||
s.commit()
|
||||
except (SQLAlchemyDatabaseError, OperationalError) as exc:
|
||||
session.rollback()
|
||||
if own_session:
|
||||
s.rollback()
|
||||
raise DatabaseError(f"Failed to save LLM trace: {exc}") from exc
|
||||
|
||||
@database_retry
|
||||
|
||||
Reference in New Issue
Block a user