Skip to content

feat: audit log IP and append-only table, reward supply invariant proofs, claim flow e2e, coverage gate - #1423

Merged
joelpeace48-cell merged 1 commit into
FinesseStudioLab:mainfrom
web-ghost-dotcom:feat/e2e-kani-coverage-audit-log
Oct 18, 2026
Merged

joelpeace48-cell merged 1 commit into
FinesseStudioLab:mainfrom
web-ghost-dotcom:feat/e2e-kani-coverage-audit-log

Conversation

@web-ghost-dotcom

Copy link
Copy Markdown
Contributor

Summary

#1318 – Audit log with IP and timestamp. Audit entries already had actor, action, entity, diff and timestamp, but not the source IP, and the table was only tamper-evident. Now:

  • migration 052_audit_log_ip_address adds audit_logs.ip_address and two triggers that make the table append-only (any UPDATE/DELETE aborts);
  • recordAuditEntry stores req.ip; the repository returns ipAddress, can filter by it, and includes it in the CSV export;
  • the IP is part of the SHA-256 hash chain only when present, so entries written before this change still verify;
  • the three existing tamper tests drop the triggers first (they simulate an attacker with raw DB access); new tests cover recording, filtering, hash coverage and the append-only triggers.
    Behind a proxy, Express trust proxy must be set for req.ip to be the real client (noted in backend/README.md). The Postgres repository has no IP column yet.

#1317 – Formal verification of the reward accounting invariant. Z3 (SMT) models in contracts/rewards/formal/ prove that TOTAL_SUPPLY == Σ balances is preserved by credit, claim (and that its saturating_sub never clamps), admin_transfer and batch_credit, plus the base case after initialize. Each file declares its expected result and scripts/verify-reward-invariants.sh fails on any mismatch, including negative controls that must keep finding a counter-example. CI runs it in the contracts job. Write-up in docs/FORMAL_VERIFICATION.md.
The model found a real defect: batch_credit credited balances but never updated TOTAL_SUPPLY (model 05 returns the counter-example), so total_supply() was wrong and a later claim clamped the supply at 0. Fixed: the batch total is added to the supply (checked, before any write, so overflow stays atomic). A second defect surfaced while fixing it: a recipient listed twice in one batch only got the last amount; duplicates now accumulate. Regression tests added in contracts/rewards/src/test.rs.
Z3 was used rather than the K Framework; the properties are linear arithmetic. Not modelled yet: vesting, staking, redemption, referral/airdrop payouts and burn proposals (listed in the doc).

#1319 – Playwright E2E, campaign creation to reward claim. frontend/tests/flow/campaign-to-claim.spec.js drives the real UI: the admin area is gated until an admin wallet connects, the campaign is created through the form (payload checked), the app navigates to the new campaign page, then a participant claims points on the home page with a signed Soroban transaction and the displayed balance goes 500 → 300. Also covered: claiming more than the balance (readable contract error, chain state unchanged) and the claim button's input validation. It is hermetic: the REST API, Horizon, the wallet extension and the Soroban RPC (support/soroban-chain-mock.js, real XDR with a stateful balance) are mocked in the test, so no testnet or backend is needed. It uses its own config (playwright.flow.config.js, npm run test:e2e:flow) because it needs a build with an admin wallet and rewards contract configured; a new campaign-to-claim-e2e CI job runs it. 3 tests pass locally on Chromium.
The test exposed an app bug, fixed in frontend/src/stellar.js: simulate() / signAndSend() resolve to a transaction object whose value is on .result, but the code used the object itself, so "Available points" always showed 0 and a claim always reported a 0 new balance (same for redeem, payout reserve, campaign on-chain state and is_participant). Added unwrapContractResult, which unwraps .result and passes plain values through unchanged. RegisterCampaign.jsx has two similar .simulate() uses that I did not touch.

#1320 – Backend coverage with CI enforcement. npm run test:coverage --workspace=backend runs the suite with Node's built-in coverage (lcov) and scripts/check-coverage.mjs fails when line, branch or function coverage is below backend/coverage-thresholds.json; a new backend-coverage CI job (Node 22) runs it and uploads the report. The 85 % target is not reached: measured coverage is 61.2 % lines / 82.3 % branches / 64.4 % functions, so the floors are set just below (60 / 80 / 62) as a ratchet, with the 85 % target recorded in the file and README. I added 24 unit tests for previously untested modules (errorHandler, cacheMiddleware, envValidation, auditLogService) and auditChain. Reaching 85 % needs tests for ~40 more route/service modules and for the ~220 tests that currently fail or time out.

Fixes needed to run anything on main

main currently cannot boot, so these are included (small, and needed for the migration/audit tests and the coverage run): a missing ); in backend/src/index.js (syntax error), the missing backend/src/lib/logger.js that three services import, and migrations 046/047/048 that exported an object instead of the version/description/up the runner requires (given the next free versions 49–51, since 46–48 are already used by sibling files).

Testing

  • Backend: sqliteAuditLogRepository, auditChain, auditLogService, errorHandler, cacheMiddleware, envValidation tests: 43 passing; full-suite coverage gate passes at the floors above.
  • scripts/verify-reward-invariants.sh: 8/8 models give the expected result.
  • Frontend: flow spec 3/3 passing; vitest unchanged (the same 2 failures as on main: CampaignDetail.countdown, contractErrors).

Not verified

  • The Rust contract change and its tests were not compiled or run. contracts/rewards does not build on main (33 compile errors unrelated to this change, e.g. duplicate Error discriminants), so only cargo check error counts could be compared (identical before and after). The SMT proofs cover the fixed logic; the Rust tests should be run once the crate compiles.
  • The new CI jobs have not run on GitHub.

Closes #1318
Closes #1317
Closes #1319
Closes #1320

@drips-wave

drips-wave Bot commented Oct 17, 2026

Copy link
Copy Markdown

@web-ghost-dotcom 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

@joelpeace48-cell
joelpeace48-cell merged commit dab0256 into FinesseStudioLab:main Oct 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment