Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3077fb8
ci: stop the ungated auto-merge loop and add real CI
RohanExploit Aug 19, 2026
cbc1a79
fix(backend): make the application importable and bootable
RohanExploit Aug 19, 2026
391f6cc
fix(frontend): repair the broken production build
RohanExploit Aug 19, 2026
58dd4bc
feat(api): route the 15 endpoints the frontend was already calling
RohanExploit Aug 19, 2026
e2e8224
fix: repair the upvote endpoint and make the PWA installable
RohanExploit Aug 19, 2026
8053119
fix(api): close the gaps an adversarial review found
RohanExploit Aug 19, 2026
6fbb240
fix(mobile): make the API reachable from a packaged app
RohanExploit Aug 19, 2026
e0566ec
fix(frontend): clear the lint gate and repair a hook-order crash
RohanExploit Aug 19, 2026
8a70abd
style: clear the backend lint gate and delete the dead main variant
RohanExploit Aug 19, 2026
f7835df
feat(issues): background the action plan, add nearby lookup and dedup…
RohanExploit Aug 19, 2026
7f76d61
feat(android): add the Capacitor Android app and its build pipeline
RohanExploit Aug 19, 2026
92b9a4d
feat(api): enforce rate limiting and take the Telegram poller off the…
RohanExploit Aug 19, 2026
3d36fa9
ci: disable the second scheduled auto-merge
RohanExploit Aug 19, 2026
b9fff8f
build: add a production container image for the API
RohanExploit Aug 19, 2026
4807022
fix(android): repair two build defects found by actually building
RohanExploit Aug 19, 2026
53a0d50
feat(android): verify the app on a real device and add the dev-only a…
RohanExploit Aug 19, 2026
92a6213
fix(ci): clear the three failing checks on this PR
RohanExploit Aug 19, 2026
6f889d3
feat(auth): require a key for the two endpoints that change official …
RohanExploit Aug 19, 2026
5b4b91c
feat(db): put the schema under Alembic and delete the startup migrations
RohanExploit Aug 19, 2026
78e56ef
style: scope the subprocess lint rules to test paths
RohanExploit Aug 19, 2026
3d7eaa3
feat(frontend): route the eight detectors that nothing could reach
RohanExploit Aug 19, 2026
6e88b0a
fix: repair the chat endpoint, the Smart Scanner CTA, and 4xx-as-5xx …
RohanExploit Aug 19, 2026
08d05d4
fix(ui): unblock the report card and cover ReportForm with tests
RohanExploit Aug 25, 2026
b3f80e6
fix(ops): make the health check tell the truth and survive a missing …
RohanExploit Aug 25, 2026
9add48a
fix(api): survive the backend's cold start, and test the real client
RohanExploit Aug 25, 2026
76409c0
fix(db): degrade to SQLite when the configured database is unreachable
RohanExploit Aug 25, 2026
cc3325e
style: apply ruff format to test_health.py and test_json_payload_cont…
RohanExploit Aug 26, 2026
5355375
fix: mark frontend/android/gradlew executable
RohanExploit Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Keep the build context small and secrets out of the image.
.git
.github
.venv
venv
node_modules
frontend
android
docs
documents
tests
scripts
assets

*.md
!README.md

.env
*.keystore
*.jks

__pycache__
*.py[cod]
.pytest_cache
.ruff_cache
htmlcov
.coverage

data/uploads/*
data/*.db
backend/data/*.db
69 changes: 69 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,72 @@ LOCAL_ML_QUANTIZE=false

# CLIP model used for local inference
LOCAL_CLIP_MODEL=openai/clip-vit-base-patch32


# ===============================
# API server configuration
# ===============================

# Comma-separated list of browser origins allowed to call the API. The Capacitor
# WebView origins are always appended, so the packaged Android app works without
# listing them here.
CORS_ORIGINS=http://localhost:5173

# Rate limiting. Both were declared in render.yaml long before anything read
# them; they are enforced now.
RATE_LIMIT_ENABLED=true
MAX_REQUESTS_PER_MINUTE=60
# Tighter bucket for routes that call a paid inference API.
AI_REQUESTS_PER_MINUTE=12
# In-process counters are correct for a single instance. Use redis:// beyond one.
RATE_LIMIT_STORAGE_URI=memory://

# Largest accepted upload, in megabytes.
MAX_UPLOAD_SIZE_MB=10

# Set on exactly ONE process. Telegram rejects a second long-poll on the same
# token with HTTP 409, so enabling this on a multi-worker web service breaks the
# bot. Run the API with it unset and a single dedicated worker with it set.
RUN_TELEGRAM_BOT=false

# ===============================
# Frontend build
# ===============================

# Absolute API base URL baked into the web build. Required for the Android app:
# a WebView has no dev proxy or Netlify redirect, so a relative /api path has
# nothing to resolve against, and Android blocks cleartext http:// by default.
VITE_API_URL=https://your-backend.onrender.com

# ===============================
# Authentication
# ===============================

# Shared administrative key for the endpoints that change state officials act
# on: escalating a grievance (which reassigns the responsible authority) and
# verifying an issue (which changes its status). Must be at least 32
# characters; the server refuses shorter keys rather than pretending to be
# protected. If this is unset those endpoints answer 503, never 200 -- a
# missing secret must not read as "no authentication required".
#
# python -c "import secrets; print(secrets.token_urlsafe(48))"
ADMIN_API_KEY=

# Secret for verifying bearer tokens used for user attribution. Optional:
# without it, requests carrying no token are still served anonymously, but a
# request that does carry one is rejected rather than silently downgraded.
JWT_SECRET=

# If the configured DATABASE_URL cannot be reached at startup, the service falls
# back to local SQLite rather than failing every request. The fallback is logged
# at ERROR and reported by /health as "sqlite-fallback", and /health/ready stays
# 503, because a service that works is not the same as one configured correctly.
#
# It is a stopgap. On a platform with an ephemeral filesystem the SQLite file
# does not survive a restart, so reports collected while degraded can be lost.
# Set this to false for deployments that should refuse to start instead.
SQLITE_FALLBACK_ENABLED=true

# Seconds to wait for the configured database before giving up on it. Kept
# short: this runs during startup.
DB_CONNECT_TIMEOUT=10
137 changes: 137 additions & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
name: Android

# Builds the Capacitor Android app.
#
# Pull requests get an unsigned debug APK so packaging breakage is caught before
# merge. Tags and manual runs produce a signed release bundle for Play Console.
#
# VITE_API_URL must be an absolute https:// URL. Inside a WebView there is no
# Vite dev proxy and no Netlify redirect, so a relative /api path has nothing to
# resolve against, and Android blocks cleartext http:// by default from API 28.

on:
pull_request:
paths:
- 'frontend/**'
- '.github/workflows/android.yml'
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version_name:
description: 'Version name, e.g. 1.2.0'
required: false
type: string

permissions:
contents: read

concurrency:
group: android-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
name: Build ${{ (github.event_name == 'pull_request') && 'debug APK' || 'release AAB' }}
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
cache-dependency-path: frontend/package-lock.json

# Capacitor 8's Android tooling requires JDK 21.
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'

- uses: android-actions/setup-android@v3

- uses: gradle/actions/setup-gradle@v4

- name: Install dependencies
working-directory: frontend
run: npm ci

- name: Verify the API base URL is absolute
env:
VITE_API_URL: ${{ vars.VITE_API_URL }}
run: |
if [ -z "$VITE_API_URL" ]; then
echo "::error::VITE_API_URL repository variable is not set. A packaged app cannot reach a relative /api path."
exit 1
fi
case "$VITE_API_URL" in
https://*) ;;
*) echo "::error::VITE_API_URL must start with https:// (got '$VITE_API_URL'). Android blocks cleartext traffic."; exit 1 ;;
esac

- name: Build web bundle and sync to Android
working-directory: frontend
env:
VITE_API_URL: ${{ vars.VITE_API_URL }}
run: npm run mobile:sync

- name: Decode signing keystore
if: github.event_name != 'pull_request'
env:
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
run: |
if [ -z "$KEYSTORE_BASE64" ]; then
echo "::error::ANDROID_KEYSTORE_BASE64 secret is not set; a release build cannot be signed."
exit 1
fi
echo "$KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/release.keystore"
echo "ANDROID_KEYSTORE_PATH=$RUNNER_TEMP/release.keystore" >> "$GITHUB_ENV"

- name: Build debug APK
if: github.event_name == 'pull_request'
working-directory: frontend/android
run: ./gradlew --no-daemon assembleDebug

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 Android job cannot run either build because frontend/android/gradlew is not executable in Git. Mark the wrapper executable in the repository (preferred), or invoke bash ./gradlew in both build steps.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/android.yml, line 96:

<comment>The Android job cannot run either build because `frontend/android/gradlew` is not executable in Git. Mark the wrapper executable in the repository (preferred), or invoke `bash ./gradlew` in both build steps.</comment>

<file context>
@@ -0,0 +1,137 @@
+      - name: Build debug APK
+        if: github.event_name == 'pull_request'
+        working-directory: frontend/android
+        run: ./gradlew --no-daemon assembleDebug
+
+      - name: Build release bundle
</file context>

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 Android job cannot run either build because frontend/android/gradlew is not executable in Git. Mark the wrapper executable in the repository (preferred), or invoke bash ./gradlew in both build steps.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/android.yml, line 96:

<comment>The Android job cannot run either build because `frontend/android/gradlew` is not executable in Git. Mark the wrapper executable in the repository (preferred), or invoke `bash ./gradlew` in both build steps.</comment>

<file context>
@@ -0,0 +1,137 @@
+      - name: Build debug APK
+        if: github.event_name == 'pull_request'
+        working-directory: frontend/android
+        run: ./gradlew --no-daemon assembleDebug
+
+      - name: Build release bundle
</file context>


- name: Build release bundle
if: github.event_name != 'pull_request'
working-directory: frontend/android
env:
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
# Monotonic per run, which Play Console requires for every upload.
ANDROID_VERSION_CODE: ${{ github.run_number }}
ANDROID_VERSION_NAME: ${{ inputs.version_name || github.ref_name }}
run: ./gradlew --no-daemon bundleRelease

- name: Confirm the bundle is signed
if: github.event_name != 'pull_request'
run: |
BUNDLE=frontend/android/app/build/outputs/bundle/release/app-release.aab
test -f "$BUNDLE" || { echo "::error::No bundle produced at $BUNDLE"; exit 1; }
# A v2-signed artifact carries META-INF/*.RSA (or .EC); its absence
# means Gradle silently fell back to the debug signing config.
if ! unzip -l "$BUNDLE" | grep -qE 'META-INF/.*\.(RSA|EC|DSA)'; then
echo "::error::Bundle is not signed with the release key."
exit 1
fi
echo "Signed bundle: $(du -h "$BUNDLE" | cut -f1)"

- name: Upload debug APK
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@v4
with:
name: vishwaguru-debug-apk
path: frontend/android/app/build/outputs/apk/debug/*.apk
retention-days: 7

- name: Upload release bundle
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v4
with:
name: vishwaguru-release-aab
path: frontend/android/app/build/outputs/bundle/release/*.aab
retention-days: 30
27 changes: 23 additions & 4 deletions .github/workflows/auto-deploy.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
name: Automated CI/CD Pipeline

# DISABLED daily cron on 2026-08-19.

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: This workflow is kept "for manual dispatch only, for the deployment steps," but the retained run: python vishwaguru_pipeline.py step still executes the full auto-merge pipeline with GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}. That script (vishwaguru_pipeline.py) fetches every open PR and squash-merges it via the GitHub API, then reverts on failure. A manual dispatch therefore still auto-merges all open, human-unreviewed PRs to main, defeating this PR's purpose. The cron removal only removes the timer, not the merge behavior. Remove the python vishwaguru_pipeline.py step (or its PR-merge logic) and keep only the deployment steps.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/auto-deploy.yml, line 3:

<comment>This workflow is kept "for manual dispatch only, for the deployment steps," but the retained `run: python vishwaguru_pipeline.py` step still executes the full auto-merge pipeline with `GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}`. That script (vishwaguru_pipeline.py) fetches every open PR and squash-merges it via the GitHub API, then reverts on failure. A manual dispatch therefore still auto-merges all open, human-unreviewed PRs to main, defeating this PR's purpose. The cron removal only removes the timer, not the merge behavior. Remove the `python vishwaguru_pipeline.py` step (or its PR-merge logic) and keep only the deployment steps.</comment>

<file context>
@@ -1,10 +1,29 @@
 name: Automated CI/CD Pipeline
 
+# DISABLED daily cron on 2026-08-19.
+#
+# This workflow ran vishwaguru_pipeline.py, which squash-merges open pull
</file context>

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: This workflow is kept "for manual dispatch only, for the deployment steps," but the retained run: python vishwaguru_pipeline.py step still executes the full auto-merge pipeline with GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}. That script (vishwaguru_pipeline.py) fetches every open PR and squash-merges it via the GitHub API, then reverts on failure. A manual dispatch therefore still auto-merges all open, human-unreviewed PRs to main, defeating this PR's purpose. The cron removal only removes the timer, not the merge behavior. Remove the python vishwaguru_pipeline.py step (or its PR-merge logic) and keep only the deployment steps.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/auto-deploy.yml, line 3:

<comment>This workflow is kept "for manual dispatch only, for the deployment steps," but the retained `run: python vishwaguru_pipeline.py` step still executes the full auto-merge pipeline with `GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}`. That script (vishwaguru_pipeline.py) fetches every open PR and squash-merges it via the GitHub API, then reverts on failure. A manual dispatch therefore still auto-merges all open, human-unreviewed PRs to main, defeating this PR's purpose. The cron removal only removes the timer, not the merge behavior. Remove the `python vishwaguru_pipeline.py` step (or its PR-merge logic) and keep only the deployment steps.</comment>

<file context>
@@ -1,10 +1,29 @@
 name: Automated CI/CD Pipeline
 
+# DISABLED daily cron on 2026-08-19.
+#
+# This workflow ran vishwaguru_pipeline.py, which squash-merges open pull
</file context>

#
# This workflow ran vishwaguru_pipeline.py, which squash-merges open pull
# requests through the GitHub API. Its gate did not check what it appeared to:
#
# * quality check = PR title >= 5 characters and body >= 10 characters
# * security check = grep the diff for a keyword list
# * "run tests" = `npm test` against the ROOT package.json, whose script is
# `jest tests/`. That collects one TypeScript file. It never ran the backend
# pytest suite or the frontend Jest suite.
# * "deploy and health check" = no docker-compose.yml and no manage.py exist,
# so it fell through to `python -m http.server`, then confirmed that static
# file server answered 200 -- and treated that as the application being
# healthy.
#
# So it merged to main daily on evidence that proved nothing. Together with
# auto-merge-jules.yml (deleted in the same pass) this is how the repository
# reached a state where the backend could not import, the frontend could not
# build, and 15 endpoints the frontend called did not exist.
#
# Merges now go through .github/workflows/ci.yml and human review. This is kept
# for manual dispatch only, for the deployment steps.

on:
schedule:
# Trigger at 2 AM UTC daily
- cron: '0 2 * * *'
# Allow manual triggering
workflow_dispatch:

jobs:
Expand Down
30 changes: 0 additions & 30 deletions .github/workflows/auto-merge-jules.yml

This file was deleted.

Loading
Loading