Skip to content

Persist orchestration and payment events for audit history (#26)#73

Open
Kirtiman-55 wants to merge 1 commit into
Flamki:masterfrom
Kirtiman-55:feat/persist-run-audit-history
Open

Persist orchestration and payment events for audit history (#26)#73
Kirtiman-55 wants to merge 1 commit into
Flamki:masterfrom
Kirtiman-55:feat/persist-run-audit-history

Conversation

@Kirtiman-55
Copy link
Copy Markdown
Contributor

@Kirtiman-55 Kirtiman-55 commented Jun 1, 2026

Summary

This PR adds durable run history so orchestration and payment activity can be reviewed after restarts for debugging and demos.

What changed

  • Added a configurable run-history storage adapter (file or memory)
  • Persisted orchestration and payment metadata per run
  • Added GET /api/runs to fetch recent runs
  • Documented storage strategy, file path, and retention config

Audit data captured

  • Task, budget, run status, and timestamps
  • Spend summary (totalSpent, payment protocol, tx counts, elapsed)
  • Transaction proof linkage (txHash, explorerUrl, payment method)
  • Key per-run event trail from orchestration/payment flow

Config

  • RUN_HISTORY_STORAGE (file or memory)
  • RUN_HISTORY_FILE (default: ./data/run-history.json)
  • RUN_HISTORY_MAX_RUNS (default: 200)

Acceptance criteria

  • Historical runs survive restart: ? (file mode)
  • Audit records include spend + tx proof linkage: ?
  • Storage strategy is documented and configurable: ?

Validation

  • npm test passes

@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented Jun 1, 2026

Hey @Kirtiman-55! 👋 It looks like this PR isn't linked to any issue.

If this PR is for one of the issues assigned to you as part of a Wave, please link it to ensure your contribution is tracked properly. You can do this by adding a keyword to the PR description (e.g., Closes #123), or by clicking a button below:

Issue Title
#16 Implement structured logging with request correlation IDs Link to this issue
#31 Stop printing wallet secrets in setup output by default Link to this issue
#22 Add timeout and retry controls for Anthropic API calls Link to this issue
#18 Add integration tests for x402 premium endpoints Link to this issue

ℹ️ Learn more about linking PRs to issues

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 1, 2026

@Kirtiman-55 is attempting to deploy a commit to the flamki's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces configurable run history and audit logging for orchestration and payment transactions. It adds environment variables and configuration for storage mode, persistent file-backed or in-memory storage of run events and transaction proofs, server-side initialization and orchestrate endpoint integration, and a new /api/runs endpoint to retrieve recent run history with optional limits.

Changes

Run History Audit Logging

Layer / File(s) Summary
Configuration and environment setup
.env.example, src/config.js
Environment variables RUN_HISTORY_STORAGE, RUN_HISTORY_FILE, and RUN_HISTORY_MAX_RUNS are defined; config exports storage mode (file or memory), resolved file path with <repoRoot>/data/run-history.json default, and maximum retained runs (minimum 10, default 200).
In-memory run history store
src/storage/run-history.js
InMemoryRunHistoryStore class maintains runs with status, timestamps, events, and transaction proofs; supports creating runs, appending formatted events, computing tx proofs from payments on completion, persisting error state on failure, and listing recent runs by limit with normalization.
File-backed persistence and storage factory
src/storage/run-history.js
FileRunHistoryStore extends in-memory store, adds atomic file write (temp + rename) after each mutation, directory creation, and JSON load-on-init with maxRuns truncation; createRunHistoryStore(config) factory selects in-memory or file-backed implementation and initializes it.
Server initialization and orchestrate endpoint integration
src/server.js
Server initializes runHistoryStore at startup; both /api/orchestrate POST and GET handlers create a run, tag broadcasted events with runId, append events asynchronously to history (warn-only on failure), and persist completion or failure; POST response includes runId; new /api/runs?limit=20 endpoint lists recent runs; /api/status exposes run-history configuration under runHistory object.
Documentation of run-history feature and API
README.md
Adds environment variable documentation, describes file-based and in-memory storage modes, and documents the GET /api/runs?limit=20 endpoint behavior, response structure (including transaction proof linkage), and persistence semantics.

Sequence Diagram

sequenceDiagram
  participant client as Client
  participant server as /api/orchestrate
  participant store as runHistoryStore
  participant status as /api/status
  client->>server: POST/GET orchestrate(task, budget)
  server->>store: createRun({ task, budget, source })
  server->>server: broadcast events with runId
  server->>store: appendEvent(runId, event)
  server->>server: orchestrate logic
  server->>store: completeRun(runId, result)
  server->>client: response + runId
  client->>status: GET /api/status
  status->>store: (reads config)
  status->>client: status + runHistory config
  client->>server: GET /api/runs?limit=20
  server->>store: listRecent(limit)
  server->>client: recent runs list
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #26: Implements run-history persistence feature with file and memory storage adapters, configuration via environment variables, event append logic, transaction proof derivation, and a new /api/runs endpoint for audit history retrieval.

Poem

🐰 With history now in hand, we track each quest so grand,
File-backed or memory's fleeting dance, runs recorded as they prance,
Transaction proofs and audit trails, completion whispers, failure's wails,
A /api/runs to glimpse the past, where config keeps the whole thing fast! 🌙

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: persisting orchestration and payment events for audit history, which aligns perfectly with all file modifications.
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.
Description check ✅ Passed The PR description covers the main changes, acceptance criteria, and validation. However, the Validation section is incomplete with only 'npm test passes' mentioned without confirmation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

Copy link
Copy Markdown

@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: 4

🧹 Nitpick comments (1)
src/server.js (1)

45-45: ⚡ Quick win

Run-history init failure is fatal to startup.

This top-level await rejects if the store fails to initialize (e.g., the data directory isn't writable), preventing the server from booting. That's inconsistent with the non-fatal handling of x402 init below (warn-and-continue). Since run history is an audit/observability feature, consider degrading gracefully (warn + fall back to in-memory) rather than blocking the whole server.

Confirm whether a boot-time failure of the audit store should be fatal for this service, or whether availability of the core API should be preserved.

🤖 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 `@src/server.js` at line 45, The top-level await of createRunHistoryStore can
abort startup if it rejects; wrap the call to createRunHistoryStore so
initialization failures are caught, log a warning via the same logger used
elsewhere, and fall back to an in-memory/no-op run-history implementation
instead of letting the process exit; update the runHistoryStore binding (the
variable assigned from createRunHistoryStore) to point to the fallback when
createRunHistoryStore throws, and ensure callers that expect runHistoryStore
(e.g., any functions referencing runHistoryStore) still work with the in-memory
implementation.
🤖 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 `@src/config.js`:
- Line 44: The config value runHistoryStorage should be normalized to a
consistent case at source so consumers match; change the exported
runHistoryStorage assignment to lowercased value (e.g., set runHistoryStorage to
(process.env.RUN_HISTORY_STORAGE || 'file').toLowerCase()) so
createRunHistoryStore and the comparisons in src/server.js (where
config.runHistoryStorage === 'file' is used) see the same lowercase token;
update the runHistoryStorage export only (leave createRunHistoryStore intact) to
ensure endpoints return the correct store instance.

In `@src/storage/run-history.js`:
- Around line 21-26: The object assignment for cost and totalSpent in
run-history.js incorrectly uses the logical OR fallback (event.cost || null,
event.totalSpent || null) which converts legitimate 0 values to null; update
these assignments to use the nullish coalescing operator (event.cost ?? null and
event.totalSpent ?? null) so only undefined or null become null while preserving
numeric 0, leaving other fields (paidVia, txHash, explorerUrl, status)
unchanged.
- Around line 123-128: The catch in the load routine is overwriting a
possibly-corrupt JSON file by calling this.persist() on any non-ENOENT error;
change the handler to first move/backup the bad file (e.g., rename original
filename to filename + `.corrupt.<timestamp>` or `.bak`) before calling
this.persist(), log the backup path and original error, and only then persist
the in-memory this.runs; update the catch in run-history.js that references
this.persist and this.runs to perform the backup/rename with a timestamp and
handle rename errors gracefully (log and abort persist if rename fails).
- Around line 131-136: The persist() method in run-history.js can race because
multiple callers (appendEvent, completeRun, failRun) write to the same temp
file; fix by making persist() use a unique temp filename (e.g., include a
timestamp/UUID or process+pid+counter) instead of `${this.filePath}.tmp` and
serialize concurrent persists with an internal write queue or mutex on the
RunHistory instance (add a simple Promise-based queue or lock property used by
persist()). Update persist() to await acquiring the lock, perform writeFile to
the unique temp name and rename, then release the lock; ensure appendEvent,
completeRun, and failRun continue to call persist() but will now be ordered and
safe.

---

Nitpick comments:
In `@src/server.js`:
- Line 45: The top-level await of createRunHistoryStore can abort startup if it
rejects; wrap the call to createRunHistoryStore so initialization failures are
caught, log a warning via the same logger used elsewhere, and fall back to an
in-memory/no-op run-history implementation instead of letting the process exit;
update the runHistoryStore binding (the variable assigned from
createRunHistoryStore) to point to the fallback when createRunHistoryStore
throws, and ensure callers that expect runHistoryStore (e.g., any functions
referencing runHistoryStore) still work with the in-memory implementation.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 717cec9f-e4b9-4d14-bbd0-316b8ef588d5

📥 Commits

Reviewing files that changed from the base of the PR and between b9a790d and a831c06.

📒 Files selected for processing (5)
  • .env.example
  • README.md
  • src/config.js
  • src/server.js
  • src/storage/run-history.js

Comment thread src/config.js
anthropicRetryBaseDelayMs: Math.max(100, toNumberOr(process.env.ANTHROPIC_RETRY_BASE_DELAY_MS, 500)),

// Run history persistence
runHistoryStorage: process.env.RUN_HISTORY_STORAGE || 'file',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize runHistoryStorage casing to match the factory.

createRunHistoryStore lowercases this value ((config.runHistoryStorage || 'file').toLowerCase()), but src/server.js compares it case-sensitively (config.runHistoryStorage === 'file') in both /api/status and /api/runs. With RUN_HISTORY_STORAGE=FILE, the file store is still created, but those endpoints report file: null. Normalize once here so all consumers agree.

🔧 Proposed fix
-  runHistoryStorage: process.env.RUN_HISTORY_STORAGE || 'file',
+  runHistoryStorage: (process.env.RUN_HISTORY_STORAGE || 'file').toLowerCase(),
🤖 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 `@src/config.js` at line 44, The config value runHistoryStorage should be
normalized to a consistent case at source so consumers match; change the
exported runHistoryStorage assignment to lowercased value (e.g., set
runHistoryStorage to (process.env.RUN_HISTORY_STORAGE || 'file').toLowerCase())
so createRunHistoryStore and the comparisons in src/server.js (where
config.runHistoryStorage === 'file' is used) see the same lowercase token;
update the runHistoryStorage export only (leave createRunHistoryStore intact) to
ensure endpoints return the correct store instance.

Comment on lines +21 to +26
cost: event.cost || null,
paidVia: event.paidVia || null,
txHash: event.txHash || null,
explorerUrl: event.explorerUrl || null,
status: event.status || null,
totalSpent: event.totalSpent || null,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

|| null drops legitimate numeric 0 for cost/totalSpent.

A genuine 0 cost or 0 total-spent (e.g., a skipped/free step) is coerced to null, misrepresenting the audit trail. Use nullish coalescing.

🔧 Proposed fix
-    cost: event.cost || null,
+    cost: event.cost ?? null,
     paidVia: event.paidVia || null,
     txHash: event.txHash || null,
     explorerUrl: event.explorerUrl || null,
     status: event.status || null,
-    totalSpent: event.totalSpent || null,
+    totalSpent: event.totalSpent ?? null,
📝 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
cost: event.cost || null,
paidVia: event.paidVia || null,
txHash: event.txHash || null,
explorerUrl: event.explorerUrl || null,
status: event.status || null,
totalSpent: event.totalSpent || null,
cost: event.cost ?? null,
paidVia: event.paidVia || null,
txHash: event.txHash || null,
explorerUrl: event.explorerUrl || null,
status: event.status || null,
totalSpent: event.totalSpent ?? null,
🤖 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 `@src/storage/run-history.js` around lines 21 - 26, The object assignment for
cost and totalSpent in run-history.js incorrectly uses the logical OR fallback
(event.cost || null, event.totalSpent || null) which converts legitimate 0
values to null; update these assignments to use the nullish coalescing operator
(event.cost ?? null and event.totalSpent ?? null) so only undefined or null
become null while preserving numeric 0, leaving other fields (paidVia, txHash,
explorerUrl, status) unchanged.

Comment on lines +123 to +128
} catch (err) {
if (err.code !== 'ENOENT') {
console.warn(` run history load warning: ${err.message}`);
}
await this.persist();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Parse failure silently overwrites existing history.

On any non-ENOENT error (e.g., a partially-written/corrupt JSON file), the catch logs a warning and immediately persist()s, overwriting the file with the current (empty) in-memory runs. That discards potentially recoverable audit data. Consider backing up the bad file before overwriting.

🛡️ Suggested approach
     } catch (err) {
       if (err.code !== 'ENOENT') {
         console.warn(`  run history load warning: ${err.message}`);
+        await fs.rename(this.filePath, `${this.filePath}.corrupt-${Date.now()}`).catch(() => {});
       }
       await this.persist();
     }
🤖 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 `@src/storage/run-history.js` around lines 123 - 128, The catch in the load
routine is overwriting a possibly-corrupt JSON file by calling this.persist() on
any non-ENOENT error; change the handler to first move/backup the bad file
(e.g., rename original filename to filename + `.corrupt.<timestamp>` or `.bak`)
before calling this.persist(), log the backup path and original error, and only
then persist the in-memory this.runs; update the catch in run-history.js that
references this.persist and this.runs to perform the backup/rename with a
timestamp and handle rename errors gracefully (log and abort persist if rename
fails).

Comment on lines +131 to +136
async persist() {
const payload = JSON.stringify({ runs: this.runs.slice(0, this.maxRuns) }, null, 2);
const tempPath = `${this.filePath}.tmp`;
await fs.writeFile(tempPath, payload, 'utf8');
await fs.rename(tempPath, this.filePath);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Concurrent persists race on a shared temp file and can corrupt history.

appendEvent is invoked fire-and-forget from both /api/orchestrate handlers, and completeRun/failRun also persist. With concurrent runs (or an unawaited appendEvent overlapping completeRun on the same run), two persist() calls run simultaneously. Both write to the same ${this.filePath}.tmp path and then rename it: the writes can interleave (corrupt JSON) and the second rename can fail with ENOENT once the first already moved the temp file. Use a unique temp name and serialize writes.

🔒 Proposed fix (unique temp + write queue)
   async persist() {
-    const payload = JSON.stringify({ runs: this.runs.slice(0, this.maxRuns) }, null, 2);
-    const tempPath = `${this.filePath}.tmp`;
-    await fs.writeFile(tempPath, payload, 'utf8');
-    await fs.rename(tempPath, this.filePath);
+    this._writeChain = (this._writeChain || Promise.resolve()).then(async () => {
+      const payload = JSON.stringify({ runs: this.runs.slice(0, this.maxRuns) }, null, 2);
+      const tempPath = `${this.filePath}.${process.pid}.${crypto.randomBytes(4).toString('hex')}.tmp`;
+      await fs.writeFile(tempPath, payload, 'utf8');
+      await fs.rename(tempPath, this.filePath);
+    });
+    return this._writeChain;
   }
🤖 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 `@src/storage/run-history.js` around lines 131 - 136, The persist() method in
run-history.js can race because multiple callers (appendEvent, completeRun,
failRun) write to the same temp file; fix by making persist() use a unique temp
filename (e.g., include a timestamp/UUID or process+pid+counter) instead of
`${this.filePath}.tmp` and serialize concurrent persists with an internal write
queue or mutex on the RunHistory instance (add a simple Promise-based queue or
lock property used by persist()). Update persist() to await acquiring the lock,
perform writeFile to the unique temp name and rename, then release the lock;
ensure appendEvent, completeRun, and failRun continue to call persist() but will
now be ordered and safe.

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.

1 participant