Skip to content

Conversation

@avelino
Copy link
Member

@avelino avelino commented Oct 31, 2025

This PR introduces a transaction core module and structured Git note support for every write operation in ChronDB, fulfilling architectural requirements for chronological traceability and external correlation/audit.

Highlights:

  • Adds chrondb.transaction.core namespace. Provides a shared transaction context, unique tx_id generation, and helpers for capturing metadata (origin, user, flags) across all entrypoints (REST, SQL, Redis).
  • Implements a new chrondb.storage.git.notes module. Every commit now stores a JSON note under the chrondb namespace, containing: tx_id, origin (protocol/source), optional user, semantic flags (bulk-load, migration, rollback, etc.), and client/request metadata (e.g. X-Request-Id).
  • Refactors all transaction/write paths in the Git-backed storage engine to invoke these helpers. Enforces that missing notes are treated as a regression (test coverage included).
  • Documents new transaction block (with-transaction) usage, and how to pass metadata via HTTP headers or protocol attributes to propagate context into Git notes. See docs/api.md, "Transaction Operations" and "Transaction Metadata and Git Notes".
  • Adds and updates unit/integration tests for: correct note attachment, lookup with git notes, failure modes (e.g., repository contention, branch mutation), and regression coverage (notes always present after a commit).
  • Updates AGENT.md: All write paths must call transaction helpers to guarantee Git note context. No destructive mutations; always append-only.
  • No backwards-incompatible API changes. Protocols remain consistent across REST, SQL (Postgres), and Redis wire formats.

Rationale:
ChronDB's Git-based architecture enables full auditability and time-travel. Storing rich transaction metadata as Git notes ensures every operation is contextualized—supporting debugging, lineage analysis, external audit, and advanced correlation across distributed systems. This change centralizes transaction and commit metadata handling, eliminating per-protocol ad-hoc logic and reducing the risk of silent context loss.

See also:

  • src/chrondb/storage/git/notes.clj (implementation)
  • src/chrondb/transaction/core.clj (transaction core logic)
  • New/updated tests in test/chrondb/storage/git/notes_test.clj, test/chrondb/transaction/core_test.clj
  • Expanded documentation in docs/api.md and process requirements in AGENT.md.

BREAKING: None, but all future write code must route through the transaction context to guarantee note coverage and protocol metadata flow.

fixed #43
fixed #44

Summary by CodeRabbit

  • New Features

    • Transaction metadata (tx_id, origin, user, flags, request metadata) is recorded and attached to commits as Git notes; many write paths now run inside transactions and initial repo initialization includes a note. CLI now sends an origin header.
  • Documentation

    • Added docs and examples for transaction metadata, Git notes, header usage, and how to inspect notes.
  • Bug Fixes

    • Restore flows now record rollback metadata and preserve source-commit info.
  • Tests

    • Added tests validating Git note writing/merging and transactional behaviors across storage, SQL, and Redis paths.

This PR introduces a transaction core module and structured Git note support for every write operation in ChronDB, fulfilling architectural requirements for chronological traceability and external correlation/audit.

Highlights:
- Adds `chrondb.transaction.core` namespace. Provides a shared transaction context, unique `tx_id` generation, and helpers for capturing metadata (origin, user, flags) across all entrypoints (REST, SQL, Redis).
- Implements a new `chrondb.storage.git.notes` module. Every commit now stores a JSON note under the `chrondb` namespace, containing: `tx_id`, origin (protocol/source), optional `user`, semantic flags (`bulk-load`, `migration`, `rollback`, etc.), and client/request metadata (e.g. `X-Request-Id`).
- Refactors all transaction/write paths in the Git-backed storage engine to invoke these helpers. Enforces that missing notes are treated as a regression (test coverage included).
- Documents new transaction block (`with-transaction`) usage, and how to pass metadata via HTTP headers or protocol attributes to propagate context into Git notes. See `docs/api.md`, "Transaction Operations" and "Transaction Metadata and Git Notes".
- Adds and updates unit/integration tests for: correct note attachment, lookup with `git notes`, failure modes (e.g., repository contention, branch mutation), and regression coverage (notes always present after a commit).
- Updates AGENT.md: All write paths _must_ call transaction helpers to guarantee Git note context. No destructive mutations; always append-only.
- No backwards-incompatible API changes. Protocols remain consistent across REST, SQL (Postgres), and Redis wire formats.

Rationale:
ChronDB's Git-based architecture enables full auditability and time-travel. Storing rich transaction metadata as Git notes ensures every operation is contextualized—supporting debugging, lineage analysis, external audit, and advanced correlation across distributed systems. This change centralizes transaction and commit metadata handling, eliminating per-protocol ad-hoc logic and reducing the risk of silent context loss.

See also:
- `src/chrondb/storage/git/notes.clj` (implementation)
- `src/chrondb/transaction/core.clj` (transaction core logic)
- New/updated tests in `test/chrondb/storage/git/notes_test.clj`, `test/chrondb/transaction/core_test.clj`
- Expanded documentation in `docs/api.md` and process requirements in AGENT.md.

BREAKING: None, but all future write code must route through the transaction context to guarantee note coverage and protocol metadata flow.

fixed #43
fixed #44

Signed-off-by: Avelino <[email protected]>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 31, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds an end-to-end transaction context system that attaches structured Git notes to commits for all write paths (REST, CLI, Redis, SQL), including note read/merge utilities, commit integration, request-context propagation across routes/handlers, and tests validating note payloads and lifecycle.

Changes

Cohort / File(s) Summary
Documentation
AGENT.md, docs/api.md, docs/architecture.md, docs/examples-clojure.md
Add guidance, examples, and header/inspection commands describing transaction metadata, Git notes format, semantic flags, and example usage.
Transaction Core
src/chrondb/transaction/core.clj
New transactional context namespace: dynamic *transaction-context*, normalization/merge helpers, context->note/context-for-commit, mutation helpers, with-transaction* and with-transaction macro, and lifecycle callbacks (commit-success!/rollback!).
Git Notes Utilities
src/chrondb/storage/git/notes.clj
New notes helpers: default-notes-ref, ->object-id, read-note, add-git-note, merge-note with JSON (de)serialization and flags merging/deduplication.
Git Storage Integration
src/chrondb/storage/git/commit.clj, src/chrondb/storage/git/core.clj, src/chrondb/storage/git/document.clj, src/chrondb/storage/git/history.clj
Commit flows accept optional note payloads; attach notes on init/save/delete/restore; rollback operations mark flags/metadata; commit-virtual gains optional arity to accept :note.
Redis API
src/chrondb/api/redis/core.clj
Add execute-redis-write and helpers to build tx metadata and normalize flags; wrap Redis write commands inside transactional blocks passing enriched metadata.
SQL API
src/chrondb/api/sql/execution/query.clj
Add sql-normalize-flags, sql-tx-options; refactor insert/update/delete/select flows to execute within transactions and include metadata/flags.
REST API / Routes
src/chrondb/api/v1.clj, src/chrondb/api/v1/routes.clj
Centralize request-context building (headers, branch, flags), add build-transaction-options / request-header, and update handlers/routes to pass context and run writes in transactions.
CLI HTTP Client
src/chrondb/cli/http.clj
Add X-ChronDB-Origin: cli header to CLI HTTP requests.
Tests
test/chrondb/storage/git/document_test.clj, test/chrondb/storage/git/history_test.clj, test/chrondb/storage/git/notes_test.clj, test/chrondb/transaction/core_test.clj, test/chrondb/api/sql/execution/transaction_test.clj, test/chrondb/storage/git/commit_test.clj
New/updated tests verifying git-note read/write/merge behavior, transaction lifecycle (success/rollback), commit failure semantics, and propagation of tx metadata across SQL/Redis/API flows.
CI workflow
.github/workflows/publish-docker.yml
Reformat YAML and add conditionalization so certain steps run only on push to main; add pull_request trigger.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Client
    participant Router as Routes
    participant API as APIv1
    participant TxCore as TxCore
    participant Storage as GitStorage
    participant Notes as GitNotes

    Client->>Router: Send request (headers + body)
    Router->>Router: build-request-context()
    Router->>API: handle-*(context)
    API->>TxCore: with-transaction(tx-opts)
    activate TxCore
    TxCore->>TxCore: bind/extend *transaction-context*
    TxCore->>Storage: perform write (commit-virtual / save / delete / restore)
    activate Storage
    Storage->>Storage: create commit (git)
    Storage-->>TxCore: return commit-id
    Storage->>Notes: add-git-note(commit-id, tx-note)    %% attach structured note after commit
    Notes-->>Storage: merged-note
    deactivate Storage
    TxCore->>TxCore: run on-complete callbacks (success / rollback)
    deactivate TxCore
    Note right of Notes: "notes visible via git log --show-notes"
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

  • Areas needing extra attention:
    • src/chrondb/transaction/core.clj — concurrency, nested context semantics, and on-complete callback error handling.
    • src/chrondb/storage/git/notes.clj and src/chrondb/storage/git/commit.clj — object-id handling, JSON marshal/unmarshal, merge semantics, and ensuring note failures don't advance refs.
    • API integrations (redis, sql, v1 routes) — consistent metadata construction, flag normalization, and preserving response shapes.

Possibly related PRs

Suggested labels

enhancement, :documentation

Suggested reviewers

  • matheusfrancisco

Poem

🐇
I hopped through commits and left a note,
A tiny tx_id tucked in JSON throat,
From REST to Redis, SQL and CLI,
Flags flutter like leaves as commits go by,
Hop—metadata stitched, neat trail in tow.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning Most changes align directly with the transaction notes feature objectives. However, the change to .github/workflows/publish-docker.yml introduces workflow restructuring and conditional logic (adding pull_request trigger, conditionalizing the login and push steps for main branch only) that is unrelated to the transaction notes or operation flags requirements specified in issues #43 and #44. While this workflow improvement may be beneficial for CI/CD processes, it falls outside the scope of the transaction context and git-notes feature implementation. Consider moving the workflow changes to a separate pull request focused on CI/CD improvements to keep this PR's scope focused on transaction notes and metadata support. If the workflow changes are necessary as supporting infrastructure for this PR, document that rationale or combine them with a dedicated workflow improvement PR.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "feat: Git-backed transaction notes and core helpers" directly and accurately captures the main objectives of the PR. The title reflects the addition of Git-backed transaction notes via the new chrondb.storage.git.notes namespace and the core transaction helpers introduced in chrondb.transaction.core. The title is concise, specific, and clearly communicates the primary change without vagueness or generic phrasing.
Linked Issues Check ✅ Passed The PR comprehensively implements all acceptance criteria from both linked issues. For issue #43, the PR adds the core git-notes namespace with add-git-note and read-note utilities, implements transaction context generation with tx_id and structured metadata, and integrates notes across all interfaces (REST via src/chrondb/api/v1.clj, SQL via src/chrondb/api/sql/execution/query.clj, Redis via src/chrondb/api/redis/core.clj, and CLI via the X-ChronDB-Origin header). For issue #44, the PR supports semantic flags via chrondb.transaction.core with flag normalization and merging logic, enabling special operation detection for bulk-load, rollback, and migration scenarios. Documentation updates in docs/api.md and AGENT.md explain the feature, and comprehensive tests in test/chrondb/storage/git/notes_test.clj and test/chrondb/transaction/core_test.clj verify the implementation.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 924f987 and 690bf78.

📒 Files selected for processing (3)
  • src/chrondb/api/v1.clj (4 hunks)
  • src/chrondb/storage/git/history.clj (3 hunks)
  • src/chrondb/transaction/core.clj (1 hunks)

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

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

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.

ℹ️ 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".

- Update `.github/workflows/publish-docker.yml` to also trigger on pull_request events, not just pushes to main.
- Enables CI/CD Docker build checks for PRs, improving contributor feedback and pre-merge validation.

Signed-off-by: Avelino <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/chrondb/storage/git/history.clj (1)

135-213: Fix unused locals that are breaking lint.

data-dir and id-only are no longer used after the refactor, and clj-kondo flags both (see pipeline warning). Please drop the binding and ignore the second return value so the lint job goes green again.

-        ; Get data directory from config
-        data-dir (get-in config-map [:storage :data-dir])
-        ; Split logic - extract table if ID has table prefix (e.g., "user:1")
-        [table-hint id-only] (path/extract-table-and-id id)
+        ; Split logic - extract table if ID has table prefix (e.g., "user:1")
+        [table-hint _] (path/extract-table-and-id id)
🧹 Nitpick comments (3)
src/chrondb/transaction/core.clj (1)

45-45: Silence the new clj-kondo warnings.

Lint is flagging opts, overrides, and storage as unused. Dropping the unused destructuring and renaming the ignored argument keeps CI clean without affecting behavior.

-(defn- base-context
-  [{:keys [tx-id origin user timestamp flags metadata status] :as opts}]
+(defn- base-context
+  [{:keys [tx-id origin user timestamp flags metadata status]}]
@@
-  ([context {:keys [commit-id commit-message branch path document-id operation flags metadata timestamp]
-             :as overrides}]
+  ([context {:keys [commit-id commit-message branch path document-id operation flags metadata timestamp]}]
@@
-  [storage {:keys [on-complete] :as opts} f]
+  [_storage {:keys [on-complete] :as opts} f]

Also applies to: 127-127, 168-168

docs/architecture.md (1)

59-62: Add language tag to Git CLI example.
markdownlint (MD040) flags the new fenced block because it lacks a language. Tagging it as bash keeps tooling green and matches the rest of the docs.

-```
+```bash
 git log --show-notes=chrondb
 git notes --ref=chrondb show <commit>

</blockquote></details>
<details>
<summary>docs/api.md (1)</summary><blockquote>

`195-198`: **Add language tag to Git commands block.**  
The new fenced example lacks a language hint, tripping markdownlint (MD040). Mark it as `bash` so tooling stays happy.

```diff
-```
+```bash
 git log --show-notes=chrondb
 git notes --ref=chrondb show <commit-hash>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used**: CodeRabbit UI

**Review profile**: CHILL

**Plan**: Pro

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9c46bdccb6fb7ea6fc30d16be03f86a2103aec1f and 706eda39f594a3359e23b5f610587b641b01b944.

</details>

<details>
<summary>📒 Files selected for processing (19)</summary>

* `AGENT.md` (1 hunks)
* `docs/api.md` (2 hunks)
* `docs/architecture.md` (1 hunks)
* `docs/examples-clojure.md` (1 hunks)
* `src/chrondb/api/redis/core.clj` (15 hunks)
* `src/chrondb/api/sql/execution/query.clj` (4 hunks)
* `src/chrondb/api/v1.clj` (4 hunks)
* `src/chrondb/api/v1/routes.clj` (3 hunks)
* `src/chrondb/cli/http.clj` (1 hunks)
* `src/chrondb/storage/git/commit.clj` (2 hunks)
* `src/chrondb/storage/git/core.clj` (1 hunks)
* `src/chrondb/storage/git/document.clj` (2 hunks)
* `src/chrondb/storage/git/history.clj` (3 hunks)
* `src/chrondb/storage/git/notes.clj` (1 hunks)
* `src/chrondb/transaction/core.clj` (1 hunks)
* `test/chrondb/storage/git/document_test.clj` (3 hunks)
* `test/chrondb/storage/git/history_test.clj` (2 hunks)
* `test/chrondb/storage/git/notes_test.clj` (1 hunks)
* `test/chrondb/transaction/core_test.clj` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>📚 Learning: 2025-10-12T09:40:13.442Z</summary>

Learnt from: avelino
Repo: moclojer/chrondb PR: 59
File: src/chrondb/core.clj:158-171
Timestamp: 2025-10-12T09:40:13.442Z
Learning: ChronDB's GitStorage only implements the internal protocol chrondb.storage.protocol/Storage and is not a Closeable. There is no simple way to use with-open or try-with-resources patterns without changing the storage's public API. The storage remains open and in use by the server throughout the process lifetime.


**Applied to files:**
- `src/chrondb/api/v1.clj`
- `test/chrondb/storage/git/history_test.clj`
- `test/chrondb/storage/git/notes_test.clj`
- `docs/api.md`
- `src/chrondb/transaction/core.clj`
- `src/chrondb/storage/git/commit.clj`

</details>

</details><details>
<summary>🪛 GitHub Actions: Lint project with clj-kondo</summary>

<details>
<summary>src/chrondb/api/v1/routes.clj</summary>

[warning] 132-132: clj-kondo: Redundant nested call: merge.

</details>
<details>
<summary>src/chrondb/api/v1.clj</summary>

[warning] 81-81: clj-kondo: unused binding ctx.

---

[warning] 106-106: clj-kondo: unused binding ctx.

---

[warning] 178-178: clj-kondo: unused binding ctx.

---

[warning] 215-215: clj-kondo: unused binding ctx.

</details>
<details>
<summary>src/chrondb/transaction/core.clj</summary>

[warning] 46-46: clj-kondo: unused binding opts.

---

[warning] 128-128: clj-kondo: unused binding overrides.

---

[warning] 168-168: clj-kondo: unused binding storage.

</details>
<details>
<summary>src/chrondb/storage/git/history.clj</summary>

[warning] 207-207: clj-kondo: unused binding data-dir.

---

[warning] 210-210: clj-kondo: unused binding id-only.

</details>

</details>
<details>
<summary>🪛 markdownlint-cli2 (0.18.1)</summary>

<details>
<summary>docs/architecture.md</summary>

59-59: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>
<details>
<summary>docs/api.md</summary>

195-195: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)</summary>

* GitHub Check: test
* GitHub Check: Run SQL Protocol Benchmark
* GitHub Check: test

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines +57 to +62
(defn execute-redis-write
([storage command args f]
(execute-redis-write storage command args {} f))
([storage command args opts f]
(tx/with-transaction [storage (redis-tx-options command args opts)]
(f))))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Redis transaction callback never sees the transactional storage

tx/with-transaction rebinding is purely lexical. Because we pass a zero-arity closure, it closes over the pre-transaction storage. Every (storage/...) call inside Redis handlers is still hitting the original handle, so the transaction context (including git notes) never runs. Please thread the transaction-scoped storage into the callback (and update the call sites) so writes execute under the new binding.

 (defn execute-redis-write
   ([storage command args f]
    (execute-redis-write storage command args {} f))
   ([storage command args opts f]
-   (tx/with-transaction [storage (redis-tx-options command args opts)]
-     (f))))
+   (tx/with-transaction [tx-storage (redis-tx-options command args opts)]
+     (f tx-storage))))

After this change, each caller should accept the transaction storage (e.g. (fn [tx-storage] (storage/save-document tx-storage doc) ...)).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/chrondb/api/redis/core.clj around lines 57 to 62, the transaction
callback is a zero-arity closure that closes over the pre-transaction storage so
transactional operations never see the rebinding; change execute-redis-write to
invoke the callback with the transaction-scoped storage (inside
tx/with-transaction call pass the rebounded storage into f), and update all
callers to accept and use that tx-storage (e.g. change (fn [] ...) to (fn
[tx-storage] (storage/save-document tx-storage ...))).

Comment on lines +316 to 320
(let [context (build-request-context request {:metadata {:endpoint "/api/v1/restore"}})
params* (keywordize-params params)
backup-file (:tempfile params)
result (handlers/handle-restore storage backup-file (merge params* context))]
(-> (response/response (:body result))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Restore endpoint never gets the uploaded file

Ring’s :params map keeps string keys, so (:tempfile params) is always nil. The handler therefore receives backup-file nil and responds with “file upload is required” even when a file was posted. Pull the upload from the keywordized params (and plumb the filename) before invoking handle-restore.

-    (let [context (build-request-context request {:metadata {:endpoint "/api/v1/restore"}})
-          params* (keywordize-params params)
-          backup-file (:tempfile params)
-          result (handlers/handle-restore storage backup-file (merge params* context))]
+    (let [context (build-request-context request {:metadata {:endpoint "/api/v1/restore"}})
+          params* (keywordize-params params)
+          upload  (:tempfile params*)
+          backup-file (:tempfile upload)
+          filename (:filename upload)
+          result (handlers/handle-restore storage backup-file
+                                          (cond-> (merge params* context)
+                                            filename (assoc :filename filename)))]

With this change the uploaded archive reaches handle-restore, and transaction metadata can record the filename.

🤖 Prompt for AI Agents
In src/chrondb/api/v1/routes.clj around lines 316 to 320, the code reads the
uploaded file from the non-keywordized params so backup-file is nil; change it
to pull the upload from the already keywordized params* and extract both its
:tempfile and :filename before calling handle-restore (for example, let [upload
(:file params*) backup-file (:tempfile upload) filename (:filename upload)] and
then call (handlers/handle-restore storage backup-file (merge params* context
{:filename filename})) so the handler receives the actual tempfile and the
original filename).

Comment on lines +45 to 48
request-headers (cond-> {"Accept" "application/json"
"X-ChronDB-Origin" "cli"}
(:token cfg) (assoc "Authorization" (str "Bearer " (:token cfg)))
headers (merge headers))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Ensure the CLI origin header remains trustworthy.

Because the user-provided headers map is merged after the defaults, anyone calling this helper can override "X-ChronDB-Origin" and spoof a different origin. That defeats the point of emitting a stable origin for transaction metadata and lets clients mis-tag commits. Please set the origin header after merging user headers so the CLI always identifies itself as "cli".

-        request-headers (cond-> {"Accept" "application/json"
-                                 "X-ChronDB-Origin" "cli"}
-                          (:token cfg) (assoc "Authorization" (str "Bearer " (:token cfg)))
-                          headers (merge headers))
+        request-headers (-> {"Accept" "application/json"}
+                            (cond-> (:token cfg)
+                              (assoc "Authorization" (str "Bearer " (:token cfg))))
+                            (merge headers)
+                            (assoc "X-ChronDB-Origin" "cli"))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
request-headers (cond-> {"Accept" "application/json"
"X-ChronDB-Origin" "cli"}
(:token cfg) (assoc "Authorization" (str "Bearer " (:token cfg)))
headers (merge headers))
request-headers (-> {"Accept" "application/json"}
(cond-> (:token cfg)
(assoc "Authorization" (str "Bearer " (:token cfg))))
(merge headers)
(assoc "X-ChronDB-Origin" "cli"))
🤖 Prompt for AI Agents
In src/chrondb/cli/http.clj around lines 45 to 48, the user-supplied headers are
currently merged after the defaults which allows callers to override
"X-ChronDB-Origin"; change the build order so user headers are merged into the
defaults first and then explicitly set "X-ChronDB-Origin" to "cli" (and finally
assoc the Authorization header when :token is present) so callers cannot spoof
the CLI origin.

Comment on lines +201 to 204
{:note (cond-> {:document-id (:id document)
:operation "save-document"}
table-name (assoc :metadata {:table table-name}))})

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Document note keys should use snake_case.
The options map hands commit-virtual a note with :document-id, which serializes to document-id. Consumers (and the new tests) look for document_id, so the field comes back nil. Switch both save/delete paths to :document_id to align with the documented schema.

-                           {:note (cond-> {:document-id (:id document)
+                           {:note (cond-> {:document_id (:id document)
                                            :operation "save-document"}
                                     table-name (assoc :metadata {:table table-name}))})
...
-                                         {:note (cond-> {:document-id id
+                                         {:note (cond-> {:document_id id
                                                         :operation "delete-document"
                                                         :flags ["delete"]}
                                                  table-name (assoc :metadata {:table table-name}))})

Also applies to: 340-343

🤖 Prompt for AI Agents
In src/chrondb/storage/git/document.clj around lines 201-204 (and also adjust
the identical case at lines 340-343), the note map keys use :document-id which
serializes to document-id but consumers expect snake_case document_id; change
both save and delete note constructions to use :document_id instead of
:document-id, and preserve the existing :operation and optional :metadata/:table
behavior so the serialized note uses document_id and aligns with the documented
schema.

…lways record commit notes

- Add regression test coverage to assert that every mutation (insert/update/delete) via SQL records a commit context note with unique `tx_id` and correct `origin`
- Validate commit note metadata for inserts (document-count), updates (updated doc IDs), and deletes (delete flags, target doc ID)
- Cover concurrent insert scenarios for unique transaction IDs and context isolation
- Ensure every path leverages the shared `tx/context-for-commit` helper for structured note payloads
- Prevents silent note omissions and guards chronological auditability invariants

Signed-off-by: Avelino <[email protected]>
- Refactor `commit-virtual` so all Git write operations add a structured transaction note (with `commit-id`, `commit-message`, and branch, plus propagated metadata if available) via `notes/add-git-note`.
- New note payload is built by merging intrinsic commit info, optional path, and the active transaction metadata using `tx/context-for-commit`.
- Any failure to attach a git note now aborts the commit and logs an error, ensuring no commits are created without contextual notes.
- Unit/integration test coverage added (`test-commit-virtual-note-failure-does-not-advance-ref`) to assert that commit refs are rolled back if note attachment fails, preventing orphaned commits without notes.

See: #62 (comment)

Signed-off-by: Avelino <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
test/chrondb/storage/git/notes_test.clj (3)

55-59: Clarify assertion targets for consistency.

The test checks tx_id on the merged return value (line 57) but verifies origin and flags on the separately-read note (lines 58-59). While this may intentionally validate both the return value and the persisted note, the inconsistency can be confusing.

Consider checking all fields on the same object for clarity, or add a comment explaining why both objects are being validated:

-        (let [merged (notes/add-git-note git commit-id payload)
-              note (notes/read-note git commit-id)]
-          (is (= "tx-123" (:tx_id merged)))
-          (is (= "rest" (:origin note)))
-          (is (= ["bulk-load"] (:flags note))))
+        (let [merged (notes/add-git-note git commit-id payload)
+              note (notes/read-note git commit-id)]
+          ;; Verify both return value and persisted note
+          (is (= "tx-123" (:tx_id merged)))
+          (is (= "rest" (:origin merged)))
+          (is (= ["bulk-load"] (:flags merged)))
+          (is (= note merged)))

38-61: Consider adding edge case test coverage.

The current test validates the happy path successfully. Consider adding tests for edge cases such as:

  • Reading a note that doesn't exist
  • Invalid or nil commit-id
  • Malformed note payload
  • Large note payloads

63-86: LGTM with a suggestion for enhanced coverage.

The test correctly validates flag deduplication and metadata merging. The use of set comparison for flags (lines 82-83) appropriately handles the unordered nature of the collection. The cleanup pattern with protocol/close is correct. Based on learnings.

Consider adding a test case where both note additions include metadata to verify complete metadata merging behavior:

(deftest merge-metadata
  (testing "merges metadata from multiple notes"
    (let [storage (create-test-storage)
          repository (:repository storage)
          git (Git/wrap repository)
          commit-id (commit/commit-virtual git "main" "baz.txt" "data"
                                           "Third commit" "Test User" "[email protected]")]
      (try
        (notes/add-git-note git commit-id {:tx_id "tx-789"
                                           :metadata {:key1 "value1"}})
        (let [merged (notes/add-git-note git commit-id {:metadata {:key2 "value2"}})
              note (notes/read-note git commit-id)]
          (is (= {:key1 "value1" :key2 "value2"} (:metadata note))))
        (finally
          (protocol/close storage))))))
test/chrondb/api/sql/execution/transaction_test.clj (1)

76-102: Consider verifying storage state in addition to transaction contexts.

The test correctly validates transaction-context isolation (distinct tx_id values, correct origin, and metadata). However, it doesn't assert that the documents were actually persisted in the mock storage (@docs) or that the final document count matches the thread count. Adding these checks would strengthen confidence that concurrent writes don't corrupt storage state.

Consider adding assertions like:

       (is (every? #(= "sql" (:origin %)) @contexts) "Origin should be sql for all contexts")
       (is (every? #(= 1 (get-in % [:metadata :document-count])) @contexts)
-          "Each insert metadata should record a single document"))))
+          "Each insert metadata should record a single document")
+      (is (= thread-count (count @docs)) "All documents should be persisted in storage"))))
src/chrondb/api/sql/execution/query.clj (1)

698-711: Indexing inside transaction may impact performance.

The batch insert correctly wraps all document saves in a single transaction for atomicity. However, index operations (lines 703-709) execute within the transaction block. If indexing is I/O-intensive or slow, it could hold the transaction open longer than necessary. Consider whether indexing should be deferred to after transaction commit, or verify that the performance impact is acceptable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 706eda3 and 426dee8.

📒 Files selected for processing (4)
  • .github/workflows/publish-docker.yml (1 hunks)
  • src/chrondb/api/sql/execution/query.clj (7 hunks)
  • test/chrondb/api/sql/execution/transaction_test.clj (1 hunks)
  • test/chrondb/storage/git/notes_test.clj (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-12T09:40:13.442Z
Learnt from: avelino
Repo: moclojer/chrondb PR: 59
File: src/chrondb/core.clj:158-171
Timestamp: 2025-10-12T09:40:13.442Z
Learning: ChronDB's GitStorage only implements the internal protocol chrondb.storage.protocol/Storage and is not a Closeable. There is no simple way to use with-open or try-with-resources patterns without changing the storage's public API. The storage remains open and in use by the server throughout the process lifetime.

Applied to files:

  • test/chrondb/storage/git/notes_test.clj
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: test
  • GitHub Check: build-and-push
  • GitHub Check: Run SQL Protocol Benchmark
  • GitHub Check: test
🔇 Additional comments (19)
.github/workflows/publish-docker.yml (2)

3-7: Add pull_request trigger for CI validation.

Well done restructuring the workflow triggers to explicitly include both push (to main) and pull_request events. This enables validation builds on PRs before merge—a best practice for CI/CD.


26-32: Conditionals correctly gate credentials and registry pushes to main.

The conditionals on the login (line 28) and build-push (line 44) steps correctly restrict actual registry operations to push events on the main branch, while permitting validation builds on PR events. This is secure and follows GitHub Actions best practices.

Also applies to: 44-44

test/chrondb/storage/git/notes_test.clj (5)

1-10: LGTM!

The namespace declaration and imports are well-organized and appropriate for testing Git note functionality.


12-21: LGTM!

The test configuration properly isolates tests by disabling push and using a dedicated test directory.


23-26: LGTM!

The recursive directory deletion helper follows the correct pattern (deleting from leaves to root).


28-33: LGTM!

The fixture properly ensures test isolation by cleaning the repository before each test and overriding configuration.


35-36: LGTM!

Clean test helper for creating storage instances.

test/chrondb/api/sql/execution/transaction_test.clj (4)

69-74: LGTM!

Helper functions are straightforward and correctly implemented.


104-118: LGTM!

The test correctly validates that UPDATE operations produce transaction contexts with the "update" flag, proper origin, and document metadata.


120-135: Review comment is incorrect. No changes needed.

The code uses :document_id (underscore) on line 133 because tx/context-for-commit explicitly transforms the input parameter :document-id (hyphen) into :document_id (underscore) when constructing the returned context payload. This transformation is defined at src/chrondb/transaction/core.clj:145 with document-id (assoc :document_id document-id). The test correctly reads the transformed key from the context object.

Likely an incorrect or invalid review comment.


10-67: The mock captures transaction contexts at the wrong layer; clarify whether these tests should verify git integration or only SQL layer behavior.

The mock storage calls tx/context-for-commit directly in save-document and delete-document methods. However, in production code (src/chrondb/storage/git/commit.clj:197), context-for-commit is called in the Git commit handler after storage operations complete, not within the storage layer itself.

The tests as written verify that SQL query handlers (query/handle-insert-case, etc.) properly wrap operations in tx/with-transaction and that context-for-commit works when invoked within that context. However, they do not test the actual Git note attachment flow where the final payload is built.

This is acceptable if the tests are scoped to SQL layer behavior only. But if the intent is to verify end-to-end transaction context flow including Git integration, the mock should either:

  • Remove direct context-for-commit calls from storage methods, or
  • Be paired with integration tests that exercise the full git commit layer
src/chrondb/api/sql/execution/query.clj (8)

1-14: LGTM!

The namespace imports are appropriate for integrating transaction context and index operations.


18-30: LGTM!

The flag normalization logic correctly flattens nested collections, removes blanks, deduplicates, and returns nil for empty results (which is properly handled by the caller at line 41).


32-41: LGTM!

Transaction options builder correctly constructs metadata from operation parameters, merges additional metadata, normalizes flags, and returns a well-formed options map with origin "sql".


476-487: LGTM!

Single-document insert correctly wrapped in transaction with appropriate metadata (operation, id, table, branch).


489-511: LGTM!

Document update correctly wrapped in transaction with proper metadata.


513-520: LGTM!

Document deletion correctly wrapped in transaction with "delete" flag.


736-786: LGTM!

Batch UPDATE correctly wrapped in transaction with "update" flag and document metadata. All matching documents are updated atomically.


788-856: LGTM!

DELETE operation correctly wrapped in transaction with "delete" flag, including both storage and index removal.

…structuring

- Remove unused `:as ctx` destructurings that trigger linter warnings
- No logic/functionality changed, only signature cleanup for commit standards

Signed-off-by: Avelino <[email protected]>
@avelino avelino merged commit 3c1d08d into main Oct 31, 2025
10 of 11 checks passed
@avelino avelino deleted the avelino/transaction-witch-git-notes branch October 31, 2025 12:28
avelino added a commit that referenced this pull request Oct 31, 2025
- Refactor `commit-virtual` so all Git write operations add a structured transaction note (with `commit-id`, `commit-message`, and branch, plus propagated metadata if available) via `notes/add-git-note`.
- New note payload is built by merging intrinsic commit info, optional path, and the active transaction metadata using `tx/context-for-commit`.
- Any failure to attach a git note now aborts the commit and logs an error, ensuring no commits are created without contextual notes.
- Unit/integration test coverage added (`test-commit-virtual-note-failure-does-not-advance-ref`) to assert that commit refs are rolled back if note attachment fails, preventing orphaned commits without notes.

See: #62 (comment)

Signed-off-by: Avelino <[email protected]>
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.

Add operation flags using git-notes for special ChronDB events Add transaction context using git-notes for ChronDB operations

2 participants