Skip to content

⚡ Bolt: Escalation Blockchain Integrity & N+1 Optimization#908

Open
RohanExploit wants to merge 4 commits into
mainfrom
bolt-escalation-blockchain-opt-v1-3725348573393189050
Open

⚡ Bolt: Escalation Blockchain Integrity & N+1 Optimization#908
RohanExploit wants to merge 4 commits into
mainfrom
bolt-escalation-blockchain-opt-v1-3725348573393189050

Conversation

@RohanExploit

@RohanExploit RohanExploit commented Jul 14, 2026

Copy link
Copy Markdown
Owner

💡 What: Implemented a blockchain-style cryptographic integrity system for grievance escalation audits, combined with a major database query optimization.

🎯 Why:

  1. The user requested "implement blockchain" for transparency in grievance handling.
  2. The periodic escalation evaluation had a classic N+1 query bottleneck, where loading $N$ grievances would trigger $N$ additional queries to fetch their jurisdictions, significantly slowing down the process as the database grows.

📊 Impact:

  • N+1 Fix: Reduces database queries for escalation evaluation from $O(N+1)$ to $O(1)$ JOIN query.
  • Blockchain Optimization: Achieves $O(1)$ hash lookup for chaining using a thread-safe in-memory cache, avoiding expensive database scans for the previous hash record.

🔬 Measurement:

  • Verified correct hash chaining and link integrity with a dedicated test script (tests/verify_audit_blockchain.py).
  • Confirmed that the verification endpoint correctly identifies valid/invalid records.
  • Baseline escalation functionality remains intact as verified by backend/test_grievance_escalation.py.
  • No regressions in frontend or Node.js test suites.

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


Summary by cubic

Adds blockchain-style integrity to escalation audits, removes the N+1 query in escalation checks, and restores backend deployment by fixing syntax and entrypoint issues.

  • New Features

    • SHA256 hash chaining for EscalationAudit via integrity_hash and previous_integrity_hash.
    • GET /api/escalation/{audit_id}/blockchain-verify to validate audit integrity.
  • Performance & Fixes

    • Removed N+1 in escalation evaluation using joinedload(Grievance.jurisdiction) and added a thread-safe O(1) last-hash cache with DB fallback.
    • Fixed deploy failures: corrected SyntaxErrors in backend/main.py and backend/hf_service.py, consolidated the app into backend/main.py, and updated start-backend.py to use backend.main:app.

Written for commit d4cc866. Summary will update on new commits.

Review in cubic

…ttleneck in escalation engine

- Implemented blockchain-style SHA256 hash chaining for EscalationAudit records.
- Optimized hash lookup with a thread-safe O(1) in-memory cache, reducing DB round-trips.
- Fixed N+1 query bottleneck in periodic escalation evaluation using joinedload(Grievance.jurisdiction).
- Added /api/escalation/{audit_id}/blockchain-verify endpoint for integrity verification.
- Updated models, schemas, and service logic with performance-first patterns.
Copilot AI review requested due to automatic review settings July 14, 2026 14:06
@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 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@RohanExploit, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: adcbaa26-faa6-430c-99b0-07c1863a1634

📥 Commits

Reviewing files that changed from the base of the PR and between 39ffc17 and d4cc866.

📒 Files selected for processing (9)
  • .jules/bolt.md
  • backend/escalation_engine.py
  • backend/grievance_service.py
  • backend/hf_service.py
  • backend/main.py
  • backend/main_fixed.py
  • backend/models.py
  • backend/schemas.py
  • start-backend.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bolt-escalation-blockchain-opt-v1-3725348573393189050

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.

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

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

<violation number="1" location="backend/escalation_engine.py:24">
P2: The class-level cache retains one hash for every escalated grievance forever, so memory grows with the lifetime total rather than the active working set. Use a bounded or TTL cache, or provide explicit invalidation.</violation>

<violation number="2" location="backend/escalation_engine.py:263">
P1: Concurrent escalations for one grievance can create sibling audit records with the same `previous_integrity_hash`, breaking the single ordered audit chain. Serialize hash selection through audit insert/commit with database-level coordination, then update the cache after the transaction succeeds.</violation>

<violation number="3" location="backend/escalation_engine.py:305">
P2: Cold-cache lookups can scan `escalation_audits` because `EscalationAudit.grievance_id` is not indexed, so the fallback does not scale as claimed. Add a composite `(grievance_id, id)` index for this filter-and-order query.</violation>

<violation number="4" location="backend/escalation_engine.py:309">
P2: A grievance whose newest audit has a NULL `integrity_hash` starts a new GENESIS chain even though an audit already exists, leaving the new record disconnected from its history. Backfill or explicitly handle legacy unhashed rows before creating a chained audit.</violation>
</file>

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

<violation number="1" location="backend/grievance_service.py:322">
P2: Callers using the documented `service.manual_escalate(..., reason=...)` form now fail with `TypeError: unexpected keyword argument 'reason'` before escalation runs, because this public service parameter was renamed without a compatibility alias. Preserving `reason` or accepting both names would keep existing callers functional.</violation>
</file>

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

<violation number="1" location="backend/models.py:109">
P2: New audit records can be stored with a NULL `integrity_hash`, so the database does not enforce that every audit is part of a verifiable chain. Making this column non-null after backfilling legacy rows would align it with the follower integrity field and preserve the audit invariant.</violation>

<violation number="2" location="backend/models.py:109">
P1: Existing deployments will not receive this new column: `create_all()` does not migrate an already-created `escalation_audits` table, and the startup migration has no `escalation_audits` steps. A migration adding both integrity columns and the `integrity_hash` index is needed before the new audit query/insert paths run against persisted databases.</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 will not receive this new column: create_all() does not migrate an already-created escalation_audits table, and the startup migration has no escalation_audits steps. A migration adding both integrity columns and the integrity_hash index is needed before the new audit query/insert paths run against persisted databases.

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 will not receive this new column: `create_all()` does not migrate an already-created `escalation_audits` table, and the startup migration has no `escalation_audits` steps. A migration adding both integrity columns and the `integrity_hash` index is needed before the new audit query/insert paths run against persisted databases.</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>

Comment thread backend/main_fixed.py Outdated
Comment thread backend/main_fixed.py Outdated

# Blockchain-style integrity hashing
# Get 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 for one grievance can create sibling audit records with the same previous_integrity_hash, breaking the single ordered audit chain. Serialize hash selection through audit insert/commit with database-level coordination, then update the cache after the transaction succeeds.

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>Concurrent escalations for one grievance can create sibling audit records with the same `previous_integrity_hash`, breaking the single ordered audit chain. Serialize hash selection through audit insert/commit with database-level coordination, then update the cache after the transaction succeeds.</comment>

<file context>
@@ -248,25 +258,63 @@ def _escalate_grievance(self, grievance: Grievance, reason: EscalationReason,
 
+            # Blockchain-style integrity hashing
+            # Get 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 | prev_hash)
</file context>

return self.escalation_engine.escalate_grievance_severity(grievance_id, new_severity, reason)

def manual_escalate(self, grievance_id: int, reason: str = "") -> bool:
def manual_escalate(self, grievance_id: int, notes: str = "") -> bool:

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: Callers using the documented service.manual_escalate(..., reason=...) form now fail with TypeError: unexpected keyword argument 'reason' before escalation runs, because this public service parameter was renamed without a compatibility alias. Preserving reason or accepting both names would keep existing callers functional.

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

<comment>Callers using the documented `service.manual_escalate(..., reason=...)` form now fail with `TypeError: unexpected keyword argument 'reason'` before escalation runs, because this public service parameter was renamed without a compatibility alias. Preserving `reason` or accepting both names would keep existing callers functional.</comment>

<file context>
@@ -319,18 +319,18 @@ def escalate_grievance_severity(self, grievance_id: int, new_severity: SeverityL
         return self.escalation_engine.escalate_grievance_severity(grievance_id, new_severity, reason)
 
-    def manual_escalate(self, grievance_id: int, reason: str = "") -> bool:
+    def manual_escalate(self, grievance_id: int, notes: str = "") -> bool:
         """
         Manually escalate a grievance.
</file context>

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.

P2: New audit records can be stored with a NULL integrity_hash, so the database does not enforce that every audit is part of a verifiable chain. Making this column non-null after backfilling legacy rows would align it with the follower integrity field and preserve the audit invariant.

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>New audit records can be stored with a NULL `integrity_hash`, so the database does not enforce that every audit is part of a verifiable chain. Making this column non-null after backfilling legacy rows would align it with the follower integrity field and preserve the audit invariant.</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>


# Cache for O(1) blockchain integrity hash lookups
# Stores {grievance_id: last_audit_integrity_hash}
_audit_last_hash_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.

P2: The class-level cache retains one hash for every escalated grievance forever, so memory grows with the lifetime total rather than the active working set. Use a bounded or TTL cache, or provide explicit invalidation.

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

<comment>The class-level cache retains one hash for every escalated grievance forever, so memory grows with the lifetime total rather than the active working set. Use a bounded or TTL cache, or provide explicit invalidation.</comment>

<file context>
@@ -17,6 +19,11 @@ class EscalationEngine:
 
+    # Cache for O(1) blockchain integrity hash lookups
+    # Stores {grievance_id: last_audit_integrity_hash}
+    _audit_last_hash_cache = {}
+    _cache_lock = threading.Lock()
+
</file context>


# Cache miss: Fallback to indexed DB query
last_audit = db.query(EscalationAudit)\
.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: Cold-cache lookups can scan escalation_audits because EscalationAudit.grievance_id is not indexed, so the fallback does not scale as claimed. Add a composite (grievance_id, id) index for this filter-and-order 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>Cold-cache lookups can scan `escalation_audits` because `EscalationAudit.grievance_id` is not indexed, so the fallback does not scale as claimed. Add a composite `(grievance_id, id)` index for this filter-and-order query.</comment>

<file context>
@@ -248,25 +258,63 @@ def _escalate_grievance(self, grievance: Grievance, reason: EscalationReason,
+
+        # Cache miss: Fallback to indexed DB query
+        last_audit = db.query(EscalationAudit)\
+            .filter(EscalationAudit.grievance_id == grievance_id)\
+            .order_by(desc(EscalationAudit.id))\
+            .first()
</file context>

.order_by(desc(EscalationAudit.id))\
.first()

last_hash = last_audit.integrity_hash if last_audit else None

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: A grievance whose newest audit has a NULL integrity_hash starts a new GENESIS chain even though an audit already exists, leaving the new record disconnected from its history. Backfill or explicitly handle legacy unhashed rows before creating a chained audit.

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

<comment>A grievance whose newest audit has a NULL `integrity_hash` starts a new GENESIS chain even though an audit already exists, leaving the new record disconnected from its history. Backfill or explicitly handle legacy unhashed rows before creating a chained audit.</comment>

<file context>
@@ -248,25 +258,63 @@ def _escalate_grievance(self, grievance: Grievance, reason: EscalationReason,
+            .order_by(desc(EscalationAudit.id))\
+            .first()
+
+        last_hash = last_audit.integrity_hash if last_audit else None
+
+        # Update cache for next time
</file context>

Comment thread backend/main_fixed.py Outdated
…ttleneck (with deployment fix)

- Implemented blockchain-style SHA256 hash chaining for EscalationAudit records.
- Optimized hash lookup with a thread-safe O(1) in-memory cache, reducing DB round-trips.
- Fixed N+1 query bottleneck in periodic escalation evaluation using joinedload(Grievance.jurisdiction).
- Fixed a SyntaxError in backend/main.py (missing except block) that caused deployment failure.
- Updated models, schemas, and service logic with performance-first patterns.

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

7 issues found across 1 file (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:150">
P1: Background action-plan generation never persists on the normal path: a dict is assigned to the `Text` column and the commit fails, leaving new issues without their generated plan. Serializing the plan for storage and deserializing it when building `IssueResponse` would keep the database and API types consistent.</violation>

<violation number="2" location="backend/main.py:313">
P2: After a successful vote, `/api/issues/recent` can show the old upvote count for up to five minutes because the cached response is not invalidated. Invalidating `recent_issues_cache` after the commit would keep the recent-issues view consistent.</violation>

<violation number="3" location="backend/main.py:459">
P2: Urgency analysis from the report form always returns 422 because this handler requires `UrgencyAnalysisRequest.category`, but the frontend sends only `description`. Making `category` optional/deriving it, or including it in the frontend payload, would restore the workflow.</violation>

<violation number="4" location="backend/main.py:564">
P1: Concurrent follow requests can create divergent follower-chain branches because this call does not serialize the predecessor read with the insert and commit. A database transaction/lock or another atomic sequencing mechanism would preserve a single chain under concurrency.</violation>

<violation number="5" location="backend/main.py:579">
P2: A nonexistent follower ID produces a 500 instead of a not-found response because the service's short result does not satisfy `BlockchainVerificationResponse`. Returning a 404 or a complete verification payload would make this endpoint handle missing records correctly.</violation>

<violation number="6" location="backend/main.py:594">
P1: On an existing deployment, the escalation blockchain verification endpoint can fail with a database 'no such column' error because the new audit hash columns are not migrated. Adding an explicit migration for both columns before querying `EscalationAudit` would preserve existing databases.</violation>

<violation number="7" location="backend/main.py:776">
P1: These external detector endpoints bypass the 10 MB and MIME/image validation policy, so an unbounded upload is read into memory, base64-encoded, and forwarded to Hugging Face. Applying `validate_uploaded_file` to each handler would prevent oversized or non-image uploads from reaching this path.</violation>
</file>

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

Re-trigger cubic

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 follow requests can create divergent follower-chain branches because this call does not serialize the predecessor read with the insert and commit. A database transaction/lock or another atomic sequencing mechanism would preserve a single chain under concurrency.

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 follow requests can create divergent follower-chain branches because this call does not serialize the predecessor read with the insert and commit. A database transaction/lock or another atomic sequencing mechanism would preserve a single chain under concurrency.</comment>

<file context>
@@ -196,185 +304,663 @@ 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: Background action-plan generation never persists on the normal path: a dict is assigned to the Text column and the commit fails, leaving new issues without their generated plan. Serializing the plan for storage and deserializing it when building IssueResponse would keep the database and API types consistent.

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>Background action-plan generation never persists on the normal path: a dict is assigned to the `Text` column and the commit fails, leaving new issues without their generated plan. Serializing the plan for storage and deserializing it when building `IssueResponse` would keep the database and API types consistent.</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
@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: These external detector endpoints bypass the 10 MB and MIME/image validation policy, so an unbounded upload is read into memory, base64-encoded, and forwarded to Hugging Face. Applying validate_uploaded_file to each handler would prevent oversized or non-image uploads from reaching this path.

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

<comment>These external detector endpoints bypass the 10 MB and MIME/image validation policy, so an unbounded upload is read into memory, base64-encoded, and forwarded to Hugging Face. Applying `validate_uploaded_file` to each handler would prevent oversized or non-image uploads from reaching this path.</comment>

<file context>
@@ -196,185 +304,663 @@ async def ml_status():
[email protected]("/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
import hashlib

def verify_audit():
audit = db.query(EscalationAudit).filter(EscalationAudit.id == audit_id).first()

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: On an existing deployment, the escalation blockchain verification endpoint can fail with a database 'no such column' error because the new audit hash columns are not migrated. Adding an explicit migration for both columns before querying EscalationAudit would preserve existing databases.

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

<comment>On an existing deployment, the escalation blockchain verification endpoint can fail with a database 'no such column' error because the new audit hash columns are not migrated. Adding an explicit migration for both columns before querying `EscalationAudit` would preserve existing databases.</comment>

<file context>
@@ -196,185 +304,663 @@ async def ml_status():
+    import hashlib
+
+    def verify_audit():
+        audit = db.query(EscalationAudit).filter(EscalationAudit.id == audit_id).first()
+        if not audit:
+            raise HTTPException(status_code=404, detail="Audit record not found")
</file context>

Comment thread backend/main.py
Verify the cryptographic integrity of a follower record.
"""
result = await run_in_threadpool(
grievance_service.verify_follower_integrity,

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: A nonexistent follower ID produces a 500 instead of a not-found response because the service's short result does not satisfy BlockchainVerificationResponse. Returning a 404 or a complete verification payload would make this endpoint handle missing records correctly.

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

<comment>A nonexistent follower ID produces a 500 instead of a not-found response because the service's short result does not satisfy `BlockchainVerificationResponse`. Returning a 404 or a complete verification payload would make this endpoint handle missing records correctly.</comment>

<file context>
@@ -196,185 +304,663 @@ async def ml_status():
+    Verify the cryptographic integrity of a follower record.
+    """
+    result = await run_in_threadpool(
+        grievance_service.verify_follower_integrity,
+        follower_id,
+        db
</file context>

Comment thread backend/main.py
def save_issue_db(db: Session, issue: Issue):
db.add(issue)
db.commit()
db.refresh(issue)

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: After a successful vote, /api/issues/recent can show the old upvote count for up to five minutes because the cached response is not invalidated. Invalidating recent_issues_cache after the commit would keep the recent-issues view consistent.

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

<comment>After a successful vote, `/api/issues/recent` can show the old upvote count for up to five minutes because the cached response is not invalidated. Invalidating `recent_issues_cache` after the commit would keep the recent-issues view consistent.</comment>

<file context>
@@ -196,185 +304,663 @@ async def ml_status():
+def save_issue_db(db: Session, issue: Issue):
+    db.add(issue)
+    db.commit()
+    db.refresh(issue)
+    return issue
+
</file context>
Suggested change
db.refresh(issue)
db.refresh(issue)
recent_issues_cache.invalidate()

Comment thread backend/main.py
class ChatRequest(BaseModel):
query: str
@app.post("/api/analyze-urgency", response_model=UrgencyAnalysisResponse)
async def analyze_urgency_endpoint(request: Request, urgency_req: UrgencyAnalysisRequest):

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: Urgency analysis from the report form always returns 422 because this handler requires UrgencyAnalysisRequest.category, but the frontend sends only description. Making category optional/deriving it, or including it in the frontend payload, would restore the workflow.

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

<comment>Urgency analysis from the report form always returns 422 because this handler requires `UrgencyAnalysisRequest.category`, but the frontend sends only `description`. Making `category` optional/deriving it, or including it in the frontend payload, would restore the workflow.</comment>

<file context>
@@ -196,185 +304,663 @@ async def ml_status():
-class ChatRequest(BaseModel):
-    query: str
[email protected]("/api/analyze-urgency", response_model=UrgencyAnalysisResponse)
+async def analyze_urgency_endpoint(request: Request, urgency_req: UrgencyAnalysisRequest):
+    try:
+        client = request.app.state.http_client
</file context>

- Resolved a critical SyntaxError in backend/main.py (missing except block) that caused Render deployment to fail.
- Restored backend/main.py to a clean, optimized state by removing duplicated imports and endpoints.
- Implemented blockchain-style SHA256 hash chaining for EscalationAudit records to ensure tamper-evident logs.
- Optimized hash chaining with a thread-safe O(1) in-memory cache, eliminating unnecessary database scans.
- Fixed an N+1 query bottleneck in periodic escalation evaluation using joinedload(Grievance.jurisdiction).
- Corrected a SyntaxError in backend/hf_service.py (await outside async function).
- Added a new /api/escalation/{audit_id}/blockchain-verify endpoint for audit integrity verification.
@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.

1 issue found across 1 file (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/hf_service.py">

<violation number="1" location="backend/hf_service.py:96">
P2: RGBA and other non-RGB images without a source format are serialized as JPEG, which Pillow rejects and turns into an empty detection result. Converting non-RGB images before JPEG encoding, or choosing a format compatible with the image mode, preserves detection for these inputs.</violation>
</file>

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

Re-trigger cubic

Comment thread backend/hf_service.py
Comment on lines +96 to +97
fmt = getattr(image, 'format', 'JPEG') or 'JPEG'
image.save(img_byte_arr, format=fmt)

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: RGBA and other non-RGB images without a source format are serialized as JPEG, which Pillow rejects and turns into an empty detection result. Converting non-RGB images before JPEG encoding, or choosing a format compatible with the image mode, preserves detection for these inputs.

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

<comment>RGBA and other non-RGB images without a source format are serialized as JPEG, which Pillow rejects and turns into an empty detection result. Converting non-RGB images before JPEG encoding, or choosing a format compatible with the image mode, preserves detection for these inputs.</comment>

<file context>
@@ -88,6 +88,16 @@ async def _make_request(client, image_bytes, labels):
+
+    img_byte_arr = io.BytesIO()
+    # Handle different image formats
+    fmt = getattr(image, 'format', 'JPEG') or 'JPEG'
+    image.save(img_byte_arr, format=fmt)
+    return img_byte_arr.getvalue()
</file context>
Suggested change
fmt = getattr(image, 'format', 'JPEG') or 'JPEG'
image.save(img_byte_arr, format=fmt)
fmt = getattr(image, 'format', 'JPEG') or 'JPEG'
if fmt == 'JPEG' and image.mode != 'RGB':
image = image.convert('RGB')
image.save(img_byte_arr, format=fmt)

…ttleneck (with deployment fix)

- Implemented blockchain-style SHA256 hash chaining for EscalationAudit records to ensure tamper-evident logs.
- Optimized hash lookup with a thread-safe O(1) in-memory cache, reducing DB round-trips.
- Fixed N+1 query bottleneck in periodic escalation evaluation using joinedload(Grievance.jurisdiction).
- Fixed a SyntaxError in backend/main.py (missing except block) that caused deployment failure.
- Fixed a SyntaxError in backend/hf_service.py (await outside async function).
- Updated models, schemas, and service logic with performance-first patterns.
- Synchronized start-backend.py to use the optimized main application entry point.
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