Skip to content

Hardening/sqlite write atomicity - #96

Merged
Lyellr88 merged 3 commits into
MARM-mainfrom
hardening/sqlite-write-atomicity
Jul 17, 2026
Merged

Hardening/sqlite write atomicity#96
Lyellr88 merged 3 commits into
MARM-mainfrom
hardening/sqlite-write-atomicity

Conversation

@Lyellr88

@Lyellr88 Lyellr88 commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Reliability Improvements

    • Hardened SQLite writes to be atomic across session start, log entry creation/switching, notebook add/update/delete, memory merging, documentation cleanup, and deletion flows.
    • Improved concurrency safety for memory updates and duplicate handling, preventing incorrect “merge success” outcomes when writes don’t land.
    • Expanded rollback-on-failure behavior to preserve prior data when commits fail.
  • Tests

    • Added SQLite write-atomicity regression coverage with deterministic failure injection and sequencing/rollback assertions.
    • Extended consolidation coverage for concurrent deletion and correct fallback to creating new memory records.
  • Documentation

    • Updated changelog and clarified compaction/session/documentation endpoint behavior around transactional guarantees and rollback semantics.

claude added 2 commits July 17, 2026 09:57
MARM's pooled connections run in full autocommit mode (isolation_level=
None), so a multi-statement write sequence followed by conn.commit() was
never actually atomic -- each statement committed immediately on its own,
and a failure partway through left earlier statements permanently applied
with nothing to roll back. Wrapped every meaningful multi-statement
mutation path in explicit BEGIN IMMEDIATE / COMMIT / ROLLBACK-on-exception,
matching the pattern already established by _store_memory and
apply_compaction_write:

- services/log_entry.py: log-entry creation, session-switch, dated-
  fallback session, single-entry delete, whole-session delete, notebook
  delete
- endpoints/session.py: marm_start's session activation
- services/notebook.py: notebook add's update-then-insert
- services/documentation.py: legacy system-notebook cleanup + marker
  insert
- core/memory_ops.py: _update_memory, which used to hold the write lock
  open across an await for embedding generation (a real event-loop-stall
  risk under concurrent load) -- restructured to compute the merge and
  its embedding before acquiring the lock, then re-verify under the lock
  that neither content nor metadata changed concurrently before writing,
  and folded the memory-chunks cleanup into the same transaction as the
  content update

Audited and left unchanged: endpoints/compaction.py (every mutation path
is already single-statement, hence already atomic; wrapping the whole
per-candidate loop in one transaction would change intended per-candidate-
independent semantics) and core/memory_db.py (isolation_level untouched
by design; no shared transaction helper added since the inline pattern
stayed consistent and small across every file that needed it).

Work was split into 3 packets to keep the diff reviewable: two spawned
agents each owned a non-overlapping file pair, the third packet (memory_
ops.py + the compaction.py/memory_db.py audit) was implemented directly.
Every packet got an independent review before being accepted:
- Packet A's own review surfaced no code issues; running the full suite
  against it did surface one pre-existing test relying on an
  implementation detail (intercepting .commit() directly) that no longer
  applied once commits went through conn.execute(COMMIT) -- fixed the
  test, not the production code.
- Packet B's review found nothing to fix.
- Packet C (implemented by the coordinator) was reviewed by Agent A, which
  found one real, narrow TOCTOU gap: _update_memory's post-lock re-check
  originally compared only content against the unlocked pre-read, missing
  a concurrent metadata-only write from _restore_sources_from_deleted_
  summary/_update_summary_metadata_after_delete. Fixed by widening the
  re-check to compare metadata too.
- The coordinator's own first draft of _update_memory also had a real bug
  caught by the full test suite (not by review): the re-check compared
  the re-read row against the truncated existing_content instead of the
  original, causing every large merge to silently no-op. Fixed by
  preserving the untruncated original for comparison.

Every new transaction boundary has a mutation-tested regression test in
the new tests/test_sqlite_write_atomicity.py: each test forces a specific
SQL statement to fail and asserts the earlier statement(s) in the same
block did not durably apply. Every test was verified to actually fail
against the pre-hardening code before being accepted -- a happy-path-only
test doesn't prove rollback.

No schema changes, no new endpoints, no MCP tool-surface changes.

Full suite: 636 passed, 20 skipped, 8 slow_stdio passed, zero regressions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011s8tBNnQTmb1q5nRAKwhvv
- endpoints/compaction.py: documented the audit conclusion inline
  (every mutation path is single-statement, batch-wrapping the
  per-candidate loop would regress intended per-candidate-independent
  semantics, not harden them) and added a real regression test proving
  it: one expired candidate in the same request as one valid candidate
  must not block the valid one from staging.
- services/documentation.py: documented why _index_doc's cleanup block
  is intentionally left unwrapped (single statement per branch, already
  atomic, no gap).

Refuted the third finding (log_entry.py's guarded session_summary_cache
update swallowing exceptions): confirmed via git history that this
exact guard predates the write-atomicity branch entirely (already on
origin/MARM-main before this branch started) and is explicitly
documented elsewhere in the same file as deliberate -- cache
dirty-marking is best-effort and must never abort a real log write.
The spec's testing-checklist wording that prompted this finding was
imprecise; the actual rollback-worthy failure (session upsert) is
already covered by test_create_log_entry_rollback_no_partial_row_on_
second_statement_failure.

Full suite: 637 passed, 20 skipped, zero regressions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011s8tBNnQTmb1q5nRAKwhvv
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

SQLite write paths now use explicit BEGIN IMMEDIATE transactions with commit and rollback handling. Memory consolidation avoids holding locks across embedding awaits and atomically updates chunks. Regression tests cover rollback, ordering, cleanup, deletion, session switching, and compaction staging.

Changes

SQLite atomicity hardening

Layer / File(s) Summary
Memory merge consistency
marm-mcp-server/marm_mcp_server/core/memory_ops.py, marm-mcp-server/marm_mcp_server/core/memory.py, marm-mcp-server/tests/test_consolidation_write_time.py, marm-mcp-server/tests/test_sqlite_write_atomicity.py
Memory merges compute embeddings before locking, recheck content and metadata, update memory rows with chunk deletion in one transaction, and report whether the merge wrote.
Mutation transaction boundaries
marm-mcp-server/marm_mcp_server/endpoints/session.py, marm-mcp-server/marm_mcp_server/services/{log_entry,notebook,documentation}.py, marm-mcp-server/tests/test_stdio_transport.py
Session, log-entry, notebook, deletion, and legacy documentation cleanup flows now use explicit begin, commit, and rollback handling.
Atomicity regression coverage
marm-mcp-server/tests/test_sqlite_write_atomicity.py
Failure-injection and SQL-order tests cover rollback across session, log, notebook, documentation, and memory mutation paths.
Compaction validation and release documentation
marm-mcp-server/marm_mcp_server/endpoints/compaction.py, marm-mcp-server/tests/test_sqlite_write_atomicity.py, CHANGELOG.md
Compaction staging constraints and candidate independence are documented and tested, and the changelog records the hardening changes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • Lyellr88/marm-memory#94: Overlaps with the explicit transaction hardening in the centralized log-entry persistence paths.
  • Lyellr88/marm-memory#95: Overlaps with transactional memory mutation and consolidation behavior in core/memory_ops.py.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cc93d39065

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +112 to +118
if (
current is None
or current[0] != original_existing_content
or current[1] != metadata_json
):
conn.execute("ROLLBACK")
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Retry stale merges instead of reporting success

When direct writes run concurrently (for example WRITE_QUEUE_ENABLED=0 or callers using MARMMemory.store_memory directly) and two semantic duplicates target the same existing row, both can pre-read the old content while embedding generation yields; the first merge commits, then the second hits this stale check, rolls back, and returns as if the store succeeded. _store_memory still returns the existing id and increments write accounting, but the second entry's content is never stored. This should retry against the fresh row or signal failure instead of silently dropping the merge.

Useful? React with 👍 / 👎.

@Lyellr88

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@marm-mcp-server/marm_mcp_server/core/memory_ops.py`:
- Around line 112-118: Update the concurrency check in _store_memory so a
changed snapshot does not return silently: on mismatch, reload the latest row,
recompute the merge and embedding inputs, and retry the transaction with a
bounded attempt count. If all retries encounter contention, raise or propagate
an error instead of reporting the existing ID as successful; preserve the
current commit path when the snapshot remains unchanged.

In `@marm-mcp-server/marm_mcp_server/services/documentation.py`:
- Around line 112-119: Update the documentation replacement flow around
store_memory_queued so the new memory is successfully stored before deleting the
existing documentation memory. Once storage succeeds, atomically update
doc_index and remove the old row through the serialized asynchronous write
queue, ensuring failures leave the current memory and index intact and that no
direct memory writes bypass the queue.

In `@marm-mcp-server/marm_mcp_server/services/log_entry.py`:
- Around line 166-202: Route every listed transaction through the existing
serialized asynchronous write queue instead of directly executing BEGIN
IMMEDIATE on the async paths: update log_entry.py lines 166-202, 49-89, 116-132,
324-359, 360-387, and 401-410; session.py lines 25-38; notebook.py lines 31-53;
and documentation.py lines 274-289. Preserve each operation’s current SQL,
ordering, error behavior, and results while ensuring all memory writes are
submitted to the queue.

In `@marm-mcp-server/tests/test_sqlite_write_atomicity.py`:
- Line 95: Add failure-injection rollback tests in the SQLite write atomicity
test suite for session-switch creation, dated fallback creation, targeted log
deletion, and notebook deletion. Exercise each path through the real FastAPI
endpoints backed by real SQLite, verify the injected failure rolls back all
mutations, and cover the corresponding service flows in services/log_entry.py
and the additional referenced branch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: af53f954-44d4-440b-ad88-33d1ffdd95fc

📥 Commits

Reviewing files that changed from the base of the PR and between 7a56a15 and cc93d39.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • marm-mcp-server/marm_mcp_server/core/memory_ops.py
  • marm-mcp-server/marm_mcp_server/endpoints/compaction.py
  • marm-mcp-server/marm_mcp_server/endpoints/session.py
  • marm-mcp-server/marm_mcp_server/services/documentation.py
  • marm-mcp-server/marm_mcp_server/services/log_entry.py
  • marm-mcp-server/marm_mcp_server/services/notebook.py
  • marm-mcp-server/tests/test_sqlite_write_atomicity.py
  • marm-mcp-server/tests/test_stdio_transport.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
marm-mcp-server/marm_mcp_server/endpoints/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

marm-mcp-server/marm_mcp_server/endpoints/**/*.py: Endpoint logic belongs under endpoints/, split by surface; shared helpers belong in core/.
New MCP tools must be implemented in the appropriate endpoints/<surface>.py module.

Files:

  • marm-mcp-server/marm_mcp_server/endpoints/session.py
  • marm-mcp-server/marm_mcp_server/endpoints/compaction.py
marm-mcp-server/marm_mcp_server/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

marm-mcp-server/marm_mcp_server/**/*.py: All memory writes must use the serialized asynchronous write queue; do not bypass it.
A marm_log_entry semantic-store failure must never fail the corresponding log write.
Never share database connections between the memory SQLite database and the concept-graph SQLite database.
Graph and concept failures must not break the seven core memory tools; the graph supervisor starts lazily and operates degraded on failure.
Use one lazy-loaded all-MiniLM-L6-v2 fastembed encoder serialized behind a lock; writes must succeed when embeddings are unavailable.
Prefer the smallest implementation that solves the problem; avoid speculative abstractions and unrequested configuration flags.
Use minimal comments only for non-obvious rationale; do not add comments narrating the next line.
Keep orchestration in its current owner file and extract modules only at genuine boundaries.

Files:

  • marm-mcp-server/marm_mcp_server/endpoints/session.py
  • marm-mcp-server/marm_mcp_server/endpoints/compaction.py
  • marm-mcp-server/marm_mcp_server/services/notebook.py
  • marm-mcp-server/marm_mcp_server/services/documentation.py
  • marm-mcp-server/marm_mcp_server/core/memory_ops.py
  • marm-mcp-server/marm_mcp_server/services/log_entry.py
**/*.py

⚙️ CodeRabbit configuration file

**/*.py: Prioritize runtime correctness, async/concurrency safety, SQLite transaction safety, auth/rate-limit behavior, release-breaking packaging issues, and MCP protocol compatibility.

Files:

  • marm-mcp-server/marm_mcp_server/endpoints/session.py
  • marm-mcp-server/marm_mcp_server/endpoints/compaction.py
  • marm-mcp-server/marm_mcp_server/services/notebook.py
  • marm-mcp-server/marm_mcp_server/services/documentation.py
  • marm-mcp-server/marm_mcp_server/core/memory_ops.py
  • marm-mcp-server/tests/test_stdio_transport.py
  • marm-mcp-server/marm_mcp_server/services/log_entry.py
  • marm-mcp-server/tests/test_sqlite_write_atomicity.py
**/*.md

⚙️ CodeRabbit configuration file

**/*.md: Only flag documentation issues that are materially wrong, misleading for installation/release behavior, or inconsistent with live MCP behavior. Skip style, phrasing, formatting, and wording preferences.

Files:

  • CHANGELOG.md
marm-mcp-server/tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

marm-mcp-server/tests/**/*.py: Run tests with pytest from marm-mcp-server/; tests should use real FastAPI endpoints and real SQLite, mocking only when it closely matches real behavior.
Every new MARM Console API route must have at least one happy-path FastAPI response-contract test with the MCP adapter stubbed.
Do not write existence-check or coded-to-pass tests; prefer deep tests exercising real paths over broad shallow coverage.
Use pytest.mark.skip only for genuinely unavailable dependencies, never merely to avoid implementation effort.

Files:

  • marm-mcp-server/tests/test_stdio_transport.py
  • marm-mcp-server/tests/test_sqlite_write_atomicity.py
**/tests/**

⚙️ CodeRabbit configuration file

**/tests/**: Focus on tests that are flaky, non-isolated, incorrectly asserting behavior, or missing coverage for a changed high-risk path. Skip minor naming, comments, and layout preferences.

Files:

  • marm-mcp-server/tests/test_stdio_transport.py
  • marm-mcp-server/tests/test_sqlite_write_atomicity.py
🪛 ast-grep (0.44.1)
marm-mcp-server/marm_mcp_server/core/memory_ops.py

[info] 124-124: use jsonify instead of json.dumps for JSON output
Context: json.dumps(metadata)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 136-136: use jsonify instead of json.dumps for JSON output
Context: json.dumps(metadata)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🔇 Additional comments (3)
marm-mcp-server/tests/test_stdio_transport.py (1)

325-356: LGTM!

marm-mcp-server/marm_mcp_server/endpoints/compaction.py (1)

1-17: LGTM!

CHANGELOG.md (1)

5-11: LGTM!

Also applies to: 13-15

Comment on lines +112 to +118
if (
current is None
or current[0] != original_existing_content
or current[1] != metadata_json
):
conn.execute("ROLLBACK")
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Retry instead of silently dropping a concurrent merge.

If the snapshot changes during embedding generation, this returns without storing new_content; _store_memory still reports the existing ID as successful. Recompute from the latest row with a bounded retry, and surface an error if contention persists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@marm-mcp-server/marm_mcp_server/core/memory_ops.py` around lines 112 - 118,
Update the concurrency check in _store_memory so a changed snapshot does not
return silently: on mismatch, reload the latest row, recompute the merge and
embedding inputs, and retry the transaction with a bounded attempt count. If all
retries encounter contention, raise or propagate an error instead of reporting
the existing ID as successful; preserve the current commit path when the
snapshot remains unchanged.

Comment on lines +112 to +119
# Audited under the SQLite write-atomicity hardening effort
# (docs/current/sqlite-write-atomicity-hardening.md): no BEGIN
# IMMEDIATE needed here. Exactly one of the two branches below
# runs per call, and each is a single statement -- a lone
# statement is already atomic under SQLite regardless of
# isolation_level, so there's no multi-statement sequence to
# protect. store_memory_queued below intentionally stays outside
# any transaction (it awaits and does its own internal locking).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not delete the current documentation memory before its replacement exists.

The old memory is committed as deleted before store_memory_queued runs. If storage fails, documentation disappears and doc_index remains stale until a later retry. Store first, then atomically swap the index and remove the old row through the write queue.

As per coding guidelines, all memory writes must use the serialized asynchronous write queue; do not bypass it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@marm-mcp-server/marm_mcp_server/services/documentation.py` around lines 112 -
119, Update the documentation replacement flow around store_memory_queued so the
new memory is successfully stored before deleting the existing documentation
memory. Once storage succeeds, atomically update doc_index and remove the old
row through the serialized asynchronous write queue, ensuring failures leave the
current memory and index intact and that no direct memory writes bypass the
queue.

Source: Coding guidelines

Comment thread marm-mcp-server/marm_mcp_server/services/log_entry.py
Comment thread marm-mcp-server/tests/test_sqlite_write_atomicity.py
PR #96 review findings (CodeRabbit + Codex):

- core/memory_ops.py: _update_memory now returns bool indicating whether
  it actually wrote. _store_memory previously reported a stale semantic
  merge as successful even when _update_memory's lock re-check bailed on
  a concurrent change -- silently dropping the caller's content. Falls
  through to storing as a new memory instead.
- tests/test_sqlite_write_atomicity.py: added mutation-tested rollback
  coverage for the session-switch, dated-fallback, targeted log-delete,
  and notebook-delete transaction boundaries (previously untested despite
  the CHANGELOG's coverage claim).
- tests/test_consolidation_write_time.py: regression test for the
  dropped-write fix; tightened the missing-id test to assert the new
  bool contract.

Deferred (documented, not fixed): documentation.py's delete-before-store
ordering in doc re-indexing -- the suggested reorder would expose the
new content to the semantic-duplicate scan when CONSOLIDATION_ENABLED=1,
causing merge-append corruption instead of clean replacement. Self-heals
today via the existing stale-doc_index recovery path.

Refuted: routing log/session/notebook SQL through WriteQueue -- contradicts
the hardening spec's explicit rejection of that approach; WriteQueue
serializes writes, it doesn't make a SQL sequence atomic.
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Lyellr88

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@marm-mcp-server/tests/test_sqlite_write_atomicity.py`:
- Around line 285-316: Update
test_dated_fallback_rollback_keeps_previous_active_session so
memory.active_log_session remains "main" when /marm_log_entry is called,
triggering the dated-fallback insert. Remove or avoid the preceding /marm_start
setup and adjust assertions/setup as needed while preserving verification that
the prior active session remains active and no dated fallback row is created.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3e8859bc-e743-452d-b765-9e75cacce9d2

📥 Commits

Reviewing files that changed from the base of the PR and between cc93d39 and ee9b115.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • marm-mcp-server/marm_mcp_server/core/memory.py
  • marm-mcp-server/marm_mcp_server/core/memory_ops.py
  • marm-mcp-server/tests/test_consolidation_write_time.py
  • marm-mcp-server/tests/test_sqlite_write_atomicity.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • CHANGELOG.md
  • marm-mcp-server/marm_mcp_server/core/memory_ops.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
marm-mcp-server/marm_mcp_server/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

marm-mcp-server/marm_mcp_server/**/*.py: All memory writes must use the serialized asynchronous write queue; do not bypass it.
A marm_log_entry semantic-store failure must never fail the corresponding log write.
Never share database connections between the memory SQLite database and the concept-graph SQLite database.
Graph and concept failures must not break the seven core memory tools; the graph supervisor starts lazily and operates degraded on failure.
Use one lazy-loaded all-MiniLM-L6-v2 fastembed encoder serialized behind a lock; writes must succeed when embeddings are unavailable.
Prefer the smallest implementation that solves the problem; avoid speculative abstractions and unrequested configuration flags.
Use minimal comments only for non-obvious rationale; do not add comments narrating the next line.
Keep orchestration in its current owner file and extract modules only at genuine boundaries.

Files:

  • marm-mcp-server/marm_mcp_server/core/memory.py
**/*.py

⚙️ CodeRabbit configuration file

**/*.py: Prioritize runtime correctness, async/concurrency safety, SQLite transaction safety, auth/rate-limit behavior, release-breaking packaging issues, and MCP protocol compatibility.

Files:

  • marm-mcp-server/marm_mcp_server/core/memory.py
  • marm-mcp-server/tests/test_consolidation_write_time.py
  • marm-mcp-server/tests/test_sqlite_write_atomicity.py
marm-mcp-server/tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

marm-mcp-server/tests/**/*.py: Run tests with pytest from marm-mcp-server/; tests should use real FastAPI endpoints and real SQLite, mocking only when it closely matches real behavior.
Every new MARM Console API route must have at least one happy-path FastAPI response-contract test with the MCP adapter stubbed.
Do not write existence-check or coded-to-pass tests; prefer deep tests exercising real paths over broad shallow coverage.
Use pytest.mark.skip only for genuinely unavailable dependencies, never merely to avoid implementation effort.

Files:

  • marm-mcp-server/tests/test_consolidation_write_time.py
  • marm-mcp-server/tests/test_sqlite_write_atomicity.py
**/tests/**

⚙️ CodeRabbit configuration file

**/tests/**: Focus on tests that are flaky, non-isolated, incorrectly asserting behavior, or missing coverage for a changed high-risk path. Skip minor naming, comments, and layout preferences.

Files:

  • marm-mcp-server/tests/test_consolidation_write_time.py
  • marm-mcp-server/tests/test_sqlite_write_atomicity.py
🔇 Additional comments (4)
marm-mcp-server/marm_mcp_server/core/memory.py (1)

247-248: LGTM!

marm-mcp-server/tests/test_consolidation_write_time.py (2)

57-60: LGTM!


259-304: LGTM!

marm-mcp-server/tests/test_sqlite_write_atomicity.py (1)

256-279: 🎯 Functional Correctness

This assertion matches the Session: path. Both Session: and Topic: append -{today} to the new session name, so the test’s new_session check is correct.

			> Likely an incorrect or invalid review comment.

Comment on lines +285 to +316
def test_dated_fallback_rollback_keeps_previous_active_session(monkeypatch, tmp_path):
"""create_log_entry's dated-fallback block (no session_name given and
memory.active_log_session is still "main"): UPDATE sessions SET
marm_active = FALSE, then an INSERT for the new dated session. Force
that insert to fail -- the previously active session's marm_active
flag must not have been cleared, and the dated fallback session row
must not exist."""
server = load_isolated_server(monkeypatch, tmp_path)
client = local_client(server.app)

first = client.post("/marm_start", json={"session_name": "atomicity-a5-old"})
assert first.status_code == 200

db_path = tmp_path / "marm_memory.db"
with sqlite3.connect(db_path) as conn:
active_before = conn.execute(
"SELECT marm_active FROM sessions WHERE session_name = ?",
("atomicity-a5-old",),
).fetchone()[0]
assert active_before in (1, True)

import marm_mcp_server.services.log_entry as log_entry

# First occurrence of "INSERT INTO sessions" in this call path is the
# dated-fallback block's own insert (the later per-entry upsert further
# down in create_log_entry is a second, distinct occurrence that never
# gets reached once this one raises).
_fail_on(monkeypatch, log_entry.memory, "INSERT INTO sessions")

resp = client.post(
"/marm_log_entry", json={"entry": "plain entry with no session prefix"}
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify the fallback block logic in create_log_entry to confirm the active session condition.
rg -A 10 -B 2 'active_log_session' marm-mcp-server/marm_mcp_server/services/log_entry.py

Repository: Lyellr88/marm-memory

Length of output: 2714


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the test and its helpers around the cited lines.
ast-grep outline marm-mcp-server/tests/test_sqlite_write_atomicity.py --view expanded || true
printf '\n--- relevant slice ---\n'
sed -n '250,340p' marm-mcp-server/tests/test_sqlite_write_atomicity.py

printf '\n--- helper search ---\n'
rg -n "def load_isolated_server|def local_client|/marm_start|active_log_session" marm-mcp-server/tests marm-mcp-server/marm_mcp_server -g '!**/__pycache__/**'

Repository: Lyellr88/marm-memory

Length of output: 9687


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the dated-fallback implementation directly for the session-selection condition.
sed -n '120,220p' marm-mcp-server/marm_mcp_server/services/log_entry.py

Repository: Lyellr88/marm-memory

Length of output: 4306


Trigger the dated-fallback branch in this test. /marm_start switches memory.active_log_session off "main", so the later /marm_log_entry call reuses atomicity-a5-old instead of hitting the dated fallback insert. Start this case with "main" (or skip the prior start call) so the rollback path is actually covered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@marm-mcp-server/tests/test_sqlite_write_atomicity.py` around lines 285 - 316,
Update test_dated_fallback_rollback_keeps_previous_active_session so
memory.active_log_session remains "main" when /marm_log_entry is called,
triggering the dated-fallback insert. Remove or avoid the preceding /marm_start
setup and adjust assertions/setup as needed while preserving verification that
the prior active session remains active and no dated fallback row is created.

Source: Coding guidelines

@Lyellr88
Lyellr88 merged commit 1cc7df0 into MARM-main Jul 17, 2026
7 checks passed
@Lyellr88
Lyellr88 deleted the hardening/sqlite-write-atomicity branch July 18, 2026 04:06
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