Skip to content

feat: implement audit logging core, repository schemas, and tracking … - #525

Closed
TechBroAfrica wants to merge 827 commits into
TegoLabs:mainfrom
TechBroAfrica:feat/export-extension-restore-audit-log
Closed

TechBroAfrica wants to merge 827 commits into
TegoLabs:mainfrom
TechBroAfrica:feat/export-extension-restore-audit-log

Conversation

@TechBroAfrica

Copy link
Copy Markdown

closes #342

…utilities

What does this PR do?

Why?

Does this touch secret-key handling or transaction submission?

  • Yes — see notes above
  • No

Checklist

  • Tests pass (npm test)
  • Type check passes (npx tsc --noEmit)
  • Lint passes (npm run lint)
  • Tests cover the new functionality (TDD preferred — see CONTRIBUTING.md)
  • No unnecessary dependencies added
  • Commit messages follow conventional format
  • No console.log in core logic
  • ADR added if this is a significant design decision (see docs/adr)
  • E2E sandbox tested, if this touches RPC or daemon behavior (see docs/e2e-sandbox.md)

Olagoke22 and others added 30 commits June 29, 2026 16:11
…FIXED (TegoLabs#270)

* TegoLabs#145 feat(core): integrate HashiCorp Vault for key retrieval FIXED

* chore(tests): split mock secrets to evade GitGuardian false positives

* chore(tests): split more mock secrets to evade GitGuardian

---------

Co-authored-by: AbdulmalikAlayande <114596864+AbdulmalikAlayande@users.noreply.github.com>
…FIXED (TegoLabs#270)

* TegoLabs#145 feat(core): integrate HashiCorp Vault for key retrieval FIXED

* chore(tests): split mock secrets to evade GitGuardian false positives

* chore(tests): split more mock secrets to evade GitGuardian

---------

Co-authored-by: AbdulmalikAlayande <114596864+AbdulmalikAlayande@users.noreply.github.com>
- Add docker-compose.devnet.yaml: Quickstart testing image, --limits unlimited,
  30s polling cadence, debug logging, isolated named volumes
- Enhance docker-compose.yaml: restart policies, JSON log rotation, parameterised
  ports, LOG_LEVEL/NODE_ENV env vars
- Add .env.example: full environment variable reference with inline comments
- Add tests/docker/devnet-compose.test.ts: 32 TDD assertions covering file
  presence, service config, volume isolation, network sharing, .env.example,
  and compose merge compatibility
- Update .dockerignore: exclude compose files, systemd/, docs/, templates/
- Update .gitignore: allow .env.example via negation rule

Acceptance criteria met: docker compose -f docker-compose.yaml
-f docker-compose.devnet.yaml up boots daemon and mock RPC environment
successfully.

All 530 tests pass, 63 docker-specific tests, 5 skipped TODOs.
…estimates

- Add countExtensionsInLastHour() to repositories.ts to query extension_history
  for the past 60-minute window (issue TegoLabs#142)
- Export HOURLY_RATE_LIMIT = 5 constant from extension.ts (issue TegoLabs#142)
- Export isRateLimited() that gates on countExtensionsInLastHour >= limit (issue TegoLabs#142)
- Enforce rate limit in runAutoExtensions(): skip + log when limit reached (issue TegoLabs#142)
- Export ResourceEstimate interface and parseResourceEstimate() in rpc/client.ts
  to extract cpuInstructions, memoryBytes, minResourceFee from simulation
  responses (issue TegoLabs#133)
- Add comprehensive TDD tests written before implementation:
  - tests/db/rate_limiter.test.ts: countExtensionsInLastHour edge cases
  - tests/core/rate_limiter.test.ts: isRateLimited, runAutoExtensions integration
  - tests/rpc/resource_estimate.test.ts: parseResourceEstimate + failure edge cases

Closes TegoLabs#133
Closes TegoLabs#137
Closes TegoLabs#142
AbdulmalikAlayande and others added 17 commits July 27, 2026 22:25
vitest.config.ts only globs tests/**/*.test.ts, so this file was
never executed despite being valid, passing coverage for the exact
dispatch/retry/channel-routing logic about to be refactored to
support pluggable alert channels.
Central registration point for alert channel plugins. A contributor
adding a new channel calls registerAlertChannel() with a
ChannelDefinition instead of editing dispatcher.ts's channel map,
the CLI's --type if/else chain, and a DB CHECK constraint.
Preserves existing behavior exactly: same target flags, same missing-
target error text, same lazy dynamic import for discord/telegram, same
webhook-only HMAC signing. This is the reference implementation new
channel plugins should follow.
Replaces the hardcoded DEFAULT_CHANNELS object with a registry-backed
lookup, so a plugin channel registered anywhere becomes deliverable
without editing this file. Explicit channels overrides (used
throughout the test suite) are unaffected — only the default when one
is omitted changed source. deliverSingleAlert's channelType is widened
from a fixed union to string for the same reason.
channel_type validity is now enforced by the alert channel registry
at the application layer instead of a fixed SQL enum, so adding a
channel no longer requires a schema change. The CHECK now only
guards against an empty string.
…ration

The SCHEMA comment-stripper (`--.*\n`) silently failed to match
comments ending in \r\n, since JS's `.` excludes all line terminators
including \r. On a CRLF checkout, an unstripped comment survives into
the whitespace-collapsed script, and SQLite's own -- comment then
runs to the string's end, swallowing every statement after it with
no thrown error. Switched to `--[^\n]*\n`, which matches either line
ending. Latent since schema.sql had no comments before this change.

Also adds relaxChannelTypeChecks(), following the existing
migrateAlertConfigsChannelTypeCheck() convention, to rebuild
alert_configs and resource_alert_configs in place for databases
created before the CHECK was relaxed.
AlertConfig, UndeliveredAlert, ResourceAlertConfig, and
UndeliveredResourceAlerts previously hardcoded the built-in channel
names in their type signatures. The registry is now the source of
truth for valid channel names, so these widen to string.
The beforeEach block manually rebuilt alert_configs with a hardcoded
5-name CHECK on every test, a leftover workaround from before
schema.sql had these columns natively. It silently undid the CHECK
relaxation, since it ran unconditionally rather than detecting
whether schema.sql already had the change. getDatabaseForTesting()
already execs the current schema.sql into a fresh database, so the
whole block was redundant even before this. Also adds coverage for
plugin channel_type values and empty-string rejection on both
alert_configs and resource_alert_configs.
Replaces the per-channel if/else chain with a lookup against the
alert channel registry, so a plugin channel's --type, target flag,
missing-target error, and signing behavior all come from its
ChannelDefinition instead of a hardcoded branch in this file. All
existing error message text is preserved exactly for the five
built-in channels; the generic "unknown type" message is now built
from whatever channels are actually registered.
@drips-wave

drips-wave Bot commented Jul 28, 2026

Copy link
Copy Markdown

@TechBroAfrica Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added an audit-log CLI command to export extension audit history as JSONL.
    • Supports optional date-based filtering with --since.
    • Outputs records in execution order and omits output when no records are found.
  • Tests

    • Added coverage for JSONL formatting, audit record contents, and date filtering.

Walkthrough

Adds an audit-log CLI command that exports extension history as filtered JSONL. Records are queried with optional since timestamps, serialized with trailing newline handling, tested, and registered in the main Commander entrypoint.

Changes

Audit-log export

Layer / File(s) Summary
Audit-log query contract
src/db/repositories.ts
Adds the AuditLogRecord contract and a read-only query joining extension history with contract-entry metadata, supporting ascending order and optional executed_at filtering.
JSONL serialization and validation
src/core/audit_log.ts, tests/core/audit_log.test.ts
Serializes audit records into JSONL and tests emitted fields and since filtering.
CLI command wiring
src/commands/audit-log.ts, src/index.ts
Adds the audit-log --since <date> command, writes non-empty output to stdout, and registers it with the CLI.
Estimated code review effort: 3 (Moderate) ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant exportAuditLog
  participant getAuditLogExtensions
  CLI->>exportAuditLog: pass optional since date
  exportAuditLog->>getAuditLogExtensions: query filtered extension history
  getAuditLogExtensions-->>exportAuditLog: return audit records
  exportAuditLog-->>CLI: return JSONL
  CLI->>CLI: write non-empty JSONL to stdout
Loading

Suggested reviewers: abdulmalikalayande

Poem

A bunny hops through logs so neat,
JSONL carrots, line by line, a treat.
Since dates guide the whiskered trail,
XLM tales stream without fail.
The CLI thumps its tiny drum—
Audit records, here they come!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The extension export, command wiring, JSONL output, since filter, and tests are present, but restore-tracking coverage can't be verified from the summary. Confirm whether restore-tracking data exists; if so, add support for it, or note that the issue is intentionally scoped to extensions only.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly points to the audit logging implementation.
Description check ✅ Passed The description is related to the audit-log work and references the linked issue.
Out of Scope Changes check ✅ Passed The changes stay within the audit-log command, export logic, repository query, CLI wiring, and tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 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/core/audit_log.ts`:
- Around line 10-12: Update exportAuditLog() to include append-only restore
transaction records alongside getAuditLogExtensions() results, preserving the
existing JSON-lines output and ordering. Reuse the restore history/query source
that exposes successful restore fields such as txHash, ledger, and feeCharged,
and ensure those records are aggregated before serialization.

In `@src/db/repositories.ts`:
- Around line 374-397: Update the exported AuditLogRecord and the SELECT in
getAuditLogExtensions to include a stable entry field that satisfies the
audit-log contract, deriving it from the existing entry data without removing
current fields. Extend the JSONL test to assert the returned entry value.
- Around line 401-406: Update the audit-log query’s since filter in the
repository method containing the `executed_at` ordering to compare normalized
datetime values for both `eh.executed_at` and the `since` parameter, while
preserving ascending chronological ordering. Add a regression test covering
mixed timestamp formats and verifying correct filtering/order.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d04192fe-9e3a-44ca-b580-5aaf770fefb6

📥 Commits

Reviewing files that changed from the base of the PR and between 35d9237 and 1c21797.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • src/commands/audit-log.ts
  • src/core/audit_log.ts
  • src/db/repositories.ts
  • src/index.ts
  • tests/core/audit_log.test.ts
📜 Review details
🔇 Additional comments (2)
src/commands/audit-log.ts (1)

5-17: LGTM!

src/index.ts (1)

22-22: LGTM!

Also applies to: 53-53

Comment thread src/core/audit_log.ts
Comment on lines +10 to +12
export function exportAuditLog(db: Database.Database, since?: string): string {
const records = getAuditLogExtensions(db, since);
return records.map((record) => JSON.stringify(record)).join("\n") + (records.length > 0 ? "\n" : "");

Copy link
Copy Markdown

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
ast-grep outline src/db --items all
rg -n -i -C3 '\brestore\b|restore_history|restore.*track' src tests
rg -n -C3 'CREATE TABLE|extension_history|history' src

Repository: AbdulmalikAlayande/sorokeep

Length of output: 45460


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== audit_log.ts =="
cat -n src/core/audit_log.ts

echo
echo "== restore restoreEntries implementation =="
sed -n '460,565p' src/core/extension.ts

echo
echo "== restore command flow =="
sed -n '1,120p' src/commands/restore.ts

echo
echo "== repository exports around audit/restore-related functions =="
rg -n -C2 "export interface|export function getAuditLogExtensions|export function recordExtension|restore_|RestoreFootprintOp|txHash|tx_hash|tx_hash" src/db/repositories.ts src/core/audit_log.ts src/core/extension.ts

echo
echo "== deterministic check: audit_log source only calls getAuditLogExtensions =="
python3 - <<'PY'
from pathlib import Path
s = Path("src/core/audit_log.ts").read_text()
print("imports:", [line.strip() for line in s.splitlines() if line.startswith("import")])
for name in ["getAuditLogExtensions", "getRestore", "getRestoreHistory", "restoreHistory", "audit_log_history", "restore"]:
    print(f"contains '{name}':", name in s)
PY

Repository: AbdulmalikAlayande/sorokeep

Length of output: 19644


Include restore transactions in the audit stream.

exportAuditLog() only calls getAuditLogExtensions(), so restore transactions are missing while successful restores return keys like txHash, ledger, and feeCharged. Aggregate restore history at this boundary, or add the required append-only restore record/query before exporting.

🤖 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/core/audit_log.ts` around lines 10 - 12, Update exportAuditLog() to
include append-only restore transaction records alongside
getAuditLogExtensions() results, preserving the existing JSON-lines output and
ordering. Reuse the restore history/query source that exposes successful restore
fields such as txHash, ledger, and feeCharged, and ensure those records are
aggregated before serialization.

Comment thread src/db/repositories.ts
Comment on lines +374 to +397
export interface AuditLogRecord {
tx_hash: string;
contract_id: string;
entry_key_xdr: string;
entry_type: string;
entry_label: string | null;
old_ttl_ledgers: number;
new_ttl_ledgers: number;
cost_xlm: number | null;
executed_at: string;
}

export function getAuditLogExtensions(db: Database.Database, since?: string): AuditLogRecord[] {
let query = `
SELECT
eh.tx_hash AS tx_hash,
eh.contract_id AS contract_id,
ce.entry_key_xdr AS entry_key_xdr,
ce.entry_type AS entry_type,
ce.label AS entry_label,
eh.old_ttl_ledgers AS old_ttl_ledgers,
eh.new_ttl_ledgers AS new_ttl_ledgers,
eh.cost_xlm AS cost_xlm,
eh.executed_at AS executed_at

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Expose the required entry field.

The exported record has entry_key_xdr, entry_type, and entry_label, but no entry property as required by the audit-log contract. Add a stable entry field and assert it in the JSONL test.

Proposed fix
 export interface AuditLogRecord {
     tx_hash: string;
     contract_id: string;
+    entry: string;
     entry_key_xdr: string;
     entry_type: string;
@@
             eh.tx_hash AS tx_hash,
             eh.contract_id AS contract_id,
+            ce.entry_key_xdr AS entry,
             ce.entry_key_xdr AS entry_key_xdr,
📝 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
export interface AuditLogRecord {
tx_hash: string;
contract_id: string;
entry_key_xdr: string;
entry_type: string;
entry_label: string | null;
old_ttl_ledgers: number;
new_ttl_ledgers: number;
cost_xlm: number | null;
executed_at: string;
}
export function getAuditLogExtensions(db: Database.Database, since?: string): AuditLogRecord[] {
let query = `
SELECT
eh.tx_hash AS tx_hash,
eh.contract_id AS contract_id,
ce.entry_key_xdr AS entry_key_xdr,
ce.entry_type AS entry_type,
ce.label AS entry_label,
eh.old_ttl_ledgers AS old_ttl_ledgers,
eh.new_ttl_ledgers AS new_ttl_ledgers,
eh.cost_xlm AS cost_xlm,
eh.executed_at AS executed_at
export interface AuditLogRecord {
tx_hash: string;
contract_id: string;
entry: string;
entry_key_xdr: string;
entry_type: string;
entry_label: string | null;
old_ttl_ledgers: number;
new_ttl_ledgers: number;
cost_xlm: number | null;
executed_at: string;
}
export function getAuditLogExtensions(db: Database.Database, since?: string): AuditLogRecord[] {
let query = `
SELECT
eh.tx_hash AS tx_hash,
eh.contract_id AS contract_id,
ce.entry_key_xdr AS entry,
ce.entry_key_xdr AS entry_key_xdr,
ce.entry_type AS entry_type,
ce.label AS entry_label,
eh.old_ttl_ledgers AS old_ttl_ledgers,
eh.new_ttl_ledgers AS new_ttl_ledgers,
eh.cost_xlm AS cost_xlm,
eh.executed_at AS executed_at
🤖 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/db/repositories.ts` around lines 374 - 397, Update the exported
AuditLogRecord and the SELECT in getAuditLogExtensions to include a stable entry
field that satisfies the audit-log contract, deriving it from the existing entry
data without removing current fields. Extend the JSONL test to assert the
returned entry value.

Comment thread src/db/repositories.ts
Comment on lines +401 to +406
const params: any[] = [];
if (since) {
query += ` WHERE eh.executed_at >= ?`;
params.push(since);
}
query += ` ORDER BY eh.executed_at ASC`;

Copy link
Copy Markdown

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
ast-grep outline src/db/repositories.ts --items all
rg -n -C3 'CREATE TABLE extension_history|executed_at|recordExtension|getAuditLogExtensions' src tests

Repository: AbdulmalikAlayande/sorokeep

Length of output: 46255


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- repositories.ts lines 46-77 and 337-408 ---\n'
sed -n '46,77p;337,408p' src/db/repositories.ts | cat -n

printf '\n--- audit_log tests ---\n'
sed -n '1,90p' tests/core/audit_log.test.ts | cat -n

printf '\n--- sqlite lexical timestamp ordering probe ---\n'
sqlite3 :memory: <<'SQL'
CREATE TABLE t (ts TEXT);
INSERT INTO t VALUES
  ('2026-07-10T00:00:00Z'),
  ('2026-07-10 00:00:00'),
  ('2026-07-10T12:00:00Z'),
  ('2026-07-10 12:00:00');
SELECT 'lexical';
SELECT ts FROM t ORDER BY ts ASC;
SELECT 'datetime';
SELECT ts FROM t ORDER BY datetime(ts) ASC;
SELECT julianday('2026-07-10T00:00:00Z') AS isoz,
       julianday('2026-07-10 00:00:00') AS space,
       julianday('2026-07-10T12:00:00Z') AS iso12,
       julianday('2026-07-10 12:00:00') AS actual;
SQL

Repository: AbdulmalikAlayande/sorokeep

Length of output: 256


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- repositories.ts lines 46-77 and 337-408 ---'
sed -n '46,77p;337,408p' src/db/repositories.ts | cat -n

printf '%s\n' ''
printf '%s\n' '--- audit_log tests ---'
sed -n '1,90p' tests/core/audit_log.test.ts | cat -n

printf '%s\n' ''
printf '%s\n' '--- sqlite lexical timestamp ordering probe ---'
sqlite3 :memory: <<'SQL'
CREATE TABLE t (ts TEXT);
INSERT INTO t VALUES
  ('2026-07-10T00:00:00Z'),
  ('2026-07-10 00:00:00'),
  ('2026-07-10T12:00:00Z'),
  ('2026-07-10 12:00:00');
SELECT 'lexical';
SELECT ts FROM t ORDER BY ts ASC;
SELECT 'datetime';
SELECT ts FROM t ORDER BY datetime(ts) ASC;
SELECT julianday('2026-07-10T00:00:00Z') AS isoz,
       julianday('2026-07-10 00:00:00') AS space,
       julianday('2026-07-10T12:00:00Z') AS iso12,
       julianday('2026-07-10 12:00:00') AS actual;
SQL

Repository: AbdulmalikAlayande/sorokeep

Length of output: 7956


🌐 Web query:

SQLite ISO8601 date string sort order datetime function lexical sort datetime('now') returns

💡 Result:

SQLite does not have a dedicated date or datetime data type; instead, it stores these values as TEXT, REAL, or INTEGER [1][2]. When stored as text strings in the ISO8601 format—specifically YYYY-MM-DD HH:MM:SS or YYYY-MM-DD HH:MM:SS.SSS—these strings possess the essential property that their lexical (alphabetical) sort order is identical to their chronological sort order [3][4][5]. The datetime('now') function returns the current date and time as a text string formatted as 'YYYY-MM-DD HH:MM:SS' (or with milliseconds as 'YYYY-MM-DD HH:MM:SS.SSS' if the 'subsec' modifier is used) [6][7][8]. This function operates in UTC by default [7][9]. To obtain the local time, you can append the 'localtime' modifier, as in datetime('now', 'localtime') [10][9]. Because SQLite uses standard lexical comparison for TEXT columns, storing dates in any format other than one that matches chronological order (like ISO8601) will result in incorrect sorting [3][4][11]. If dates are stored in a non-sortable format, you must either convert them using the strftime function or by manipulating substrings (e.g., using substr) within your ORDER BY clause to transform them into a sortable string format during the query [4][12].

Citations:


🌐 Web query:

"SQLite" "datetime" "T" "not lexically sortable"

💡 Result:

SQLite date and time strings that use either a space or a 'T' as a separator are lexically sortable, provided they are formatted in a consistent ISO 8601-like manner (e.g., YYYY-MM-DD HH:MM:SS or YYYY-MM-DDTHH:MM:SS) [1][2][3]. Because SQLite stores dates and times as TEXT [1][4], they are sorted based on their ASCII/Unicode character values. Since both space and 'T' are characters that maintain the chronological order when used as a separator between a date and time of the same length, strings using either separator sort correctly in chronological order [1]. Key considerations regarding this behavior include: 1. Consistency is required: While both '2026-07-28 12:00:00' and '2026-07-28T12:00:00' are lexically sortable on their own, mixing them in the same column could lead to unexpected sorting results if the string lengths or formats vary [1]. 2. SQLite's Native Functions: SQLite's built-in date and time functions, such as datetime, natively accept both 'T' and space as separators in their input strings [2][3]. However, the output of functions like datetime defaults to using a space [2][3]. 3. ISO 8601 Compatibility: While the 'T' is the standard separator defined by ISO 8601, SQLite's use of a space is a common extension supported by many database systems and libraries for improved readability [4][5]. If your application requires strict adherence to ISO 8601 or needs to ensure consistent sorting, you can standardize your timestamps by using either a space or 'T' consistently across all entries [1][4]. If you need to include timezone offsets, note that those can break simple lexicographical sorting [6], so it is generally recommended to store timestamps in UTC [2][4].

Citations:


🌐 Web query:

"2026-07-10 12:00:00" "2026-07-10T00:00:00Z" lexical order

💡 Result:

ISO 8601 date and time strings are designed such that their chronological order corresponds to their lexicographical (alphabetical) order, provided they are in the same format and timezone [1][2][3]. This property is a primary benefit of the standard, making it widely used for organizing files, log entries, and database keys [2][3]. When comparing the two strings you provided: 1. 2026-07-10 12:00:00 2. 2026-07-10T00:00:00Z They will not sort correctly as strings because they use different formats [1]. The first string uses a space as a separator and lacks a timezone designator, while the second uses the standard ISO 8601 'T' separator and the 'Z' (UTC) indicator [2][4]. To achieve consistent lexicographical sorting that matches chronological order: - Ensure all timestamps use the same format [1]. The standard ISO 8601 format, such as 2026-07-10T00:00:00Z, is recommended for data interchange [4]. - If you have mixed formats, you must normalize them to a uniform format before performing a lexical comparison [1]. In a standard lexicographical comparison (ASCII/Unicode character-by-character), "2026-07-10 12:00:00" would come before "2026-07-10T00:00:00Z" because the space character (ASCII 32) has a lower value than the 'T' character (ASCII 84). However, this does not reflect the chronological order, as 12:00:00 is later in the day than 00:00:00 [3][5].

Citations:


Compare executed_at chronologically.

execution_history.executed_at is stored as TEXT, and the current audit-log query compares it directly with the --since string. Mixed timestamp layouts can sort/filter incorrectly (for example, 2026-07-10 12:00:00 compares before 2026-07-10T00:00:00Z lexically). Normalize executed_at and since before comparing, e.g. with datetime(eh.executed_at) >= datetime(?), and add a mixed-format regression test.

🤖 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/db/repositories.ts` around lines 401 - 406, Update the audit-log query’s
since filter in the repository method containing the `executed_at` ordering to
compare normalized datetime values for both `eh.executed_at` and the `since`
parameter, while preserving ascending chronological ordering. Add a regression
test covering mixed timestamp formats and verifying correct filtering/order.

@TechBroAfrica

Copy link
Copy Markdown
Author

@AbdulmalikAlayande HI Maintainer, kindly review PR, Thanks

@gitguardian

gitguardian Bot commented Jul 29, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

Since your pull request originates from a forked repository, GitGuardian is not able to associate the secrets uncovered with secret incidents on your GitGuardian dashboard.
Skipping this check run and merging your pull request will create secret incidents on your GitGuardian dashboard.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
- - Generic High Entropy Secret ded54f4 tests/commands/guard-cli-export-import.test.ts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@TechBroAfrica

Copy link
Copy Markdown
Author

@AbdulmalikAlayande KIndly merge.

AbdulmalikAlayande added a commit that referenced this pull request Aug 2, 2026
…nsion transactions (#525, #342)

PR #525's branch was catastrophically stale and polluted — 1110
changed files including a committed npm cache directory
(.npm-cache/_cacache/, thousands of binary blobs) and an unrelated
.kiro/ AI-tool scaffolding directory, on top of touching effectively
every file in the repo due to how far behind main the branch was. Not
mergeable in any form; extracted just the three genuinely new,
relevant files (the actual feature) and applied them fresh against
current main.

- db/repositories.ts: new AuditLogRecord interface and
  getAuditLogExtensions(db, since?) — a read-only query joining
  extension_history with contract_entries, matching the issue's
  required columns (tx_hash, contract_id, entry, old/new TTL, cost,
  timestamp). No existing functions touched.
- core/audit_log.ts: exportAuditLog(db, since?) rendering the query
  result as JSONL, one object per line.
- commands/audit-log.ts: `sorokeep audit-log --since <date>`,
  registered in src/cli/program.ts (the current entry point;
  src/index.ts is now a thin wrapper around createProgram()).
- No restore-transaction history table exists in schema.sql yet, so
  per the issue's own contingency note this stays scoped to
  extension_history only — flagging that gap rather than inventing a
  new table.

Kept the original PR's test scenarios and added two more (empty
export, --since excluding every row).

Verified: tsc clean, full suite 1311/1311, npm audit clean, build
succeeds, and manually smoke-tested `sorokeep audit-log` and
`--since` end-to-end against an isolated database — confirmed valid,
correctly-filtered JSONL output.
@AbdulmalikAlayande

Copy link
Copy Markdown
Collaborator

Thanks — the actual audit-log feature (the export function, the query, and the CLI command) was well designed and correctly matched the issue's spec.

Closing manually rather than merging: this branch was catastrophically stale and polluted — 1110 changed files, including a committed npm cache directory (.npm-cache/_cacache/, thousands of binary blobs) and an unrelated .kiro/ AI-tool scaffolding directory, on top of touching effectively every file in the repo since the branch was so far behind main. Not mergeable in any form.

Extracted just the three genuinely new files (the real feature) and applied them fresh against current main (commit 1e4a880) — same query, same JSONL export logic, same CLI command, registered in the current entry point. Correctly noted (as the issue itself anticipated) that there's no restore-transaction history table yet, so this stays scoped to extension_history only.

Verified: tsc clean, full suite 1311/1311, npm audit clean, build succeeds, and manually smoke-tested sorokeep audit-log and --since end-to-end. Good core design — please double-check your .gitignore/local npm cache config before your next PR so this doesn't happen again, but the feature work itself was solid. Thanks!

AbdulmalikAlayande added a commit that referenced this pull request Aug 5, 2026
…format (#526, #345)

PR #526's branch had the same repo-wide npm-cache/.kiro pollution seen
in #525, and its one real file (tests/observability/metrics_format.test.ts)
tested a completely fabricated parallel registry with invented metric
names (sorokeep_contracts_total, sorokeep_alerts_fired_total, etc.)
that don't exist anywhere in the real codebase, using a hand-rolled
line-splitting parser — the issue explicitly asked for "a real
Prometheus text-format parser (not a hand-rolled regex)". The test
would have passed forever without ever touching the actual /metrics
endpoint or catching a real regression in it.

Rewrote it to test the genuine article: seeds a database, starts the
real observability server (createMetricsServer) against it, fetches
/metrics, and parses the response with parse-prometheus-text-format
(added as a devDependency) — asserting every metric family currently
registered in src/observability/registry.ts is present, plus spot-
checking seeded values for the fleet and extension-cost metrics.

Also ran `npm audit fix` while installing the new devDependency, which
surfaced and cleanly resolved 3 pre-existing high/moderate severity
advisories in the production dependency tree (hono, ip-address,
fast-uri, all transitive via already-installed packages) — unrelated
to this PR but caught along the way.

Verified: tsc clean, full suite 1313/1313, npm audit clean, build
succeeds.
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.

feat(observability): add structured audit-log export (JSONL) for extension/restore transactions