Skip to content

⚡ Bolt: Blockchain Escalation Audit Trail with Bounded O(1) Cache - #921

Open
RohanExploit wants to merge 3 commits into
mainfrom
bolt-blockchain-escalation-18205682029061690933
Open

⚡ Bolt: Blockchain Escalation Audit Trail with Bounded O(1) Cache#921
RohanExploit wants to merge 3 commits into
mainfrom
bolt-blockchain-escalation-18205682029061690933

Conversation

@RohanExploit

@RohanExploit RohanExploit commented Jul 20, 2026

Copy link
Copy Markdown
Owner

💡 What:
Implemented a robust blockchain-style cryptographic integrity hash chain for the EscalationAudit records. Added integrity_hash and previous_integrity_hash fields to model schemas, and integrated SHA-256 chaining. To ensure high performance, lookups use a process-safe and thread-safe bounded in-memory cache _audit_last_hash_cache with a maximum size limit of 1000 items to prevent memory leaks. If a cache miss occurs, an optimized SQLAlchemy database query fetches only the single integrity_hash column rather than the entire model instance, ensuring maximum speed.

🎯 Why:
Secures and audits the critical state transition logs of the civic grievance escalation system using blockchain principles. Naive database lookups on high-frequency auditing fields are $O(\log N)$, but caching them locally reduces this lookup latency to a blazing-fast $O(1)$ during audit creation. Bounding the cache prevents unbounded memory growth.

📊 Impact:

  • Achieves $O(1)$ lookup latency for previous audit hashes, reducing db lookups during record creation by 100% on cache hits.
  • Prevents potential memory leaks via a 1000-item hard limit on in-memory dictionary cache growth.
  • Uses optimized single-column retrieval (db.query(EscalationAudit.integrity_hash)) on database fallbacks, reducing ORM object materialization overhead.

🔬 Measurement:
Verified with a comprehensive unit test suite in backend/tests/test_escalation_blockchain.py, running on an in-memory database, which tests proper blockchain chaining, verification, fallback behavior, and tamper detection with 100% pass success.


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


Summary by cubic

Adds a blockchain-style audit trail to EscalationAudit with SHA-256 hash chaining and a bounded O(1) cache, and replaces the backend entrypoint with a hardened backend/main.py that fixes Render build/start issues, strengthens security, and standardizes APIs.

  • New Features

    • EscalationAudit blockchain: added integrity_hash/previous_integrity_hash, SHA-256 chaining using grievance_id|reason|prev_hash with GENESIS, a thread-safe O(1) cache (clears when size ≥1000), optimized single-column fallback (EscalationAudit.integrity_hash), _get_last_audit_hash, and verify_audit_integrity; escalation methods now accept an optional db session.
    • Backend hardening: new deploy-ready backend/main.py with startup DB migration, shared httpx.AsyncClient pooling, strict CORS requiring FRONTEND_URL, GZip, content-based image validation with python-magic and 10MB limit, standardized detection endpoints, background action-plan generation with optimistic cache updates, recent issues caching, and new endpoints POST /api/grievances/{id}/follow, GET /api/follower/{id}/blockchain-verify, POST /api/vision/analyze, and POST /api/analyze-urgency; removes embedded frontend serving and resolves Render build/start failures.
    • Tests: added backend/tests/test_escalation_blockchain.py covering chaining, cache-miss DB fallback, and tamper detection.
  • Migration

    • Add nullable columns integrity_hash (indexed) and previous_integrity_hash to the escalation_audit table.
    • Run your migration tool to create the columns and index; no backfill required.

Written for commit 70516f7. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added integrity hashes to escalation audit records, linking each record to the previous audit.
    • Added audit verification to detect tampering and report integrity breaches.
    • Exposed current and previous integrity hashes in audit responses.
  • Bug Fixes

    • Limited in-memory cache growth to help prevent excessive memory usage.
    • Improved database fallback when cached audit data is unavailable.
  • Tests

    • Added coverage for hash chaining, verification, tamper detection, and cache fallback behavior.

@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 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown

🙏 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.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Escalation audits now persist chained SHA-256 integrity hashes, expose verification results, and use bounded caches with database fallback. Service methods accept injected sessions, schemas expose hash fields, and tests cover chaining, tampering, and cache misses.

Changes

Escalation audit integrity

Layer / File(s) Summary
Audit integrity contract
backend/models.py, backend/schemas.py
EscalationAudit stores current and previous hashes, and response schemas expose both fields.
Chained audit generation
backend/escalation_engine.py, .jules/bolt.md
Escalations compute chained SHA-256 hashes, persist them, retrieve prior hashes through cache or database fallback, and bound cache growth.
Verification and service integration
backend/grievance_service.py
Audit hashes can be recalculated and verified; follower caches are bounded, hash-only queries are used, and escalation methods forward database sessions.
Integrity and cache validation
backend/tests/test_escalation_blockchain.py
Tests cover deterministic chains, verification, tamper detection, cache isolation, and cache-miss fallback.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GrievanceService
  participant EscalationEngine
  participant AuditHashCache
  participant EscalationAudit
  GrievanceService->>EscalationEngine: escalate grievance
  EscalationEngine->>AuditHashCache: get previous audit hash
  AuditHashCache->>EscalationAudit: query latest hash on miss
  EscalationEngine->>EscalationAudit: save chained audit
  EscalationEngine->>AuditHashCache: cache new hash
  GrievanceService->>EscalationAudit: load audit for verification
  GrievanceService->>GrievanceService: recalculate and compare hash
Loading

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.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
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 Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a blockchain-style escalation audit trail with a bounded cache.
✨ 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-blockchain-escalation-18205682029061690933

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

🧹 Nitpick comments (4)
backend/escalation_engine.py (2)

22-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Annotate the shared cache with ClassVar.

_audit_last_hash_cache/_cache_lock are intentionally shared class-level state (tests reach them via EscalationEngine._audit_last_hash_cache), but Ruff's RUF012 flags plain mutable class attributes since this pattern is usually accidental. Annotating with typing.ClassVar documents the intent and silences the warning.

🧹 Proposed fix
+from typing import ClassVar, Dict, Optional
+
     # Cache for O(1) blockchain integrity hash lookups
     # Stores {grievance_id: last_integrity_hash}
-    _audit_last_hash_cache = {}
-    _cache_lock = threading.Lock()
+    _audit_last_hash_cache: ClassVar[Dict[int, str]] = {}
+    _cache_lock: ClassVar[threading.Lock] = threading.Lock()
🤖 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 `@backend/escalation_engine.py` around lines 22 - 26, Annotate the shared class
attributes _audit_last_hash_cache and _cache_lock in EscalationEngine with
typing.ClassVar, preserving their existing dictionary and lock initialization
and class-level access.

Source: Linters/SAST tools


280-284: 🚀 Performance & Scalability | 🔵 Trivial

Full-cache-wipe eviction defeats the O(1) cache purpose.

if len(cache) >= 1000: cache.clear() drops every cached hash once the cache fills, rather than evicting only the oldest/excess entry. Under sustained load this repeatedly forces DB fallback for all active grievances instead of just the one exceeding the bound. See consolidated comment for cross-file details and a proposed fix.

Also applies to: 311-316

🤖 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 `@backend/escalation_engine.py` around lines 280 - 284, Update the cache
eviction logic around _audit_last_hash_cache so reaching the 1000-entry limit
removes only the oldest or excess entry before storing the new grievance hash.
Preserve the cache’s maximum size and O(1) lookup behavior, and apply the same
change to the corresponding cache update at the other referenced location.
.jules/bolt.md (1)

28-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Doc says "eviction," implementation does full clear.

The learning describes bounding the cache "with eviction," but the actual implementation clears the entire cache once it reaches 1000 entries rather than evicting a single excess/oldest entry. See consolidated comment.

🤖 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 @.jules/bolt.md around lines 28 - 31, Update the cache-bounding
implementation described in the “Unbounded In-Memory Caches & Process Safety”
section to evict only the oldest or excess entry when the 1000-item limit is
reached, rather than clearing the entire cache. Keep the cache bounded at 1000
items and align the documented action with this eviction behavior.
backend/grievance_service.py (1)

172-176: 🚀 Performance & Scalability | 🔵 Trivial

Same full-cache-wipe eviction as escalation_engine.py.

_follower_last_hash_cache uses the identical "clear entire cache at 1000 entries" pattern. See consolidated comment for a cross-file fix.

Also applies to: 197-209

🤖 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 `@backend/grievance_service.py` around lines 172 - 176, Replace the full-cache
clear logic for _follower_last_hash_cache in both update paths with bounded
eviction that removes only the oldest entries needed to stay within the
1000-entry limit, then stores the new grievance_id hash. Keep the existing
_cache_lock protection and apply the same policy consistently at the referenced
second location.
🤖 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 `@backend/escalation_engine.py`:
- Around line 258-292: Serialize audit creation per grievance in the escalation
method containing `_get_last_audit_hash`, locking the grievance row with a
database `FOR UPDATE` transaction before reading `prev_hash` and committing
`EscalationAudit`. Ensure concurrent escalations for the same `grievance_id`
cannot share the same previous hash, while preserving the existing rollback and
cache-update behavior.

In `@backend/grievance_service.py`:
- Around line 245-276: Update verify_audit_integrity to handle EscalationAudit
records with a missing integrity_hash before comparing hashes. Return a
non-breach response indicating integrity data is unavailable, while preserving
the existing hash calculation and verification behavior for records with a
stored hash.

---

Nitpick comments:
In @.jules/bolt.md:
- Around line 28-31: Update the cache-bounding implementation described in the
“Unbounded In-Memory Caches & Process Safety” section to evict only the oldest
or excess entry when the 1000-item limit is reached, rather than clearing the
entire cache. Keep the cache bounded at 1000 items and align the documented
action with this eviction behavior.

In `@backend/escalation_engine.py`:
- Around line 22-26: Annotate the shared class attributes _audit_last_hash_cache
and _cache_lock in EscalationEngine with typing.ClassVar, preserving their
existing dictionary and lock initialization and class-level access.
- Around line 280-284: Update the cache eviction logic around
_audit_last_hash_cache so reaching the 1000-entry limit removes only the oldest
or excess entry before storing the new grievance hash. Preserve the cache’s
maximum size and O(1) lookup behavior, and apply the same change to the
corresponding cache update at the other referenced location.

In `@backend/grievance_service.py`:
- Around line 172-176: Replace the full-cache clear logic for
_follower_last_hash_cache in both update paths with bounded eviction that
removes only the oldest entries needed to stay within the 1000-entry limit, then
stores the new grievance_id hash. Keep the existing _cache_lock protection and
apply the same policy consistently at the referenced second location.
🪄 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: 3eb7a3ad-1046-421a-854a-c6bc40dcb146

📥 Commits

Reviewing files that changed from the base of the PR and between 3a74e60 and cc955d8.

📒 Files selected for processing (6)
  • .jules/bolt.md
  • backend/escalation_engine.py
  • backend/grievance_service.py
  • backend/models.py
  • backend/schemas.py
  • backend/tests/test_escalation_blockchain.py

Comment on lines +258 to 292
# Retrieve previous hash (O(1) from cache or O(log N) from indexed DB)
prev_hash = self._get_last_audit_hash(grievance.id, db)

# Calculate new integrity hash: SHA256(grievance_id | reason.value | prev_hash)
reason_val = reason.value if hasattr(reason, "value") else str(reason)
hash_input = f"{grievance.id}|{reason_val}|{prev_hash or 'GENESIS'}"
new_hash = hashlib.sha256(hash_input.encode()).hexdigest()

# Create audit log with blockchain integration
audit_log = EscalationAudit(
grievance_id=grievance.id,
previous_authority=previous_authority,
new_authority=grievance.assigned_authority,
reason=reason,
notes=notes
notes=notes,
integrity_hash=new_hash,
previous_integrity_hash=prev_hash
)

db.add(audit_log)
db.commit()

# Update O(1) cache for next escalation audit (with max size limit to prevent memory leak)
with self._cache_lock:
if len(self._audit_last_hash_cache) >= 1000:
self._audit_last_hash_cache.clear()
self._audit_last_hash_cache[grievance.id] = new_hash

return True

except Exception as e:
db.rollback()
print(f"Error during escalation: {e}")
return False

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 | 🏗️ Heavy lift

Hash chain can fork under concurrent escalations of the same grievance.

prev_hash is read (cache or DB) and the new EscalationAudit row is committed with no exclusivity spanning that read-then-write. Two concurrent calls (e.g., two simultaneous manual_escalate/escalate_grievance_severity requests) for the same grievance_id can both observe the same prev_hash and each commit a distinct audit row referencing it as previous_integrity_hash. Since verify_audit_integrity (backend/grievance_service.py) validates each record independently against its own previous_integrity_hash, both forked rows would pass verification individually — defeating the tamper-evidence guarantee this feature is meant to provide.

Consider serializing escalation per grievance (e.g., SELECT ... FOR UPDATE on the grievance row, or a DB-level unique constraint on (grievance_id, previous_integrity_hash) with retry-on-conflict) to make the chain write atomic with the read of the previous hash.

🧰 Tools
🪛 Ruff (0.15.21)

[warning] 288-288: Do not catch blind exception: Exception

(BLE001)

🤖 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 `@backend/escalation_engine.py` around lines 258 - 292, Serialize audit
creation per grievance in the escalation method containing
`_get_last_audit_hash`, locking the grievance row with a database `FOR UPDATE`
transaction before reading `prev_hash` and committing `EscalationAudit`. Ensure
concurrent escalations for the same `grievance_id` cannot share the same
previous hash, while preserving the existing rollback and cache-update behavior.

Comment on lines +245 to +276
def verify_audit_integrity(self, audit_id: int, db: Session = None) -> Dict[str, Any]:
"""
Verify the blockchain-style integrity of an escalation audit record.
"""
is_local_session = False
if db is None:
db = SessionLocal()
is_local_session = True

try:
audit = db.query(EscalationAudit).filter(EscalationAudit.id == audit_id).first()
if not audit:
return {"is_valid": False, "message": "Audit record not found"}

# Re-calculate hash: SHA256(grievance_id | reason.value | prev_hash)
reason_val = audit.reason.value if hasattr(audit.reason, "value") else str(audit.reason)
hash_input = f"{audit.grievance_id}|{reason_val}|{audit.previous_integrity_hash or 'GENESIS'}"
calculated_hash = hashlib.sha256(hash_input.encode()).hexdigest()

is_valid = (calculated_hash == audit.integrity_hash)

return {
"is_valid": is_valid,
"current_hash": audit.integrity_hash,
"calculated_hash": calculated_hash,
"previous_hash": audit.previous_integrity_hash,
"message": "Integrity verified" if is_valid else "INTEGRITY BREACH DETECTED"
}
finally:
if is_local_session:
db.close()

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

Legacy audit records with no stored hash are reported as "INTEGRITY BREACH DETECTED."

EscalationAudit.integrity_hash is nullable (backend/models.py), so any audit row predating this feature (or created outside _escalate_grievance) has integrity_hash = None. Here, calculated_hash (always a real SHA-256 digest) can never equal None, so is_valid becomes False and the response claims a breach — misleadingly implying tampering rather than "no integrity data available."

🛡️ Proposed fix
             audit = db.query(EscalationAudit).filter(EscalationAudit.id == audit_id).first()
             if not audit:
                 return {"is_valid": False, "message": "Audit record not found"}
 
+            if audit.integrity_hash is None:
+                return {"is_valid": False, "message": "No integrity data available for this record"}
+
             # Re-calculate hash: SHA256(grievance_id | reason.value | prev_hash)
📝 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
def verify_audit_integrity(self, audit_id: int, db: Session = None) -> Dict[str, Any]:
"""
Verify the blockchain-style integrity of an escalation audit record.
"""
is_local_session = False
if db is None:
db = SessionLocal()
is_local_session = True
try:
audit = db.query(EscalationAudit).filter(EscalationAudit.id == audit_id).first()
if not audit:
return {"is_valid": False, "message": "Audit record not found"}
# Re-calculate hash: SHA256(grievance_id | reason.value | prev_hash)
reason_val = audit.reason.value if hasattr(audit.reason, "value") else str(audit.reason)
hash_input = f"{audit.grievance_id}|{reason_val}|{audit.previous_integrity_hash or 'GENESIS'}"
calculated_hash = hashlib.sha256(hash_input.encode()).hexdigest()
is_valid = (calculated_hash == audit.integrity_hash)
return {
"is_valid": is_valid,
"current_hash": audit.integrity_hash,
"calculated_hash": calculated_hash,
"previous_hash": audit.previous_integrity_hash,
"message": "Integrity verified" if is_valid else "INTEGRITY BREACH DETECTED"
}
finally:
if is_local_session:
db.close()
def verify_audit_integrity(self, audit_id: int, db: Session = None) -> Dict[str, Any]:
"""
Verify the blockchain-style integrity of an escalation audit record.
"""
is_local_session = False
if db is None:
db = SessionLocal()
is_local_session = True
try:
audit = db.query(EscalationAudit).filter(EscalationAudit.id == audit_id).first()
if not audit:
return {"is_valid": False, "message": "Audit record not found"}
if audit.integrity_hash is None:
return {"is_valid": False, "message": "No integrity data available for this record"}
# Re-calculate hash: SHA256(grievance_id | reason.value | prev_hash)
reason_val = audit.reason.value if hasattr(audit.reason, "value") else str(audit.reason)
hash_input = f"{audit.grievance_id}|{reason_val}|{audit.previous_integrity_hash or 'GENESIS'}"
calculated_hash = hashlib.sha256(hash_input.encode()).hexdigest()
is_valid = (calculated_hash == audit.integrity_hash)
return {
"is_valid": is_valid,
"current_hash": audit.integrity_hash,
"calculated_hash": calculated_hash,
"previous_hash": audit.previous_integrity_hash,
"message": "Integrity verified" if is_valid else "INTEGRITY BREACH DETECTED"
}
finally:
if is_local_session:
db.close()
🤖 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 `@backend/grievance_service.py` around lines 245 - 276, Update
verify_audit_integrity to handle EscalationAudit records with a missing
integrity_hash before comparing hashes. Return a non-breach response indicating
integrity data is unavailable, while preserving the existing hash calculation
and verification behavior for records with a stored hash.

…h correct main_fixed.py, and implement O(1) cached blockchain escalation audit trails

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

11 issues found and verified against the latest diff

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/schemas.py">

<violation number="1" location="backend/schemas.py:209">
P2: Audit consumers can receive `null` for the record hash and cannot verify that record, while `previous_integrity_hash` alone is legitimately nullable for genesis. Make `integrity_hash` required so a missing hash fails response validation instead of silently weakening the audit trail.</violation>
</file>

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

<violation number="1" location="backend/models.py:109">
P1: Existing deployments retain the old `escalation_audits` schema, so loading or writing audit records will fail with a missing `integrity_hash`/`previous_integrity_hash` column error. Include a database migration (and an upgrade path for existing rows) with these model fields.</violation>
</file>

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

<violation number="1" location="backend/grievance_service.py:262">
P1: A modified audit trail can be forged by recomputing this unkeyed SHA-256 hash; `verify_audit_integrity` has no secret or immutable external anchor to distinguish forged hashes. Authenticate entries with a protected HMAC/signature and retain its key outside the audit database (or anchor chain heads externally).</violation>

<violation number="2" location="backend/grievance_service.py:264">
P1: When `audit.integrity_hash` is `None` (e.g., for audit rows created before this feature was deployed, or outside `_escalate_grievance`), the comparison `calculated_hash == audit.integrity_hash` will always be `False` since `calculated_hash` is a real SHA-256 digest. This causes the function to return `"INTEGRITY BREACH DETECTED"`, misleadingly implying tampering rather than simply indicating no integrity data is available.

Consider adding an early return when `audit.integrity_hash is None` with a distinct message like `"No integrity data available for this record"`.</violation>

<violation number="3" location="backend/grievance_service.py:357">
P2: Passing a caller-owned session here causes the engine to close it before this method returns, breaking the service’s established `db` ownership contract and any surrounding unit-of-work. Track whether the engine created the session, then close only locally created sessions.</violation>
</file>

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

<violation number="1" location="backend/escalation_engine.py:259">
P1: Concurrent escalations can fork the chain because both transactions may calculate from the same cached predecessor before either commits. Serialize append operations per grievance with a database transaction/row or advisory lock, and read the latest persisted hash under that lock.</violation>

<violation number="2" location="backend/escalation_engine.py:263">
P1: Changes to authorities, notes, or timestamp remain reported as "Integrity verified" because none are committed by this digest. Hash a canonical representation of every protected audit field and use the same representation during verification.</violation>

<violation number="3" location="backend/escalation_engine.py:274">
P1: Removing an audit record is not detected: verification never resolves `previous_integrity_hash` to an actual predecessor. Traverse and validate the predecessor chain (including genesis and ordering) when reporting audit integrity.</violation>

<violation number="4" location="backend/escalation_engine.py:281">
P1: The lock is released after the cache-miss check but re-acquired later for the cache-update, leaving an unlocked window where a concurrent escalation for the same grievance can create a new audit record and update the cache. The first thread then writes stale data into the cache, which the caller unconditionally overwrites with its own new hash — potentially overwriting a newer hash written by the concurrent thread. This can permanently break the hash chain, because the final cache entry after both threads finish may point to an audit that is not the most recent one. A subsequent escalation will then link against the wrong previous hash, producing a chain link that does not match what the DB actually contains. Fix: hold the lock across the full lookup-and-store, or re-check the cache under lock after the DB query before writing.</violation>

<violation number="5" location="backend/escalation_engine.py:283">
P2: Evicting the entire dictionary with `dict.clear()` when the cache hits 1000 items causes a severe performance burst: every single grievance that had a cached hash now misses, triggering up to 1000 DB queries in quick succession. This directly contradicts the "blazing-fast O(1)" promise — the escalations immediately after a clear pay O(log N) DB cost. Consider an LRU eviction (pop the oldest 200 entries, or use `cachetools.LRUCache`/`OrderedDict.popitem(last=False)`) to maintain steady cache-hit ratios.</violation>

<violation number="6" location="backend/escalation_engine.py:305">
P2: Cache misses will degrade as audit history grows because this lookup has no index for its filter/order pattern. Add and migrate a composite `(grievance_id, id)` index for the fallback query.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread backend/models.py
notes = Column(Text, nullable=True) # Additional context

# Blockchain-style integrity fields
integrity_hash = Column(String, nullable=True, index=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: Existing deployments retain the old escalation_audits schema, so loading or writing audit records will fail with a missing integrity_hash/previous_integrity_hash column error. Include a database migration (and an upgrade path for existing rows) with these model fields.

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

<comment>Existing deployments retain the old `escalation_audits` schema, so loading or writing audit records will fail with a missing `integrity_hash`/`previous_integrity_hash` column error. Include a database migration (and an upgrade path for existing rows) with these model fields.</comment>

<file context>
@@ -105,6 +105,10 @@ class EscalationAudit(Base):
     notes = Column(Text, nullable=True)  # Additional context
 
+    # Blockchain-style integrity fields
+    integrity_hash = Column(String, nullable=True, index=True)
+    previous_integrity_hash = Column(String, nullable=True)
+
</file context>

# Re-calculate hash: SHA256(grievance_id | reason.value | prev_hash)
reason_val = audit.reason.value if hasattr(audit.reason, "value") else str(audit.reason)
hash_input = f"{audit.grievance_id}|{reason_val}|{audit.previous_integrity_hash or 'GENESIS'}"
calculated_hash = hashlib.sha256(hash_input.encode()).hexdigest()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: A modified audit trail can be forged by recomputing this unkeyed SHA-256 hash; verify_audit_integrity has no secret or immutable external anchor to distinguish forged hashes. Authenticate entries with a protected HMAC/signature and retain its key outside the audit database (or anchor chain heads externally).

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

<comment>A modified audit trail can be forged by recomputing this unkeyed SHA-256 hash; `verify_audit_integrity` has no secret or immutable external anchor to distinguish forged hashes. Authenticate entries with a protected HMAC/signature and retain its key outside the audit database (or anchor chain heads externally).</comment>

<file context>
@@ -238,6 +242,38 @@ def verify_follower_integrity(self, follower_id: int, db: Session = None) -> Dic
+            # Re-calculate hash: SHA256(grievance_id | reason.value | prev_hash)
+            reason_val = audit.reason.value if hasattr(audit.reason, "value") else str(audit.reason)
+            hash_input = f"{audit.grievance_id}|{reason_val}|{audit.previous_integrity_hash or 'GENESIS'}"
+            calculated_hash = hashlib.sha256(hash_input.encode()).hexdigest()
+
+            is_valid = (calculated_hash == audit.integrity_hash)
</file context>

notes=notes
notes=notes,
integrity_hash=new_hash,
previous_integrity_hash=prev_hash

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: Removing an audit record is not detected: verification never resolves previous_integrity_hash to an actual predecessor. Traverse and validate the predecessor chain (including genesis and ordering) when reporting audit integrity.

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

<comment>Removing an audit record is not detected: verification never resolves `previous_integrity_hash` to an actual predecessor. Traverse and validate the predecessor chain (including genesis and ordering) when reporting audit integrity.</comment>

<file context>
@@ -248,25 +255,68 @@ def _escalate_grievance(self, grievance: Grievance, reason: EscalationReason,
-                notes=notes
+                notes=notes,
+                integrity_hash=new_hash,
+                previous_integrity_hash=prev_hash
             )
 
</file context>


# Create audit log
# Retrieve previous hash (O(1) from cache or O(log N) from indexed DB)
prev_hash = self._get_last_audit_hash(grievance.id, db)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: Concurrent escalations can fork the chain because both transactions may calculate from the same cached predecessor before either commits. Serialize append operations per grievance with a database transaction/row or advisory lock, and read the latest persisted hash under that lock.

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

<comment>Concurrent escalations can fork the chain because both transactions may calculate from the same cached predecessor before either commits. Serialize append operations per grievance with a database transaction/row or advisory lock, and read the latest persisted hash under that lock.</comment>

<file context>
@@ -248,25 +255,68 @@ def _escalate_grievance(self, grievance: Grievance, reason: EscalationReason,
 
-            # Create audit log
+            # Retrieve previous hash (O(1) from cache or O(log N) from indexed DB)
+            prev_hash = self._get_last_audit_hash(grievance.id, db)
+
+            # Calculate new integrity hash: SHA256(grievance_id | reason.value | prev_hash)
</file context>


# Calculate new integrity hash: SHA256(grievance_id | reason.value | prev_hash)
reason_val = reason.value if hasattr(reason, "value") else str(reason)
hash_input = f"{grievance.id}|{reason_val}|{prev_hash or 'GENESIS'}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: Changes to authorities, notes, or timestamp remain reported as "Integrity verified" because none are committed by this digest. Hash a canonical representation of every protected audit field and use the same representation during verification.

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

<comment>Changes to authorities, notes, or timestamp remain reported as "Integrity verified" because none are committed by this digest. Hash a canonical representation of every protected audit field and use the same representation during verification.</comment>

<file context>
@@ -248,25 +255,68 @@ def _escalate_grievance(self, grievance: Grievance, reason: EscalationReason,
+
+            # Calculate new integrity hash: SHA256(grievance_id | reason.value | prev_hash)
+            reason_val = reason.value if hasattr(reason, "value") else str(reason)
+            hash_input = f"{grievance.id}|{reason_val}|{prev_hash or 'GENESIS'}"
+            new_hash = hashlib.sha256(hash_input.encode()).hexdigest()
+
</file context>

db.commit()

# Update O(1) cache for next escalation audit (with max size limit to prevent memory leak)
with self._cache_lock:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: The lock is released after the cache-miss check but re-acquired later for the cache-update, leaving an unlocked window where a concurrent escalation for the same grievance can create a new audit record and update the cache. The first thread then writes stale data into the cache, which the caller unconditionally overwrites with its own new hash — potentially overwriting a newer hash written by the concurrent thread. This can permanently break the hash chain, because the final cache entry after both threads finish may point to an audit that is not the most recent one. A subsequent escalation will then link against the wrong previous hash, producing a chain link that does not match what the DB actually contains. Fix: hold the lock across the full lookup-and-store, or re-check the cache under lock after the DB query before writing.

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

<comment>The lock is released after the cache-miss check but re-acquired later for the cache-update, leaving an unlocked window where a concurrent escalation for the same grievance can create a new audit record and update the cache. The first thread then writes stale data into the cache, which the caller unconditionally overwrites with its own new hash — potentially overwriting a newer hash written by the concurrent thread. This can permanently break the hash chain, because the final cache entry after both threads finish may point to an audit that is not the most recent one. A subsequent escalation will then link against the wrong previous hash, producing a chain link that does not match what the DB actually contains. Fix: hold the lock across the full lookup-and-store, or re-check the cache under lock after the DB query before writing.</comment>

<file context>
@@ -248,25 +255,68 @@ def _escalate_grievance(self, grievance: Grievance, reason: EscalationReason,
             db.commit()
 
+            # Update O(1) cache for next escalation audit (with max size limit to prevent memory leak)
+            with self._cache_lock:
+                if len(self._audit_last_hash_cache) >= 1000:
+                    self._audit_last_hash_cache.clear()
</file context>

Comment thread backend/schemas.py
new_authority: str = Field(..., description="New authority after escalation")
timestamp: datetime = Field(..., description="When the escalation occurred")
reason: str = Field(..., description="Reason for escalation (SLA_BREACH, SEVERITY_UPGRADE, MANUAL)")
integrity_hash: Optional[str] = Field(None, description="Cryptographic integrity hash")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Audit consumers can receive null for the record hash and cannot verify that record, while previous_integrity_hash alone is legitimately nullable for genesis. Make integrity_hash required so a missing hash fails response validation instead of silently weakening the audit trail.

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

<comment>Audit consumers can receive `null` for the record hash and cannot verify that record, while `previous_integrity_hash` alone is legitimately nullable for genesis. Make `integrity_hash` required so a missing hash fails response validation instead of silently weakening the audit trail.</comment>

<file context>
@@ -206,6 +206,8 @@ class EscalationAuditResponse(BaseModel):
     new_authority: str = Field(..., description="New authority after escalation")
     timestamp: datetime = Field(..., description="When the escalation occurred")
     reason: str = Field(..., description="Reason for escalation (SLA_BREACH, SEVERITY_UPGRADE, MANUAL)")
+    integrity_hash: Optional[str] = Field(None, description="Cryptographic integrity hash")
+    previous_integrity_hash: Optional[str] = Field(None, description="Hash of the previous escalation audit record")
 
</file context>
Suggested change
integrity_hash: Optional[str] = Field(None, description="Cryptographic integrity hash")
integrity_hash: str = Field(..., description="Cryptographic integrity hash")

True if escalation successful
"""
return self.escalation_engine.escalate_grievance_severity(grievance_id, new_severity, reason)
return self.escalation_engine.escalate_grievance_severity(grievance_id, new_severity, reason, db)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Passing a caller-owned session here causes the engine to close it before this method returns, breaking the service’s established db ownership contract and any surrounding unit-of-work. Track whether the engine created the session, then close only locally created sessions.

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

<comment>Passing a caller-owned session here causes the engine to close it before this method returns, breaking the service’s established `db` ownership contract and any surrounding unit-of-work. Track whether the engine created the session, then close only locally created sessions.</comment>

<file context>
@@ -305,32 +341,34 @@ def update_grievance_status(self, grievance_id: int, status: GrievanceStatus,
             True if escalation successful
         """
-        return self.escalation_engine.escalate_grievance_severity(grievance_id, new_severity, reason)
+        return self.escalation_engine.escalate_grievance_severity(grievance_id, new_severity, reason, db)
 
-    def manual_escalate(self, grievance_id: int, reason: str = "") -> bool:
</file context>

# Cache miss: Fallback to indexed DB query using optimized single-column selection
from sqlalchemy import desc
last_audit_hash = db.query(EscalationAudit.integrity_hash)\
.filter(EscalationAudit.grievance_id == grievance_id)\

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Cache misses will degrade as audit history grows because this lookup has no index for its filter/order pattern. Add and migrate a composite (grievance_id, id) index for the fallback query.

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

<comment>Cache misses will degrade as audit history grows because this lookup has no index for its filter/order pattern. Add and migrate a composite `(grievance_id, id)` index for the fallback query.</comment>

<file context>
@@ -248,25 +255,68 @@ def _escalate_grievance(self, grievance: Grievance, reason: EscalationReason,
+        # Cache miss: Fallback to indexed DB query using optimized single-column selection
+        from sqlalchemy import desc
+        last_audit_hash = db.query(EscalationAudit.integrity_hash)\
+            .filter(EscalationAudit.grievance_id == grievance_id)\
+            .order_by(desc(EscalationAudit.id))\
+            .first()
</file context>

# Update O(1) cache for next escalation audit (with max size limit to prevent memory leak)
with self._cache_lock:
if len(self._audit_last_hash_cache) >= 1000:
self._audit_last_hash_cache.clear()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Evicting the entire dictionary with dict.clear() when the cache hits 1000 items causes a severe performance burst: every single grievance that had a cached hash now misses, triggering up to 1000 DB queries in quick succession. This directly contradicts the "blazing-fast O(1)" promise — the escalations immediately after a clear pay O(log N) DB cost. Consider an LRU eviction (pop the oldest 200 entries, or use cachetools.LRUCache/OrderedDict.popitem(last=False)) to maintain steady cache-hit ratios.

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

<comment>Evicting the entire dictionary with `dict.clear()` when the cache hits 1000 items causes a severe performance burst: every single grievance that had a cached hash now misses, triggering up to 1000 DB queries in quick succession. This directly contradicts the "blazing-fast O(1)" promise — the escalations immediately after a clear pay O(log N) DB cost. Consider an LRU eviction (pop the oldest 200 entries, or use `cachetools.LRUCache`/`OrderedDict.popitem(last=False)`) to maintain steady cache-hit ratios.</comment>

<file context>
@@ -248,25 +255,68 @@ def _escalate_grievance(self, grievance: Grievance, reason: EscalationReason,
+            # Update O(1) cache for next escalation audit (with max size limit to prevent memory leak)
+            with self._cache_lock:
+                if len(self._audit_last_hash_cache) >= 1000:
+                    self._audit_last_hash_cache.clear()
+                self._audit_last_hash_cache[grievance.id] = new_hash
+
</file context>

@github-actions

Copy link
Copy Markdown

🔍 Quality Reminder

Thanks for the updates! Please ensure:
- Your changes don't break existing functionality
- All tests still pass
- Code quality standards are maintained

*The maintainers will verify that the overall project flow remains intact.*

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

6 issues found across 3 files (changes from recent commits).

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/main.py">

<violation number="1" location="backend/main.py:24">
P1: Documented backend startup paths now fail during import because package-qualified and top-level module imports are mixed. Standardize imports on the `backend` package and use one root-based Uvicorn command.</violation>

<violation number="2" location="backend/main.py:150">
P1: New issue action plans never persist because a dict is assigned to the TEXT-mapped `Issue.action_plan`. Serialize/deserialise it consistently or map the column with `JSONEncodedDict`.</violation>

<violation number="3" location="backend/main.py:163">
P1: Existing databases cannot persist the new audit hashes: this startup migration never adds the two `escalation_audits` columns. Add idempotent column and index migrations before enabling hash-backed escalations.</violation>

<violation number="4" location="backend/main.py:564">
P1: Concurrent follows can fork the claimed hash chain, so individual record verification still passes while ordering integrity is lost. Serialize the read/hash/insert sequence per grievance or enforce it with database transaction locking.</violation>

<violation number="5" location="backend/main.py:583">
P2: Verifying a nonexistent follower returns 500 instead of a usable not-found response because `result` does not satisfy `BlockchainVerificationResponse`. Translate the service's missing-record result to 404 before response-model validation.</violation>

<violation number="6" location="backend/main.py:746">
P1: External detector uploads bypass the image size and MIME gate, then are fully read and base64-encoded for the HF request. Validate before reading in every endpoint in this block to prevent oversized or non-image uploads exhausting worker memory.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread backend/main.py
@app.post("/api/detect-illegal-parking")
async def detect_illegal_parking_endpoint(request: Request, image: UploadFile = File(...)):
try:
image_bytes = await image.read()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: External detector uploads bypass the image size and MIME gate, then are fully read and base64-encoded for the HF request. Validate before reading in every endpoint in this block to prevent oversized or non-image uploads exhausting worker memory.

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

<comment>External detector uploads bypass the image size and MIME gate, then are fully read and base64-encoded for the HF request. Validate before reading in every endpoint in this block to prevent oversized or non-image uploads exhausting worker memory.</comment>

<file context>
@@ -196,185 +304,633 @@ async def ml_status():
+@app.post("/api/detect-illegal-parking")
+async def detect_illegal_parking_endpoint(request: Request, image: UploadFile = File(...)):
+    try:
+        image_bytes = await image.read()
+    except Exception as e:
+        logger.error(f"Invalid image file: {e}", exc_info=True)
</file context>

Comment thread backend/main.py
# --- Startup ---
print("Starting up backend...")
# Startup: Migrate DB
migrate_db()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: Existing databases cannot persist the new audit hashes: this startup migration never adds the two escalation_audits columns. Add idempotent column and index migrations before enabling hash-backed escalations.

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

<comment>Existing databases cannot persist the new audit hashes: this startup migration never adds the two `escalation_audits` columns. Add idempotent column and index migrations before enabling hash-backed escalations.</comment>

<file context>
@@ -1,137 +1,272 @@
-    # --- Startup ---
-    print("Starting up backend...")
+    # Startup: Migrate DB
+    migrate_db()
 
-    # Initialize the Telegram bot
</file context>

Comment thread backend/main.py
import magic
import httpx

from backend.cache import recent_issues_cache

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: Documented backend startup paths now fail during import because package-qualified and top-level module imports are mixed. Standardize imports on the backend package and use one root-based Uvicorn command.

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

<comment>Documented backend startup paths now fail during import because package-qualified and top-level module imports are mixed. Standardize imports on the `backend` package and use one root-based Uvicorn command.</comment>

<file context>
@@ -1,137 +1,272 @@
+import magic
+import httpx
+
+from backend.cache import recent_issues_cache
+from backend.database import engine, Base, SessionLocal, get_db
+from backend.models import Issue
</file context>

Comment thread backend/main.py
Bolt Optimization: Uses O(1) in-memory cache for hash chaining.
"""
follower = await run_in_threadpool(
grievance_service.follow_grievance,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: Concurrent follows can fork the claimed hash chain, so individual record verification still passes while ordering integrity is lost. Serialize the read/hash/insert sequence per grievance or enforce it with database transaction locking.

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

<comment>Concurrent follows can fork the claimed hash chain, so individual record verification still passes while ordering integrity is lost. Serialize the read/hash/insert sequence per grievance or enforce it with database transaction locking.</comment>

<file context>
@@ -196,185 +304,633 @@ async def ml_status():
+    Bolt Optimization: Uses O(1) in-memory cache for hash chaining.
+    """
+    follower = await run_in_threadpool(
+        grievance_service.follow_grievance,
+        grievance_id,
+        request.user_email,
</file context>

Comment thread backend/main.py
# Update issue in DB
issue = db.query(Issue).filter(Issue.id == issue_id).first()
if issue:
issue.action_plan = action_plan

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: New issue action plans never persist because a dict is assigned to the TEXT-mapped Issue.action_plan. Serialize/deserialise it consistently or map the column with JSONEncodedDict.

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

<comment>New issue action plans never persist because a dict is assigned to the TEXT-mapped `Issue.action_plan`. Serialize/deserialise it consistently or map the column with `JSONEncodedDict`.</comment>

<file context>
@@ -1,137 +1,272 @@
+        # Update issue in DB
+        issue = db.query(Issue).filter(Issue.id == issue_id).first()
+        if issue:
+            issue.action_plan = action_plan
+            db.commit()
+
</file context>

Comment thread backend/main.py
follower_id,
db
)
return result

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Verifying a nonexistent follower returns 500 instead of a usable not-found response because result does not satisfy BlockchainVerificationResponse. Translate the service's missing-record result to 404 before response-model validation.

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

<comment>Verifying a nonexistent follower returns 500 instead of a usable not-found response because `result` does not satisfy `BlockchainVerificationResponse`. Translate the service's missing-record result to 404 before response-model validation.</comment>

<file context>
@@ -196,185 +304,633 @@ async def ml_status():
+        follower_id,
+        db
+    )
+    return result
+
+@app.get("/api/issues/recent", response_model=List[IssueResponse])
</file context>

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