Skip to content

⚡ Bolt: Resolution Evidence Blockchain & Performance Optimization#629

Open
RohanExploit wants to merge 1 commit into
mainfrom
bolt-resolution-blockchain-optimization-14891178781287473821
Open

⚡ Bolt: Resolution Evidence Blockchain & Performance Optimization#629
RohanExploit wants to merge 1 commit into
mainfrom
bolt-resolution-blockchain-optimization-14891178781287473821

Conversation

@RohanExploit
Copy link
Copy Markdown
Owner

@RohanExploit RohanExploit commented Apr 3, 2026

This optimization implements blockchain-style cryptographic integrity chaining for the Resolution Proof system while significantly improving performance through O(1) lookups and database query optimizations.

💡 What:

  • Integrated HMAC-SHA256 chaining into the resolution evidence submission pipeline.
  • Implemented a thread-safe cache for the latest resolution hash to eliminate redundant database queries during evidence creation.
  • Optimized the resolution verification logic to use efficient database aggregates (.count()) and limit-based fetching (.first()).
  • Added a new O(1) verification endpoint mirroring the pattern used for issues and field visits.
  • Fixed schema inconsistencies in ResolutionProofToken to support secure token signing and database constraints.

🎯 Why:

  • Ensures the tamper-proof nature of resolution evidence (critical for civic transparency).
  • Reduces database load and latency during both evidence submission (O(1) last hash lookup) and verification (avoiding O(N) memory materialization of all evidence records).

📊 Impact:

  • Evidence submission latency reduced by eliminating a database roundtrip for the previous hash.
  • Evidence verification memory footprint reduced by avoiding loading all historical evidence records for a grievance.
  • Single-record integrity checks are now O(1) instead of requiring a full chain scan or secondary database lookups.

🔬 Measurement:

  • Verified using pytest tests/test_resolution_proof.py (30 tests) and full blockchain test suite (42 tests total).
  • Schema changes verified using sqlite3 PRAGMA inspection.
  • Import and dependency consistency verified across the backend.

PR created automatically by Jules for task 14891178781287473821 started by @RohanExploit


Summary by cubic

Adds blockchain-style HMAC-SHA256 chaining to resolution evidence and an O(1) integrity verification endpoint, while cutting DB load during submission and verification. Improves latency by caching the last hash and avoiding full-record scans.

  • New Features

    • Cryptographic chaining for resolution evidence with integrity_hash and previous_integrity_hash; automatic migrations add columns and an index.
    • New O(1) endpoint: GET /api/resolution-proof/{evidence_id}/blockchain-verify for single-record integrity checks.
    • Resolution proof tokens now include valid_from, valid_until, and nonce; expires_at set explicitly for compatibility.
  • Performance

    • resolution_last_hash_cache enables O(1) previous-hash lookup; cache updates only after successful commit.
    • Verification uses .count() and fetches only the latest record (.first()), avoiding materializing all evidence.
    • Index added on previous_integrity_hash for fast linkage lookups.

Written for commit 011f375. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added blockchain-style integrity verification for resolution evidence via new API endpoint.
    • Implemented cryptographic chaining for evidence to ensure correctness and detect tampering.
    • Enhanced proof tokens with validity time windows and unique nonce support.
  • Performance Improvements

    • Optimized evidence verification queries to reduce memory usage and database transfer.

…cation

- Added `integrity_hash` and `previous_integrity_hash` columns to `ResolutionEvidence` model.
- Implemented cryptographic chaining (HMAC-SHA256) in `ResolutionProofService.submit_evidence`.
- Optimized `ResolutionProofService.verify_evidence` to use `.count()` and fetching only the latest record instead of materializing all records.
- Integrated `resolution_last_hash_cache` (ThreadSafeCache) for O(1) previous hash retrieval during submission.
- Added `/api/resolution-proof/{evidence_id}/blockchain-verify` endpoint for O(1) single-record integrity validation.
- Fixed `ResolutionProofToken` model to explicitly store `valid_from`, `valid_until`, and `nonce` for secure signing and satisfy `expires_at` NOT NULL constraint.
- Updated `backend/init_db.py` with required schema migrations.
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copilot AI review requested due to automatic review settings April 3, 2026 14:08
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 3, 2026

Deploy Preview for fixmybharat failed. Why did it fail? →

Name Link
🔨 Latest commit 011f375
🔍 Latest deploy log https://app.netlify.com/projects/fixmybharat/deploys/69cfc9fc59703d0008a094a4

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 3, 2026

🙏 Thank you for your contribution, @RohanExploit!

PR Details:

Quality Checklist:
Please ensure your PR meets the following criteria:

  • Code follows the project's style guidelines
  • Self-review of code completed
  • Code is commented where necessary
  • Documentation updated (if applicable)
  • No new warnings generated
  • Tests added/updated (if applicable)
  • All tests passing locally
  • No breaking changes to existing functionality

Review Process:

  1. Automated checks will run on your code
  2. A maintainer will review your changes
  3. Address any requested changes promptly
  4. Once approved, your PR will be merged! 🎉

Note: The maintainers will monitor code quality and ensure the overall project flow isn't broken.

@github-actions github-actions Bot added the size/m label Apr 3, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

The changes implement blockchain-style integrity chaining for resolution evidence. A new thread-safe cache stores the last integrity hash, database migrations add chaining columns and token validity fields, cryptographic signing chains evidence submissions, and a new verification endpoint validates integrity chains against stored hashes.

Changes

Cohort / File(s) Summary
Knowledge Base
.jules/bolt.md
Added prescriptive guidance entries for cache correctness (updating cache only post-commit) and evidence verification optimization (using .count() and .order_by(...desc()).first() instead of .all()).
Cache Infrastructure
backend/cache.py
Introduced new global resolution_last_hash_cache (TTL 3600s, max_size 1) to track the most recent integrity hash for chaining validation.
Database Models & Migrations
backend/models.py, backend/init_db.py
Added integrity_hash and previous_integrity_hash columns to ResolutionEvidence; added valid_from, valid_until, and nonce columns to ResolutionProofToken. Created corresponding migration logic with conditional column/index creation.
Integrity Chaining & Optimization
backend/resolution_proof_service.py
Implemented cryptographic chaining: compute integrity_hash = sign(evidence_hash|token_id|prev_hash), store both hashes on evidence submission, and update cache only post-commit. Optimized verify_evidence by replacing .all() with .count() and .order_by(...id.desc()).first().
Verification Endpoint
backend/routers/resolution_proof.py
Added new GET endpoint /{evidence_id}/blockchain-verify to retrieve evidence fields, compute chained integrity check via signed payload, validate against stored hash, and return verification result with detailed messages.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Router as resolution_proof<br/>Router
    participant Service as ResolutionProof<br/>Service
    participant DB as Database
    participant Cache as Cache
    participant Crypto as Signing

    Client->>Router: POST submit evidence<br/>(token_id, evidence)
    Router->>Service: submit_evidence(token_id, evidence)
    Service->>Cache: get(resolution_last_hash_cache)
    alt Cache hit
        Cache-->>Service: previous_hash
    else Cache miss
        Service->>DB: query ResolutionEvidence<br/>order_by desc limit 1
        DB-->>Service: most recent record
        Service->>Service: extract previous_hash<br/>from DB
    end
    Service->>Crypto: sign(evidence_hash|token_id|prev_hash)
    Crypto-->>Service: integrity_hash
    Service->>DB: create ResolutionEvidence<br/>(integrity_hash, previous_hash)
    DB-->>Service: commit success
    Service->>Cache: set(resolution_last_hash_cache,<br/>integrity_hash)
    Cache-->>Service: cached
    Service-->>Router: success response
    Router-->>Client: 201 Created
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

size/m

Poem

🐰 Hop! Hop! The chains now bind tight,
Each hash links true, each block airtight,
Cache whispers fast, no cache-despair,
Integrity verified with cryptographic flair!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly addresses the main change: blockchain-style integrity chaining and performance optimization for resolution evidence, which aligns with the primary content of the changeset.
Description check ✅ Passed The description covers type of change (new features/performance), related issue, testing approach, and detailed impact metrics; however, the 'Checklist' section checkboxes are not explicitly marked.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bolt-resolution-blockchain-optimization-14891178781287473821

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
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the Resolution Proof system with blockchain-style integrity chaining for ResolutionEvidence and adds performance optimizations to reduce unnecessary DB materialization during verification and evidence submission.

Changes:

  • Adds HMAC-SHA256 integrity chaining to resolution evidence creation (with cached “last hash” lookup) and persists integrity_hash + previous_integrity_hash.
  • Optimizes resolution verification to use .count() and fetch only the latest evidence record.
  • Introduces a new per-evidence blockchain verification endpoint and adds DB migrations/fields to align ResolutionProofToken persistence with the service/schema expectations.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
backend/routers/resolution_proof.py Adds an evidence-level blockchain verification endpoint.
backend/resolution_proof_service.py Adds integrity chaining during submission + verification query optimizations.
backend/models.py Adds integrity chain columns to ResolutionEvidence and new token fields to ResolutionProofToken.
backend/init_db.py Adds migrations for the new evidence/token columns and index.
backend/cache.py Adds a dedicated cache instance for resolution evidence last-hash.
.jules/bolt.md Documents learnings around cache safety and verification optimizations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +374 to +381
# Performance Boost: Use thread-safe cache for O(1) last hash retrieval
prev_hash = resolution_last_hash_cache.get("last_hash")
if prev_hash is None:
# Cache miss: fetch ONLY the last hash from DB
last_record = db.query(ResolutionEvidence.integrity_hash).order_by(ResolutionEvidence.id.desc()).first()
prev_hash = last_record[0] if last_record and last_record[0] else ""
resolution_last_hash_cache.set(data=prev_hash, key="last_hash")

Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

submit_evidence() derives prev_hash from an in-memory cache without validating it against the current DB tail. In multi-worker deployments (or after another process writes newer evidence), this can cause previous_integrity_hash to point to a hash that is no longer the latest persisted record, weakening the intended append-only chain semantics.

Suggestion: treat the cache as a hint only—fetch (last_id, last_hash) from DB and compare against cached values (or store a single cached tuple) before computing the new integrity_hash; refresh the cache on mismatch. If strict linear chaining is required under concurrency, compute prev_hash inside a serialized/locked transaction rather than relying on the cache.

Suggested change
# Performance Boost: Use thread-safe cache for O(1) last hash retrieval
prev_hash = resolution_last_hash_cache.get("last_hash")
if prev_hash is None:
# Cache miss: fetch ONLY the last hash from DB
last_record = db.query(ResolutionEvidence.integrity_hash).order_by(ResolutionEvidence.id.desc()).first()
prev_hash = last_record[0] if last_record and last_record[0] else ""
resolution_last_hash_cache.set(data=prev_hash, key="last_hash")
# Treat the cache as a hint only: validate against the current DB tail
cached_tail = resolution_last_hash_cache.get("last_evidence_tail")
last_record = (
db.query(ResolutionEvidence.id, ResolutionEvidence.integrity_hash)
.order_by(ResolutionEvidence.id.desc())
.first()
)
db_tail = (
(last_record[0], last_record[1] or "")
if last_record
else (None, "")
)
if cached_tail != db_tail:
resolution_last_hash_cache.set(data=db_tail, key="last_evidence_tail")
prev_hash = db_tail[1]

Copilot uses AI. Check for mistakes.
Comment thread backend/models.py
Comment on lines +306 to 310
valid_from = Column(DateTime, default=lambda: datetime.datetime.now(datetime.timezone.utc))
valid_until = Column(DateTime, nullable=True)
nonce = Column(String, nullable=True)
generated_at = Column(DateTime, default=lambda: datetime.datetime.now(datetime.timezone.utc))
expires_at = Column(DateTime, nullable=False)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The new valid_from/valid_until/nonce columns are defined as nullable, but the service logic (validate_token() and timestamp window checks) assumes these fields are always present and will raise if they are NULL.

Suggestion: make these columns non-nullable (and ensure generate_proof_token() always sets them), or update service logic to fall back to legacy fields (generated_at/expires_at) when the new columns are missing so existing rows don’t break runtime validation.

Copilot uses AI. Check for mistakes.
Comment thread backend/init_db.py
Comment on lines +225 to +236
if not column_exists("resolution_proof_tokens", "valid_from"):
conn.execute(text("ALTER TABLE resolution_proof_tokens ADD COLUMN valid_from DATETIME"))
logger.info("Added valid_from column to resolution_proof_tokens")

if not column_exists("resolution_proof_tokens", "valid_until"):
conn.execute(text("ALTER TABLE resolution_proof_tokens ADD COLUMN valid_until DATETIME"))
logger.info("Added valid_until column to resolution_proof_tokens")

if not column_exists("resolution_proof_tokens", "nonce"):
conn.execute(text("ALTER TABLE resolution_proof_tokens ADD COLUMN nonce VARCHAR"))
logger.info("Added nonce column to resolution_proof_tokens")

Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

Migration adds valid_from, valid_until, and nonce to resolution_proof_tokens but doesn’t backfill existing rows. Any still-valid pre-migration token rows will have NULL in these columns, which can break validate_token() (it reads token.valid_until / token.valid_from / token.nonce).

Suggestion: add a one-time backfill step after adding the columns (e.g., set valid_from=generated_at, valid_until=expires_at where null). If nonce is required for signature verification, either backfill from existing token payload data (if stored) or adjust signature verification to handle legacy tokens explicitly.

Suggested change
if not column_exists("resolution_proof_tokens", "valid_from"):
conn.execute(text("ALTER TABLE resolution_proof_tokens ADD COLUMN valid_from DATETIME"))
logger.info("Added valid_from column to resolution_proof_tokens")
if not column_exists("resolution_proof_tokens", "valid_until"):
conn.execute(text("ALTER TABLE resolution_proof_tokens ADD COLUMN valid_until DATETIME"))
logger.info("Added valid_until column to resolution_proof_tokens")
if not column_exists("resolution_proof_tokens", "nonce"):
conn.execute(text("ALTER TABLE resolution_proof_tokens ADD COLUMN nonce VARCHAR"))
logger.info("Added nonce column to resolution_proof_tokens")
has_valid_from = column_exists("resolution_proof_tokens", "valid_from")
if not has_valid_from:
conn.execute(text("ALTER TABLE resolution_proof_tokens ADD COLUMN valid_from DATETIME"))
logger.info("Added valid_from column to resolution_proof_tokens")
has_valid_from = True
has_valid_until = column_exists("resolution_proof_tokens", "valid_until")
if not has_valid_until:
conn.execute(text("ALTER TABLE resolution_proof_tokens ADD COLUMN valid_until DATETIME"))
logger.info("Added valid_until column to resolution_proof_tokens")
has_valid_until = True
has_nonce = column_exists("resolution_proof_tokens", "nonce")
if not has_nonce:
conn.execute(text("ALTER TABLE resolution_proof_tokens ADD COLUMN nonce VARCHAR"))
logger.info("Added nonce column to resolution_proof_tokens")
has_nonce = True
has_generated_at = column_exists("resolution_proof_tokens", "generated_at")
has_expires_at = column_exists("resolution_proof_tokens", "expires_at")
if has_valid_from and has_generated_at:
conn.execute(text(
"UPDATE resolution_proof_tokens "
"SET valid_from = generated_at "
"WHERE valid_from IS NULL AND generated_at IS NOT NULL"
))
logger.info("Backfilled valid_from from generated_at for legacy resolution_proof_tokens rows")
if has_valid_until and has_expires_at:
conn.execute(text(
"UPDATE resolution_proof_tokens "
"SET valid_until = expires_at "
"WHERE valid_until IS NULL AND expires_at IS NOT NULL"
))
logger.info("Backfilled valid_until from expires_at for legacy resolution_proof_tokens rows")
if has_nonce:
if column_exists("resolution_proof_tokens", "token"):
conn.execute(text(
"UPDATE resolution_proof_tokens "
"SET nonce = token "
"WHERE nonce IS NULL AND token IS NOT NULL"
))
logger.info("Backfilled nonce from token for legacy resolution_proof_tokens rows")
else:
logger.warning(
"resolution_proof_tokens.nonce exists but no legacy source column was found for backfill; "
"legacy rows may require explicit compatibility handling during validation."
)

Copilot uses AI. Check for mistakes.
Comment on lines +233 to +250
evidence = db.query(
ResolutionEvidence.evidence_hash,
ResolutionEvidence.token_id,
ResolutionEvidence.integrity_hash,
ResolutionEvidence.previous_integrity_hash
).filter(ResolutionEvidence.id == evidence_id).first()

if not evidence:
raise HTTPException(status_code=404, detail="Evidence not found")

# Determine previous hash (O(1) from stored column)
prev_hash = evidence.previous_integrity_hash or ""

# Fetch token_id string for chaining logic consistency
from backend.models import ResolutionProofToken
token = db.query(ResolutionProofToken.token_id).filter(ResolutionProofToken.id == evidence.token_id).first()
token_id_str = token[0] if token else ""

Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

/{evidence_id}/blockchain-verify currently does an extra query to fetch the token UUID (ResolutionProofToken.token_id) and silently falls back to "" when the token row is missing. That can both (a) undercut the stated O(1) optimization (2 DB round-trips) and (b) report a misleading “integrity check failed” when the real problem is a missing/invalid token reference.

Suggestion: fetch ResolutionProofToken.token_id via a join in the initial query (single round-trip), and if the token is missing, return an explicit error/invalid state indicating the evidence’s token reference can’t be resolved.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/resolution_proof_service.py">

<violation number="1" location="backend/resolution_proof_service.py:375">
P1: The cache-based `prev_hash` lookup is not atomic with the subsequent insert+commit, so concurrent `submit_evidence` calls will read the same `prev_hash` and produce two records chaining off the same predecessor — forking the integrity chain. A `SELECT ... FOR UPDATE` or database-level advisory lock is needed to serialize chain extension and ensure each new record links to the true latest hash.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


# 5b. Implement cryptographic chaining (Issue #BLOCKCHAIN-003)
# Performance Boost: Use thread-safe cache for O(1) last hash retrieval
prev_hash = resolution_last_hash_cache.get("last_hash")
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 3, 2026

Choose a reason for hiding this comment

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

P1: The cache-based prev_hash lookup is not atomic with the subsequent insert+commit, so concurrent submit_evidence calls will read the same prev_hash and produce two records chaining off the same predecessor — forking the integrity chain. A SELECT ... FOR UPDATE or database-level advisory lock is needed to serialize chain extension and ensure each new record links to the true latest hash.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/resolution_proof_service.py, line 375:

<comment>The cache-based `prev_hash` lookup is not atomic with the subsequent insert+commit, so concurrent `submit_evidence` calls will read the same `prev_hash` and produce two records chaining off the same predecessor — forking the integrity chain. A `SELECT ... FOR UPDATE` or database-level advisory lock is needed to serialize chain extension and ensure each new record links to the true latest hash.</comment>

<file context>
@@ -368,6 +370,19 @@ def submit_evidence(
 
+        # 5b. Implement cryptographic chaining (Issue #BLOCKCHAIN-003)
+        # Performance Boost: Use thread-safe cache for O(1) last hash retrieval
+        prev_hash = resolution_last_hash_cache.get("last_hash")
+        if prev_hash is None:
+            # Cache miss: fetch ONLY the last hash from DB
</file context>
Fix with Cubic

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: 1

Caution

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

⚠️ Outside diff range comments (1)
backend/resolution_proof_service.py (1)

373-412: ⚠️ Potential issue | 🔴 Critical

The chain head still races under concurrent submissions.

A thread-safe cache does not make the append operation atomic. Two requests, or two workers with stale in-memory caches, can read the same prev_hash, insert different rows with the same previous_integrity_hash, and both commit successfully. That forks the chain while /blockchain-verify still reports each row as valid because it only recomputes the local seal. Serialize head selection in the database or enforce a uniqueness/retry guard there, then keep the cache as a post-commit optimization.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/resolution_proof_service.py` around lines 373 - 412, The current
prepend is racy because reading resolution_last_hash_cache and inserting a new
ResolutionEvidence is not atomic; serialize head selection in the DB and add a
uniqueness/retry guard: enforce a DB-level uniqueness constraint on
ResolutionEvidence.previous_integrity_hash (allowing empty/genesis), and change
the create flow to (a) obtain the true current head with a serializing read
(e.g., SELECT ... ORDER BY id DESC FOR UPDATE on the last row or use a
single-row chain_head table and lock it), (b) compute integrity_hash
(ResolutionProofService._sign_payload) and insert the new ResolutionEvidence,
and (c) on unique-constraint violation (IntegrityError) retry the
read-sign-insert loop a few times before failing; keep updating
resolution_last_hash_cache only after a successful commit.
🧹 Nitpick comments (1)
backend/routers/resolution_proof.py (1)

233-249: Use the token UUID already sealed with the evidence.

This re-queries ResolutionProofToken.token_id, so historical verification now depends on that row still existing and remaining unchanged. The UUID is already captured in ResolutionEvidence.metadata_bundle at submission time, which is the stable value that was actually sealed. Prefer that snapshot, or denormalize it into a dedicated column, so old evidence keeps verifying even if expired tokens are cleaned up later.

♻️ Possible simplification
-        evidence = db.query(
-            ResolutionEvidence.evidence_hash,
-            ResolutionEvidence.token_id,
-            ResolutionEvidence.integrity_hash,
-            ResolutionEvidence.previous_integrity_hash
-        ).filter(ResolutionEvidence.id == evidence_id).first()
+        evidence = db.query(
+            ResolutionEvidence.evidence_hash,
+            ResolutionEvidence.metadata_bundle,
+            ResolutionEvidence.integrity_hash,
+            ResolutionEvidence.previous_integrity_hash,
+        ).filter(ResolutionEvidence.id == evidence_id).first()
...
-        from backend.models import ResolutionProofToken
-        token = db.query(ResolutionProofToken.token_id).filter(ResolutionProofToken.id == evidence.token_id).first()
-        token_id_str = token[0] if token else ""
+        token_id_str = (evidence.metadata_bundle or {}).get("token_id", "")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/routers/resolution_proof.py` around lines 233 - 249, The code
re-queries ResolutionProofToken.token_id using evidence.token_id (in the block
that assigns token_id_str), which makes verification depend on a separate token
row that can be deleted or changed; instead read the sealed UUID from
ResolutionEvidence.metadata_bundle (or add a dedicated column populated at
submission) and use that value for token_id_str so historical evidence
verification uses the immutable sealed snapshot; update the logic in the
function interacting with ResolutionEvidence (referencing
ResolutionEvidence.metadata_bundle and token_id_str) to parse/extract the UUID
from metadata_bundle and fall back to the denormalized column if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/init_db.py`:
- Around line 223-235: After adding the three nullable columns to
resolution_proof_tokens, add a data migration that (1) backfills
valid_from/valid_until from the table's existing legacy timestamp column(s)
(e.g., created_at or issued_at) so validate_token() has timezone-aware
datetimes, and (2) handles rows missing nonce by either setting valid_until to
now (explicitly expiring them) or generating a new nonce and rebuilding the HMAC
using ResolutionProofService (so future calls to
ResolutionProofService.validate_token() won't fail); implement this immediately
after the ALTER TABLEs using the same conn.execute/inspector flow (use
column_exists, conn.execute(text(...)) and ResolutionProofService methods to
recompute tokens as needed).

---

Outside diff comments:
In `@backend/resolution_proof_service.py`:
- Around line 373-412: The current prepend is racy because reading
resolution_last_hash_cache and inserting a new ResolutionEvidence is not atomic;
serialize head selection in the DB and add a uniqueness/retry guard: enforce a
DB-level uniqueness constraint on ResolutionEvidence.previous_integrity_hash
(allowing empty/genesis), and change the create flow to (a) obtain the true
current head with a serializing read (e.g., SELECT ... ORDER BY id DESC FOR
UPDATE on the last row or use a single-row chain_head table and lock it), (b)
compute integrity_hash (ResolutionProofService._sign_payload) and insert the new
ResolutionEvidence, and (c) on unique-constraint violation (IntegrityError)
retry the read-sign-insert loop a few times before failing; keep updating
resolution_last_hash_cache only after a successful commit.

---

Nitpick comments:
In `@backend/routers/resolution_proof.py`:
- Around line 233-249: The code re-queries ResolutionProofToken.token_id using
evidence.token_id (in the block that assigns token_id_str), which makes
verification depend on a separate token row that can be deleted or changed;
instead read the sealed UUID from ResolutionEvidence.metadata_bundle (or add a
dedicated column populated at submission) and use that value for token_id_str so
historical evidence verification uses the immutable sealed snapshot; update the
logic in the function interacting with ResolutionEvidence (referencing
ResolutionEvidence.metadata_bundle and token_id_str) to parse/extract the UUID
from metadata_bundle and fall back to the denormalized column if needed.
🪄 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: ab7cbda5-b53a-465a-b007-96320290d537

📥 Commits

Reviewing files that changed from the base of the PR and between 6fee644 and 011f375.

📒 Files selected for processing (6)
  • .jules/bolt.md
  • backend/cache.py
  • backend/init_db.py
  • backend/models.py
  • backend/resolution_proof_service.py
  • backend/routers/resolution_proof.py

Comment thread backend/init_db.py
Comment on lines +223 to +235
# Resolution Proof Tokens Table Migrations
if inspector.has_table("resolution_proof_tokens"):
if not column_exists("resolution_proof_tokens", "valid_from"):
conn.execute(text("ALTER TABLE resolution_proof_tokens ADD COLUMN valid_from DATETIME"))
logger.info("Added valid_from column to resolution_proof_tokens")

if not column_exists("resolution_proof_tokens", "valid_until"):
conn.execute(text("ALTER TABLE resolution_proof_tokens ADD COLUMN valid_until DATETIME"))
logger.info("Added valid_until column to resolution_proof_tokens")

if not column_exists("resolution_proof_tokens", "nonce"):
conn.execute(text("ALTER TABLE resolution_proof_tokens ADD COLUMN nonce VARCHAR"))
logger.info("Added nonce column to resolution_proof_tokens")
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

Handle legacy resolution_proof_tokens rows during this migration.

Lines 225-234 only add valid_from, valid_until, and nonce as nullable columns. ResolutionProofService.validate_token() now treats those fields as required when checking expiry and rebuilding the HMAC, so any still-live token created before this migration can start failing validation or blow up on valid_until.tzinfo. Add a data migration that backfills valid_from/valid_until from the legacy timestamps and explicitly expires or regenerates rows that have no nonce.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/init_db.py` around lines 223 - 235, After adding the three nullable
columns to resolution_proof_tokens, add a data migration that (1) backfills
valid_from/valid_until from the table's existing legacy timestamp column(s)
(e.g., created_at or issued_at) so validate_token() has timezone-aware
datetimes, and (2) handles rows missing nonce by either setting valid_until to
now (explicitly expiring them) or generating a new nonce and rebuilding the HMAC
using ResolutionProofService (so future calls to
ResolutionProofService.validate_token() won't fail); implement this immediately
after the ALTER TABLEs using the same conn.execute/inspector flow (use
column_exists, conn.execute(text(...)) and ResolutionProofService methods to
recompute tokens as needed).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants