Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
43 changes: 43 additions & 0 deletions alembic/versions/0007_worker_execution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Add concurrency-safe active-job deduplication for the PostgreSQL worker.

Revision ID: 0007_worker_execution
Revises: 0006_governance_retention
"""

from alembic import op


revision = "0007_worker_execution"
down_revision = "0006_governance_retention"
branch_labels = None
depends_on = None


def upgrade() -> None:
op.execute("ALTER TABLE job_runs ADD COLUMN active_dedupe_key TEXT")
op.execute(
"""
ALTER TABLE job_runs
ADD CONSTRAINT ck_job_runs_active_dedupe_key
CHECK (
active_dedupe_key IS NULL
OR (btrim(active_dedupe_key) <> '' AND length(active_dedupe_key) <= 200)
)
"""
)
op.execute(
"""
CREATE UNIQUE INDEX uq_job_runs_active_dedupe
ON job_runs (active_dedupe_key)
WHERE active_dedupe_key IS NOT NULL
AND status IN ('queued', 'running')
"""
)


def downgrade() -> None:
op.execute("DROP INDEX IF EXISTS uq_job_runs_active_dedupe")
op.execute(
"ALTER TABLE job_runs DROP CONSTRAINT IF EXISTS ck_job_runs_active_dedupe_key"
)
op.execute("ALTER TABLE job_runs DROP COLUMN IF EXISTS active_dedupe_key")
2 changes: 1 addition & 1 deletion config/observability.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"configuration_version": "1",
"service": "foundation-intelligence-api",
"expected_schema_version": "0006_governance_retention",
"expected_schema_version": "0007_worker_execution",
"metrics": [
{"name": "readiness_success", "type": "gauge", "unit": "None", "dimensions": ["service", "environment"]},
{"name": "api_request_duration_ms", "type": "histogram", "unit": "Milliseconds", "dimensions": ["service", "environment", "operation", "status"]},
Expand Down
22 changes: 17 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ x-backend-common: &backend-common
DATABASE_NAME: foundation_intelligence
DATABASE_USER: foundation_app
DATABASE_PASSWORD_FILE: /run/secrets/postgres_password
DATABASE_WRITE_USER: foundation_app
DATABASE_WRITE_PASSWORD_FILE: /run/secrets/postgres_password
DATABASE_CONNECT_TIMEOUT_SECONDS: "5"
DATABASE_STATEMENT_TIMEOUT_MS: "30000"
secrets:
Expand Down Expand Up @@ -114,19 +116,29 @@ services:

worker:
<<: *backend-common
profiles: ["operations"]
depends_on:
postgres:
condition: service_healthy
migration:
condition: service_completed_successfully
entrypoint: ["python", "-m"]
command: ["pipelines.run_pipeline", "--source", "consolidate"]
restart: "no"
command: ["pipelines.worker"]
environment:
<<: *backend-environment
DATABASE_PIPELINE_USER: foundation_app
DATABASE_PIPELINE_PASSWORD_FILE: /run/secrets/postgres_password
CODE_REVISION: 0000000000000000000000000000000000000000
PIPELINE_SOURCE_SCHEMA_VERSION: "7"
PIPELINE_BASELINE_PATH: /runtime-baseline/charities.db
PIPELINE_SNAPSHOT_PATH: /pipeline-snapshot/charities.db
PIPELINE_FALLBACK_SHA256: 8fc0cce61c81d54869a3cc9a61d9378e1cb03f2b9607a70c2836b52fba257651
volumes:
- ./src/data/charities.db:/runtime-baseline/charities.db:ro
- pipeline_snapshot:/pipeline-snapshot
restart: unless-stopped

secrets:
postgres_password:
file: ${POSTGRES_PASSWORD_FILE:?Set POSTGRES_PASSWORD_FILE to a local secret file}

volumes:
postgres_data:
pipeline_snapshot:
2 changes: 1 addition & 1 deletion docker/frontend-nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@ server {
try_files $uri $uri/ /index.html;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self'; connect-src 'self'; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self'; connect-src 'self' https://*.auth.eu-west-1.amazoncognito.com; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
}
}
2 changes: 1 addition & 1 deletion docker/frontend-nginx.ecs.conf
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@ server {
try_files $uri $uri/ /index.html;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self'; connect-src 'self'; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self'; connect-src 'self' https://*.auth.eu-west-1.amazoncognito.com; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
}
}
9 changes: 6 additions & 3 deletions docs/remediation/aws-postgres-route-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,13 @@ There is no internal-service route in the current application. Queue/task callba
## Phase boundary

Production/staging application data, job state, request idempotency, link
overrides, profile caches and audit records use PostgreSQL. Manual refresh
overrides and profile caches use PostgreSQL through separate reader/writer
pools. Security audit events use structured application logs so a GET never
causes PostgreSQL DML. Manual refresh
routes only enqueue durable jobs and return a job ID. Phase-8 workers claim
through PostgreSQL leases and the transactional outbox supplies the SQS
delivery contract; neither path uses a production local lock or API
subprocess. Production audit events are append-only PostgreSQL rows; local
unit tests may replace the sink with deterministic memory state.
subprocess. The exact mutation/session/table contract is documented in
`database-access-architecture.md`; local unit tests may replace durable stores
with deterministic memory state.
Edge/distributed rate limiting remains a Terraform and deployment concern.
Loading
Loading