Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
2bf255c
Add user identities and v1-anchored association schema
anth-volk Oct 12, 2026
6868fc9
Add Auth0 bearer authentication for user-owned routes
anth-volk Oct 12, 2026
98e27cc
Lock user associations to the signed-in user and add v1-anchored saves
anth-volk Oct 12, 2026
7b0cf1d
Register signed-in user route contracts and Postgres identity tests
anth-volk Oct 12, 2026
e909054
Add read-only qualification for the user-association reshape
anth-volk Oct 12, 2026
04fa436
Wire user auth settings into deploys and gate staging on signed-in ro…
anth-volk Oct 12, 2026
d92bffc
Document Stage 12A user login and propose Cloud Armor throttles
anth-volk Oct 12, 2026
292665a
Describe owner-from-token association queries in the routes skill
anth-volk Oct 12, 2026
89be2b0
Keep release-test unsets inline-short and update household reassignme…
anth-volk Oct 12, 2026
5864f9d
Pass the optional staging Auth0 realm to the live signed-in probe
anth-volk Oct 12, 2026
41c4af7
Keep retired association columns so the previous revision still loads…
anth-volk Oct 13, 2026
29c5ea8
Scope account imports to one country through shared association helpers
anth-volk Oct 13, 2026
a496e72
Bound single user-route writes and their free-text fields
anth-volk Oct 13, 2026
ed55f78
Type report and policy snapshots in user-route responses
anth-volk Oct 13, 2026
8b66fb0
Log every signed-in user route as Supabase traffic
anth-volk Oct 13, 2026
ee8f47d
Defer v1 reference checks, log unexpected user-route failures, share …
anth-volk Oct 13, 2026
c656ea1
Keep Auth0 Management deletion out of staging deploys
anth-volk Oct 13, 2026
76baf1a
Cover every signed-in operation with the fail-closed 401 test
anth-volk Oct 13, 2026
e0839f3
Keep the failure-logging test independent of Alembic's logging config
anth-volk Oct 13, 2026
361a709
Reject NaN and Infinity in user-route floats and JSON values
anth-volk Oct 13, 2026
0b72a24
Log persistence failures and publish stored snapshots through lenient…
anth-volk Oct 13, 2026
91d4669
Obtain Auth0 credentials before deleting an account, and retry a reje…
anth-volk Oct 13, 2026
4edf9bf
Keep compute labels off saved-work requests and describe the size che…
anth-volk Oct 13, 2026
9c00c18
Require an edge guard before any application rollback with signed-in …
anth-volk Oct 13, 2026
57553ed
Exercise the previous revision's table usage against the Stage 12A sc…
anth-volk Oct 13, 2026
fd6b9b9
Report account_created on /v2/me so app v3 imports only for new accounts
anth-volk Oct 13, 2026
7bd6814
Drop account_created from /v2/me now that app v3 imports at every sig…
anth-volk Oct 13, 2026
bb02660
Remove the unused ResolvedUser.provisioned flag
anth-volk Oct 13, 2026
466b803
Store no import item whose client_ref repeats in the request
anth-volk Oct 13, 2026
76c294c
Size the proposed import throttle for imports at every sign-in
anth-volk Oct 13, 2026
4f45745
Warn that the auth issuer must be final before the first sign-in
anth-volk Oct 13, 2026
a5f13a2
Save signed-out work under unauthenticated users instead of importing it
anth-volk Oct 14, 2026
799655a
Record signed-out user contracts and accept either credential in the …
anth-volk Oct 14, 2026
9aace0b
Exercise signed-out saving and upgrades in the staging and candidate …
anth-volk Oct 14, 2026
60c650d
Document unauthenticated users, upgrades, and the single write throttle
anth-volk Oct 14, 2026
1547071
Merge remote-tracking branch 'origin/master' into feat/stage-12a-user…
anth-volk Oct 14, 2026
53d64b3
Register the Stage 12A sign-in libraries and user-service guard as rules
anth-volk Oct 14, 2026
3296a58
Keep a report's budget window on its user-report record
anth-volk Oct 14, 2026
6a22bf9
Link a report's compared reform and accept growth settings on provisions
anth-volk Oct 14, 2026
77dc183
Merge remote-tracking branch 'origin/master' into feat/stage-12a-user…
anth-volk Oct 14, 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
13 changes: 13 additions & 0 deletions .github/scripts/deploy_cloud_run_candidate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,21 @@ env_vars=(
"V2_SUPABASE_PROJECT_REF=${V2_SUPABASE_PROJECT_REF}"
"V2_SUPABASE_ENVIRONMENT=${V2_SUPABASE_ENVIRONMENT}"
"V2_RUNTIME_DATABASE_URL_SECRET_RESOURCE=${V2_RUNTIME_DATABASE_URL_SECRET_RESOURCE}"
"PUBLIC_API_AUTH_REQUIRED=${PUBLIC_API_AUTH_REQUIRED}"
"PUBLIC_API_AUTH_ISSUER=${PUBLIC_API_AUTH_ISSUER:-}"
"PUBLIC_API_AUTH_AUDIENCE=${PUBLIC_API_AUTH_AUDIENCE:-}"
)

# Optional: account deletion removes the Auth0 user only when the Management
# API client is configured. Validation requires all three values or none.
if [[ -n "${AUTH0_MANAGEMENT_DOMAIN:-}" ]]; then
env_vars+=(
"AUTH0_MANAGEMENT_DOMAIN=${AUTH0_MANAGEMENT_DOMAIN}"
"AUTH0_MANAGEMENT_CLIENT_ID=${AUTH0_MANAGEMENT_CLIENT_ID}"
"AUTH0_MANAGEMENT_CLIENT_SECRET_RESOURCE=${AUTH0_MANAGEMENT_CLIENT_SECRET_RESOURCE}"
)
fi

if [[ -n "${OLD_SIMULATION_GATEWAY_URL:-}" ]]; then
env_vars+=("OLD_SIMULATION_GATEWAY_URL=${OLD_SIMULATION_GATEWAY_URL}")
fi
Expand Down
48 changes: 47 additions & 1 deletion .github/scripts/validate_cloud_run_deploy_env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ cloud_run_require_env \
GATEWAY_AUTH_ISSUER \
GATEWAY_AUTH_AUDIENCE \
GATEWAY_AUTH_CLIENT_ID \
GATEWAY_AUTH_CLIENT_SECRET_RESOURCE
GATEWAY_AUTH_CLIENT_SECRET_RESOURCE \
PUBLIC_API_AUTH_REQUIRED

for selector in \
ROUTE_IMPL_HEALTH \
Expand Down Expand Up @@ -110,6 +111,51 @@ case "${DB_WRITE_HOUSEHOLD}" in
;;
esac

# Signed-in user routes fail closed without Auth0 settings. When required,
# the issuer and audience must be present; otherwise they are set together
# or not at all.
case "${PUBLIC_API_AUTH_REQUIRED}" in
0|1) ;;
*)
printf '%s=%s is invalid; expected 0 or 1\n' \
"PUBLIC_API_AUTH_REQUIRED" "${PUBLIC_API_AUTH_REQUIRED}" >&2
exit 1
;;
esac
if [[ "${PUBLIC_API_AUTH_REQUIRED}" == "1" ]]; then
cloud_run_require_env PUBLIC_API_AUTH_ISSUER PUBLIC_API_AUTH_AUDIENCE
fi
if [[ -n "${PUBLIC_API_AUTH_ISSUER:-}" && -z "${PUBLIC_API_AUTH_AUDIENCE:-}" ]] \
|| [[ -z "${PUBLIC_API_AUTH_ISSUER:-}" && -n "${PUBLIC_API_AUTH_AUDIENCE:-}" ]]; then
echo "Set both PUBLIC_API_AUTH_ISSUER and PUBLIC_API_AUTH_AUDIENCE, or neither" >&2
exit 1
fi
if [[ -n "${PUBLIC_API_AUTH_ISSUER:-}" && "${PUBLIC_API_AUTH_ISSUER}" != https://* ]]; then
echo "PUBLIC_API_AUTH_ISSUER must be an absolute HTTPS URL" >&2
exit 1
fi

auth0_management_count=0
for name in \
AUTH0_MANAGEMENT_DOMAIN \
AUTH0_MANAGEMENT_CLIENT_ID \
AUTH0_MANAGEMENT_CLIENT_SECRET_RESOURCE; do
if [[ -n "${!name:-}" ]]; then
auth0_management_count=$((auth0_management_count + 1))
fi
done
if (( auth0_management_count != 0 && auth0_management_count != 3 )); then
echo "Set AUTH0_MANAGEMENT_DOMAIN, AUTH0_MANAGEMENT_CLIENT_ID, and AUTH0_MANAGEMENT_CLIENT_SECRET_RESOURCE together, or none" >&2
exit 1
fi
# The staging live test deletes its own account through DELETE /v2/me. With
# Management API deletion configured, that would delete the CI user from
# Auth0, so only production may configure it.
if (( auth0_management_count != 0 )) && [[ "${DEPLOYMENT_ENVIRONMENT}" != "production" ]]; then
echo "AUTH0_MANAGEMENT_* settings are production-only; unset them for ${DEPLOYMENT_ENVIRONMENT}" >&2
exit 1
fi

selected_url_env="$(
simulation_entrypoint_url_env_name "${SIM_ENTRYPOINT}"
)"
Expand Down
19 changes: 18 additions & 1 deletion .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ jobs:
DB_WRITE_POLICY: ${{ vars.DB_WRITE_POLICY }}
DB_READ_HOUSEHOLD: ${{ vars.DB_READ_HOUSEHOLD }}
DB_WRITE_HOUSEHOLD: ${{ vars.DB_WRITE_HOUSEHOLD }}
PUBLIC_API_AUTH_REQUIRED: ${{ vars.PUBLIC_API_AUTH_REQUIRED }}
PUBLIC_API_AUTH_ISSUER: ${{ vars.PUBLIC_API_AUTH_ISSUER }}
PUBLIC_API_AUTH_AUDIENCE: ${{ vars.PUBLIC_API_AUTH_AUDIENCE }}
CLOUD_RUN_SERVICE: policyengine-api-staging
CLOUD_RUN_RUNTIME_CACHE_ENVIRONMENT: staging
CLOUD_RUN_RUNTIME_CACHE_URL_SECRET: policyengine-api-staging-runtime-cache-url:latest
Expand Down Expand Up @@ -251,7 +254,7 @@ jobs:
run: >-
env -u DEPLOYMENT_ENVIRONMENT -u ROUTE_IMPL_HEALTH -u ROUTE_IMPL_SPECIFICATION -u ROUTE_IMPL_METADATA -u ROUTE_IMPL_POLICY -u ROUTE_IMPL_HOUSEHOLD -u DB_READ_POLICY -u DB_WRITE_POLICY -u DB_READ_HOUSEHOLD -u DB_WRITE_HOUSEHOLD
-u CLOUD_RUN_SERVICE -u CLOUD_RUN_RUNTIME_CACHE_ENVIRONMENT -u CLOUD_RUN_RUNTIME_CACHE_URL_SECRET -u CLOUD_RUN_RUNTIME_CACHE_CA_CERT_SECRET -u CLOUD_RUN_POLICYENGINE_DB_PASSWORD_SECRET -u CLOUD_RUN_MIN_INSTANCES -u CLOUD_RUN_SERVICE_MIN_INSTANCES -u CLOUD_RUN_MAX_INSTANCES
-u V2_SUPABASE_PROJECT_REF -u V2_SUPABASE_ENVIRONMENT -u V2_RUNTIME_DATABASE_URL_SECRET_RESOURCE -u PRODUCTION_V2_SUPABASE_PROJECT_REF -u PRODUCTION_V2_RUNTIME_DATABASE_URL_SECRET_RESOURCE -u PRODUCTION_POLICYENGINE_DB_INSTANCE_CONNECTION_NAME make test
-u V2_SUPABASE_PROJECT_REF -u V2_SUPABASE_ENVIRONMENT -u V2_RUNTIME_DATABASE_URL_SECRET_RESOURCE -u PRODUCTION_V2_SUPABASE_PROJECT_REF -u PRODUCTION_V2_RUNTIME_DATABASE_URL_SECRET_RESOURCE -u PRODUCTION_POLICYENGINE_DB_INSTANCE_CONNECTION_NAME -u PUBLIC_API_AUTH_REQUIRED -u PUBLIC_API_AUTH_ISSUER -u PUBLIC_API_AUTH_AUDIENCE make test
env:
RUNTIME_CACHE_MODE: local
RUNTIME_CACHE_URL: redis://127.0.0.1:6379/0
Expand Down Expand Up @@ -326,6 +329,14 @@ jobs:
API_BASE_URL: ${{ steps.candidate.outputs.url }}
STAGING_API_TEST_PROBE_ID: cloud-run-${{ steps.cloud_run.outputs.revision_tag }}
V2_MIGRATION_DATABASE_URL: ${{ secrets.V2_MIGRATION_DATABASE_URL }}
# Staging-only Auth0 CI application (password grant) and test user.
STAGING_AUTH0_TEST_DOMAIN: ${{ vars.STAGING_AUTH0_TEST_DOMAIN }}
STAGING_AUTH0_TEST_AUDIENCE: ${{ vars.STAGING_AUTH0_TEST_AUDIENCE }}
STAGING_AUTH0_TEST_CLIENT_ID: ${{ secrets.STAGING_AUTH0_TEST_CLIENT_ID }}
STAGING_AUTH0_TEST_CLIENT_SECRET: ${{ secrets.STAGING_AUTH0_TEST_CLIENT_SECRET }}
STAGING_AUTH0_TEST_USERNAME: ${{ secrets.STAGING_AUTH0_TEST_USERNAME }}
STAGING_AUTH0_TEST_PASSWORD: ${{ secrets.STAGING_AUTH0_TEST_PASSWORD }}
STAGING_AUTH0_TEST_REALM: ${{ vars.STAGING_AUTH0_TEST_REALM }}

parallel-live-simulation-tests-staging-cloud-run:
name: Run live simulations against the staging candidate
Expand Down Expand Up @@ -491,6 +502,12 @@ jobs:
DB_WRITE_POLICY: ${{ vars.DB_WRITE_POLICY }}
DB_READ_HOUSEHOLD: ${{ vars.DB_READ_HOUSEHOLD }}
DB_WRITE_HOUSEHOLD: ${{ vars.DB_WRITE_HOUSEHOLD }}
PUBLIC_API_AUTH_REQUIRED: ${{ vars.PUBLIC_API_AUTH_REQUIRED }}
PUBLIC_API_AUTH_ISSUER: ${{ vars.PUBLIC_API_AUTH_ISSUER }}
PUBLIC_API_AUTH_AUDIENCE: ${{ vars.PUBLIC_API_AUTH_AUDIENCE }}
AUTH0_MANAGEMENT_DOMAIN: ${{ vars.AUTH0_MANAGEMENT_DOMAIN }}
AUTH0_MANAGEMENT_CLIENT_ID: ${{ vars.AUTH0_MANAGEMENT_CLIENT_ID }}
AUTH0_MANAGEMENT_CLIENT_SECRET_RESOURCE: ${{ secrets.AUTH0_MANAGEMENT_CLIENT_SECRET_RESOURCE }}
CLOUD_RUN_SERVICE: policyengine-api
CLOUD_RUN_RUNTIME_CACHE_ENVIRONMENT: production
CLOUD_RUN_RUNTIME_CACHE_URL_SECRET: policyengine-api-prod-runtime-cache-url:latest
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/seed-v2-database.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ jobs:
run: uv run python scripts/qualify_v2_household_migration.py
env:
V2_MIGRATION_DATABASE_URL: ${{ secrets.V2_MIGRATION_DATABASE_URL }}
- name: Qualify the pending user-association reshape
run: uv run python scripts/qualify_v2_user_association_migration.py
env:
V2_MIGRATION_DATABASE_URL: ${{ secrets.V2_MIGRATION_DATABASE_URL }}
- name: Upgrade and verify the v2 schema
run: bash .github/scripts/migrate_v2_metadata_schema.sh
env:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/v2-integration-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ jobs:
run: uv run coverage run -a --branch -m pytest -q tests/integration/test_v2_catalog_publication.py tests/integration/test_v2_metadata_routes.py
- name: Test v2 policy persistence and immediate v1 mirroring
run: uv run coverage run -a --branch -m pytest -q tests/integration/test_v2_policy_persistence.py tests/integration/test_v1_policy_dual_write.py tests/integration/test_mysql_policy_dual_write.py tests/integration/test_v2_user_policy_mirroring.py tests/integration/test_v1_user_policy_dual_write.py
- name: Test signed-in identity provisioning and user associations
run: uv run coverage run -a --branch -m pytest -q tests/integration/test_v2_user_identity_persistence.py
- name: Test v2 household persistence and immediate v1 copying
run: uv run coverage run -a --branch -m pytest -q tests/integration/test_v2_household_persistence.py tests/integration/test_v1_household_dual_write.py tests/integration/test_mysql_household_spm_dual_write.py
- name: Qualify production-scale v2 metadata publication
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ To add or change a rule, read `docs/engineering/skills/rules-registry.md`.
- `api.v2.routes-no-sessions-or-sql`: v2 route modules never open database sessions or construct SQL; they call one service method and map typed failures to HTTP. (doc-only, not checked: follow it yourself)
- `api.v2.services-no-sql`: v2 services sequence work but never construct SQL expressions, call Session.exec, or define HTTP response models. (doc-only, not checked: follow it yourself)
- `api.v2.session-modules-no-queries`: v2 database_session modules own session lifetime only and never build select, insert, update, delete or exec calls. (blocking: pytest `tests/unit/v2/test_data_crud_boundaries.py`)
- `api.v2.user-services-no-sql`: v2 user services (identity, accounts, legacy references and user associations) never build or run SQL themselves or import SQLAlchemy; their connectors do. (blocking: pytest `tests/unit/v2/test_data_crud_boundaries.py`)
- `api.v2.validators-transformations-no-db`: v2 validators and transformations never import SQLAlchemy or SQLModel query or session APIs. (blocking: pytest `tests/unit/v2/test_data_crud_boundaries.py`)

### Runtime persistence and route boundaries
Expand Down Expand Up @@ -120,7 +121,7 @@ To add or change a rule, read `docs/engineering/skills/rules-registry.md`.

- `api.quality.alembic-revisions-unused-imports`: Alembic revision files are exempt from unused-import checks (F401), because autogenerated revisions import types they may not use. (advisory: ruff per-file ignore of F401 for `migrations/*/versions/*.py`, not blocking)
- `api.quality.environment-variables-load`: Required environment variables and credentials load in a deployed-like environment. (blocking: CI job `test_env_vars`)
- `api.quality.mypy-v2`: Shared query models, FastAPI dependencies, and v2 routes and services pass mypy with untyped definitions disallowed. (blocking: mypy)
- `api.quality.mypy-v2`: Shared query models, FastAPI dependencies, v2 routes and services, and the user sign-in and account-deletion libraries pass mypy with untyped definitions disallowed. (blocking: mypy)
- `api.quality.ruff-format`: All Python files are formatted with ruff format. (blocking: ruff format)
- `api.quality.ruff-lint`: Changed Python files pass ruff check with the rules selected in pyproject.toml (ruff's defaults, made explicit); agents run it before committing, but CI does not yet. (advisory: ruff check (E4, E7, E9, F), not blocking)
- `api.quality.tests-pass`: The unit, contract and to-refactor test suites pass. (blocking: CI job `test`)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add Auth0 user login and signed-out saving for app v3: /v2/me (provisioning, profile, export, upgrade of a browser's unauthenticated user at sign-in, account deletion), standard and unauthenticated user types identified by a bearer token or the PolicyEngine-Unauthenticated-User header, owner-scoped v1-anchored user associations for policies, households, simulations, and reports, and the user_identities schema.
Loading
Loading