feat: Google Vision primary OCR with Auth0 WIF and monthly usage cap #127
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Invert the OCR engine priority: use Google Cloud Vision API as the primary engine (higher accuracy) until a configurable monthly request limit is reached, then fall back to local PaddleOCR for the remainder of the month. Authentication uses Auth0 Workload Identity Federation (WIF) -- no service account keys.
Context
secrets/app/google-wif-config.jsonRequirements
Engine Priority Change
Monthly Request Counter (Redis)
ocr:vision_requests:{YYYY-MM}(e.g.,ocr:vision_requests:2026-02)Configuration (docker-compose env vars)
VISION_MONTHLY_LIMIT=1000-- configurable requests per calendar month (default 1000)OCR_PRIMARY_ENGINE=google_vision-- flip default frompaddleocrtogoogle_visionOCR_FALLBACK_ENGINE=paddleocr-- PaddleOCR becomes the fallbackOCR_CONFIDENCE_THRESHOLDandOCR_FALLBACK_THRESHOLDsettingsAuth0 Token Script
/app/scripts/fetch-auth0-token.shin the OCR container/run/secrets/auth0-ocr-client-id/run/secrets/auth0-ocr-client-secrethttps://motovaultpro.auth0.com/oauth/tokenwithclient_credentialsgrantsecrets/app/google-wif-config.jsonalready references this script pathDocker Secrets and CI/CD Deployment
Two new Gitea repository secrets must be created in the Gitea web UI:
AUTH0_OCR_CLIENT_ID-- Auth0 M2M application client ID for GCP WIFAUTH0_OCR_CLIENT_SECRET-- Auth0 M2M application client secret for GCP WIFThese are separate from the existing
AUTH0_CLIENT_SECRET(which is for the backend Auth0 tenant).scripts/inject-secrets.shAdd the two new secrets to the injection script following the existing pattern:
AUTH0_OCR_CLIENT_IDandAUTH0_OCR_CLIENT_SECRETenv vars$SECRETS_DIR/auth0-ocr-client-id.txtand$SECRETS_DIR/auth0-ocr-client-secret.txt.gitea/workflows/staging.yamlAdd to the "Inject secrets" step env block (~line 132-147):
.gitea/workflows/production.yamlAdd to the "Inject secrets" step env block (~line 122-137):
Docker Compose secret mounts (all compose files)
Mount into the OCR container following the existing bind-mount pattern:
Files to update:
docker-compose.yml(dev)docker-compose.staging.yml(staging)docker-compose.prod.yml(production)docker-compose.blue-green.yml(both blue and green OCR stacks)Secret file examples
Add
.examplefiles for documentation:secrets/app/auth0-ocr-client-id.txt.example-- contents:your-auth0-m2m-client-idsecrets/app/auth0-ocr-client-secret.txt.example-- contents:your-auth0-m2m-client-secretCounter Reset
YYYY-MM) and TTLFiles to Change
ocr/app/engines/cloud_engine.py_get_client()to use WIF config via ADC instead of service account key fileocr/app/engines/hybrid_engine.pyocr/app/config.pyVISION_MONTHLY_LIMITsetting, update defaultsocr/app/scripts/fetch-auth0-token.shocr/Dockerfilecurl/jqif not present, copy script, make executableocr/requirements.txtgoogle-cloud-visionandredisalready present (they are)ocr/tests/test_engine_abstraction.pyscripts/inject-secrets.shAUTH0_OCR_CLIENT_IDandAUTH0_OCR_CLIENT_SECRETinjection and validation.gitea/workflows/staging.yaml.gitea/workflows/production.yamldocker-compose.ymldocker-compose.staging.ymldocker-compose.prod.ymldocker-compose.blue-green.ymlsecrets/app/auth0-ocr-client-id.txt.examplesecrets/app/auth0-ocr-client-secret.txt.exampleAcceptance Criteria
VISION_MONTHLY_LIMITis configurable via docker-compose env varscripts/inject-secrets.shinjects and validates the two new secrets.gitea/workflows/staging.yamlpasses new secrets to inject script.gitea/workflows/production.yamlpasses new secrets to inject scriptdocker-compose.blue-green.ymlmounts secrets into both blue and green OCR stacks.examplefiles added for new secretsMilestone: Execution Progress
Phase: Execution | Agent: Developer | Status: IN_PROGRESS
Milestone 1: OCR Config and Engine Updates -- COMPLETE
VISION_MONTHLY_LIMITtoconfig.py(default 1000)CloudEngine._get_client()to use WIF credential config via ADC (setsGOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES=1)HybridEnginewith cloud-primary path: Redis counter check before Vision calls, hard cutoff to fallback when limit reachedengine_factory.pyto passmonthly_limittoHybridEngine4abd7d8Milestone 2: Auth0 Token Script and Dockerfile -- COMPLETE
ocr/scripts/fetch-auth0-token.sh-- reads Auth0 M2M credentials from Docker secrets, exchanges for JWT, outputs in Google executable-sourced credential formatjqto Dockerfile system dependenciesRUN chmod +xfor script in container image9209739Milestone 3: Secrets Injection and CI/CD -- COMPLETE
AUTH0_OCR_CLIENT_IDandAUTH0_OCR_CLIENT_SECRETtoscripts/inject-secrets.sh(env vars, file list, injection calls).gitea/workflows/staging.yaml).gitea/workflows/production.yaml).examplefiles for new secrets5e4848cMilestone 4: Docker Compose Updates -- COMPLETE
docker-compose.yml: engine config, secret mounts, WIF config mountdocker-compose.staging.yml: engine config, secret mounts, WIF config mountdocker-compose.prod.yml: engine configdocker-compose.blue-green.yml: secret mounts for shared OCR servicegoogle-wif-config.jsonto repo (not a secret -- contains no credentials)google-vision-key.json.examplef4a28d0Milestone 5: Tests and Validation -- COMPLETE
e6dd749Remaining
AUTH0_OCR_CLIENT_IDandAUTH0_OCR_CLIENT_SECRETmust be created manually in Gitea web UIVerdict: COMPLETE | Next: Open PR