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
6 changes: 6 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
Follow the repository's canonical engineering skills under
`docs/engineering/skills/`.

For a new HTTP route or an existing route query-parameter contract change,
read `docs/engineering/skills/api-routes.md`.

For API v2 migration contract, route-group metadata, generated migration docs,
or migration guard changes, read
`docs/engineering/skills/migration_contracts.md`.
Expand All @@ -13,5 +16,8 @@ reviewing test files.
For SQLAlchemy model or Alembic migration work, read
`docs/engineering/skills/alembic-migrations.md`.

For API v2 route, service, or database-access module additions or moves, read
`docs/engineering/skills/v2-code-organization.md`.

For pull requests, read `docs/engineering/skills/github-prs.md` before opening,
replacing, or sharing a PR.
2 changes: 1 addition & 1 deletion .github/scripts/cloud_run_env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ cloud_run_set_defaults() {
# but adds no real scale-out delay because boot exceeds it either way. Sizing
# rationale in docs/migration/cloud-run-operations.md.
CLOUD_RUN_STARTUP_PROBE="${CLOUD_RUN_STARTUP_PROBE:-httpGet.path=/readiness-check,httpGet.port=${CLOUD_RUN_PORT},initialDelaySeconds=240,periodSeconds=10,failureThreshold=24,timeoutSeconds=5}"
CLOUD_RUN_POLICYENGINE_DB_PASSWORD_SECRET="${CLOUD_RUN_POLICYENGINE_DB_PASSWORD_SECRET:-policyengine-api-prod-db-password:latest}"
CLOUD_RUN_POLICYENGINE_DB_PASSWORD_SECRET="${CLOUD_RUN_POLICYENGINE_DB_PASSWORD_SECRET:-}"
CLOUD_RUN_GITHUB_MICRODATA_TOKEN_SECRET="${CLOUD_RUN_GITHUB_MICRODATA_TOKEN_SECRET:-policyengine-api-prod-github-microdata-token:latest}"
CLOUD_RUN_OPENAI_API_KEY_SECRET="${CLOUD_RUN_OPENAI_API_KEY_SECRET:-policyengine-api-prod-openai-api-key:latest}"
CLOUD_RUN_HUGGING_FACE_TOKEN_SECRET="${CLOUD_RUN_HUGGING_FACE_TOKEN_SECRET:-policyengine-api-prod-hugging-face-token:latest}"
Expand Down
10 changes: 9 additions & 1 deletion .github/scripts/deploy_cloud_run_candidate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ env_vars=(
"ROUTE_IMPL_HEALTH=${ROUTE_IMPL_HEALTH}"
"ROUTE_IMPL_SPECIFICATION=${ROUTE_IMPL_SPECIFICATION}"
"ROUTE_IMPL_METADATA=${ROUTE_IMPL_METADATA}"
"ROUTE_IMPL_POLICY=${ROUTE_IMPL_POLICY}"
"DB_READ_POLICY=${DB_READ_POLICY}"
"DB_WRITE_POLICY=${DB_WRITE_POLICY}"
"SIM_COMPUTE_ECONOMY=old_gateway"
"CLOUD_RUN_REVISION_TAG=${CLOUD_RUN_TAG}"
"WEB_CONCURRENCY=${CLOUD_RUN_WEB_CONCURRENCY}"
Expand All @@ -37,6 +40,11 @@ fi
if [[ -n "${SIMULATION_ENTRYPOINT_URL:-}" ]]; then
env_vars+=("SIMULATION_ENTRYPOINT_URL=${SIMULATION_ENTRYPOINT_URL}")
fi
if [[ -n "${POLICYENGINE_API_STARTUP_WARMUP:-}" ]]; then
env_vars+=(
"POLICYENGINE_API_STARTUP_WARMUP=${POLICYENGINE_API_STARTUP_WARMUP}"
)
fi

secret_vars=(
"POLICYENGINE_DB_PASSWORD=${CLOUD_RUN_POLICYENGINE_DB_PASSWORD_SECRET}"
Expand All @@ -63,7 +71,7 @@ cloud_run_run gcloud run deploy "${CLOUD_RUN_SERVICE}" \
--subnet "${CLOUD_RUN_VPC_SUBNET}" \
--vpc-egress "${CLOUD_RUN_VPC_EGRESS}" \
--service-account "${CLOUD_RUN_RUNTIME_SERVICE_ACCOUNT}" \
--add-cloudsql-instances "${POLICYENGINE_DB_INSTANCE_CONNECTION_NAME}" \
--set-cloudsql-instances "${POLICYENGINE_DB_INSTANCE_CONNECTION_NAME}" \
--port "${CLOUD_RUN_PORT}" \
--cpu "${CLOUD_RUN_CPU}" \
--cpu-boost \
Expand Down
8 changes: 6 additions & 2 deletions .github/scripts/migrate_v1_cloud_sql.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
set -euo pipefail

: "${POLICYENGINE_DB_INSTANCE_CONNECTION_NAME:?POLICYENGINE_DB_INSTANCE_CONNECTION_NAME is required}"
: "${POLICYENGINE_DB_READONLY_PASSWORD_SECRET:?POLICYENGINE_DB_READONLY_PASSWORD_SECRET is required}"
: "${POLICYENGINE_DB_MIGRATION_PASSWORD_SECRET:?POLICYENGINE_DB_MIGRATION_PASSWORD_SECRET is required}"

bash .github/scripts/validate_database_environment.sh cloud-sql

readonly_password="$(
gcloud secrets versions access latest \
--secret policyengine-api-prod-db-readonly-password \
--secret "${POLICYENGINE_DB_READONLY_PASSWORD_SECRET}" \
--project policyengine-api
)"
migration_password="$(
gcloud secrets versions access latest \
--secret policyengine-api-prod-db-migration-password \
--secret "${POLICYENGINE_DB_MIGRATION_PASSWORD_SECRET}" \
--project policyengine-api
)"

Expand Down
71 changes: 64 additions & 7 deletions .github/scripts/resolve_cloud_run_candidate_state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -78,26 +78,32 @@ image="$(jq -er '
| select(type == "string" and contains("@sha256:"))
' <<<"${revision_json}")"

route_selector_count=0
deployment_selector_count=0
for selector in \
ROUTE_IMPL_HEALTH \
ROUTE_IMPL_SPECIFICATION \
ROUTE_IMPL_METADATA; do
ROUTE_IMPL_METADATA \
ROUTE_IMPL_POLICY \
DB_READ_POLICY \
DB_WRITE_POLICY; do
if [[ -n "${!selector:-}" ]]; then
route_selector_count=$((route_selector_count + 1))
deployment_selector_count=$((deployment_selector_count + 1))
fi
done

if (( route_selector_count > 0 && route_selector_count < 3 )); then
echo "All Stage 6 route selectors are required when verifying candidate configuration" >&2
if (( deployment_selector_count > 0 && deployment_selector_count < 6 )); then
echo "All route and policy database selectors are required when verifying candidate configuration" >&2
exit 2
fi

if (( route_selector_count == 3 )); then
if (( deployment_selector_count == 6 )); then
for selector in \
ROUTE_IMPL_HEALTH \
ROUTE_IMPL_SPECIFICATION \
ROUTE_IMPL_METADATA; do
ROUTE_IMPL_METADATA \
ROUTE_IMPL_POLICY \
DB_READ_POLICY \
DB_WRITE_POLICY; do
expected_value="${!selector}"
actual_value="$(jq -r --arg name "${selector}" '
[
Expand All @@ -116,6 +122,57 @@ if (( route_selector_count == 3 )); then
done
fi

database_identity_count=0
for setting in \
POLICYENGINE_DB_INSTANCE_CONNECTION_NAME \
V2_SUPABASE_PROJECT_REF \
V2_SUPABASE_ENVIRONMENT \
V2_RUNTIME_DATABASE_URL_SECRET_RESOURCE; do
if [[ -n "${!setting:-}" ]]; then
database_identity_count=$((database_identity_count + 1))
fi
done

if (( database_identity_count > 0 && database_identity_count < 4 )); then
echo "All database identity settings are required when verifying candidate configuration" >&2
exit 2
fi

if (( database_identity_count == 4 )); then
for setting in \
POLICYENGINE_DB_INSTANCE_CONNECTION_NAME \
V2_SUPABASE_PROJECT_REF \
V2_SUPABASE_ENVIRONMENT \
V2_RUNTIME_DATABASE_URL_SECRET_RESOURCE; do
expected_value="${!setting}"
actual_value="$(jq -r --arg name "${setting}" '
[
.spec.containers[0].env[]?
| select(.name == $name)
| .value
]
| if length == 1 then .[0] else "" end
' <<<"${revision_json}")"
if [[ "${actual_value}" != "${expected_value}" ]]; then
printf 'Revision %s has %s=%s; expected %s\n' \
"${revision}" "${setting}" "${actual_value:-<missing>}" \
"${expected_value}" >&2
exit 2
fi
done

attached_cloud_sql="$(jq -r '
.metadata.annotations["run.googleapis.com/cloudsql-instances"] // empty
' <<<"${revision_json}")"
if [[ "${attached_cloud_sql}" != \
"${POLICYENGINE_DB_INSTANCE_CONNECTION_NAME}" ]]; then
printf 'Revision %s attaches Cloud SQL instance %s; expected %s\n' \
"${revision}" "${attached_cloud_sql:-<missing>}" \
"${POLICYENGINE_DB_INSTANCE_CONNECTION_NAME}" >&2
exit 2
fi
fi

if [[ -n "${CLOUD_RUN_EXPECTED_REVISION:-}" \
&& "${revision}" != "${CLOUD_RUN_EXPECTED_REVISION}" ]]; then
printf 'Candidate tag %s moved: expected revision %s, found %s\n' \
Expand Down
36 changes: 36 additions & 0 deletions .github/scripts/run_phase10_staging_probe.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env bash

set -euo pipefail

: "${POLICYENGINE_DB_READONLY_PASSWORD_SECRET:?POLICYENGINE_DB_READONLY_PASSWORD_SECRET is required}"

case "${1:-}" in
activation)
test_name="test_live_phase10_activation_failure_and_retry"
;;
rollback)
test_name="test_live_phase10_cloud_sql_only_rollback"
;;
*)
echo "Usage: $0 {activation|rollback}" >&2
exit 1
;;
esac

readonly_password="$(
gcloud secrets versions access latest \
--secret "${POLICYENGINE_DB_READONLY_PASSWORD_SECRET}" \
--project policyengine-api
)"
if [[ -z "${readonly_password}" ]]; then
echo "The staging Cloud SQL read-only password must not be empty." >&2
exit 1
fi

if [[ "${GITHUB_ACTIONS:-}" == "true" ]]; then
printf '::add-mask::%s\n' "${readonly_password}"
fi
POLICYENGINE_DB_READONLY_PASSWORD="${readonly_password}" \
python -m pytest \
"tests/integration/test_live_phase10_staging.py::${test_name}" \
-v
23 changes: 22 additions & 1 deletion .github/scripts/validate_cloud_run_deploy_env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ source .github/scripts/cloud_run_env.sh
source .github/scripts/simulation_entrypoint_env.sh
cloud_run_set_defaults

bash .github/scripts/validate_database_environment.sh runtime

# Cloud Run rejects deploys where the traffic tag and service name together
# exceed 46 characters (they form the tag URL's DNS label). Fail fast here
# with a clear message instead of at gcloud.
Expand Down Expand Up @@ -41,6 +43,9 @@ cloud_run_require_env \
ROUTE_IMPL_HEALTH \
ROUTE_IMPL_SPECIFICATION \
ROUTE_IMPL_METADATA \
ROUTE_IMPL_POLICY \
DB_READ_POLICY \
DB_WRITE_POLICY \
GATEWAY_AUTH_ISSUER \
GATEWAY_AUTH_AUDIENCE \
GATEWAY_AUTH_CLIENT_ID \
Expand All @@ -49,7 +54,8 @@ cloud_run_require_env \
for selector in \
ROUTE_IMPL_HEALTH \
ROUTE_IMPL_SPECIFICATION \
ROUTE_IMPL_METADATA; do
ROUTE_IMPL_METADATA \
ROUTE_IMPL_POLICY; do
value="${!selector}"
case "${value}" in
flask_fallback|fastapi_native) ;;
Expand All @@ -61,6 +67,21 @@ for selector in \
esac
done

if [[ "${DB_READ_POLICY}" != "cloud_sql" ]]; then
printf '%s=%s is invalid; expected cloud_sql\n' \
"DB_READ_POLICY" "${DB_READ_POLICY}" >&2
exit 1
fi

case "${DB_WRITE_POLICY}" in
cloud_sql|dual_write) ;;
*)
printf '%s=%s is invalid; expected cloud_sql or dual_write\n' \
"DB_WRITE_POLICY" "${DB_WRITE_POLICY}" >&2
exit 1
;;
esac

selected_url_env="$(
simulation_entrypoint_url_env_name "${SIM_ENTRYPOINT}"
)"
Expand Down
Loading
Loading