Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5e1a2cc
Centralize API v1 observability
anth-volk Sep 21, 2026
1052052
Make draft dependency available to pip
anth-volk Sep 21, 2026
969f181
Allow pinned draft dependency source
anth-volk Sep 22, 2026
1c0fa51
Own API v1 observability deployment and instrumentation
anth-volk Sep 22, 2026
5eb5a31
Require full observability trace sampling
anth-volk Sep 22, 2026
bce388e
Use released observability package
anth-volk Sep 23, 2026
e83f0dd
Add durable observability IDs and stage registry
anth-volk Sep 23, 2026
e221d74
Use observability header as canonical transport
anth-volk Sep 24, 2026
f2cc832
Complete native request observability and Stage 12 IAM
anth-volk Sep 24, 2026
aa4a688
Keep budget-window observability IDs durable
anth-volk Sep 24, 2026
3fccd86
Bind observability IDs at API workflow boundaries
anth-volk Sep 28, 2026
066230f
Document observability identifier lifecycle
anth-volk Sep 28, 2026
5d3aee7
Keep API selected observability IDs canonical
anth-volk Sep 28, 2026
fa22d1a
Document uncapped observability attributes
anth-volk Sep 28, 2026
4f830e1
Propagate observability IDs through API spans
anth-volk Sep 28, 2026
9fabc3a
Use safe scalar observability attributes
anth-volk Sep 28, 2026
9781208
Keep consumer test compatible before package release
anth-volk Sep 28, 2026
2219970
Require observability 3.0.1
anth-volk Sep 28, 2026
0d8bd57
Prevent telemetry failures from changing API behavior
anth-volk Sep 28, 2026
e493e6e
Bind budget window identity after claim acquisition
anth-volk Sep 28, 2026
21a27f8
Validate observability boundaries before binding
anth-volk Sep 28, 2026
f7de372
Preserve observability IDs across annual claims
anth-volk Sep 29, 2026
b8939b0
Complete observability span and log routing coverage
anth-volk Sep 29, 2026
0c00187
Remove one-time observability setup scripts
anth-volk Sep 29, 2026
8420793
Remove one-time Modal identity verifier
anth-volk Sep 29, 2026
06b32b4
Remove dormant observability operator scripts
anth-volk Sep 29, 2026
c072677
Remove unused alert policy template
anth-volk Sep 29, 2026
7474086
Remove unmanaged observability templates
anth-volk Sep 29, 2026
2054c27
Use typed budget window cache validation
anth-volk Sep 29, 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
9 changes: 9 additions & 0 deletions .github/scripts/deploy_cloud_run_candidate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ cloud_run_set_defaults
bash .github/scripts/validate_cloud_run_deploy_env.sh

env_vars=(
"APP_ENVIRONMENT=${DEPLOYMENT_ENVIRONMENT}"
"POLICYENGINE_DB_INSTANCE_CONNECTION_NAME=${POLICYENGINE_DB_INSTANCE_CONNECTION_NAME}"
"POLICYENGINE_DB_USER=${POLICYENGINE_DB_USER:-policyengine}"
"POLICYENGINE_DB_NAME=${POLICYENGINE_DB_NAME:-policyengine}"
Expand All @@ -32,6 +33,14 @@ env_vars=(
"RUNTIME_CACHE_MODE=deployed"
"RUNTIME_CACHE_ENVIRONMENT=${CLOUD_RUN_RUNTIME_CACHE_ENVIRONMENT}"
"RUNTIME_CACHE_SERVICE=api"
"OBSERVABILITY_SERVICE_NAMESPACE=${OBSERVABILITY_SERVICE_NAMESPACE}"
"OBSERVABILITY_TRACE_PROJECT_ID=${OBSERVABILITY_TRACE_PROJECT_ID}"
"OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_EXPORTER_OTLP_ENDPOINT}"
"OTEL_EXPORTER_OTLP_PROTOCOL=grpc"
"OTEL_TRACES_EXPORTER=otlp"
"OTEL_METRICS_EXPORTER=otlp"
"OTEL_TRACES_SAMPLER_ARG=1.0"
"POLICYENGINE_OTEL_GOOGLE_AUDIENCE=${POLICYENGINE_OTEL_GOOGLE_AUDIENCE}"
"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}"
Expand Down
4 changes: 4 additions & 0 deletions .github/scripts/validate_cloud_run_deploy_env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ cloud_run_require_env \
CLOUD_RUN_VPC_NETWORK \
CLOUD_RUN_VPC_SUBNET \
CLOUD_RUN_VPC_EGRESS \
OBSERVABILITY_SERVICE_NAMESPACE \
OBSERVABILITY_TRACE_PROJECT_ID \
OTEL_EXPORTER_OTLP_ENDPOINT \
POLICYENGINE_OTEL_GOOGLE_AUDIENCE \
V2_SUPABASE_PROJECT_REF \
V2_SUPABASE_ENVIRONMENT \
V2_RUNTIME_DATABASE_URL_SECRET_RESOURCE \
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@ jobs:
GATEWAY_AUTH_AUDIENCE: ${{ secrets.GATEWAY_AUTH_AUDIENCE }}
GATEWAY_AUTH_CLIENT_ID: ${{ secrets.GATEWAY_AUTH_CLIENT_ID }}
GATEWAY_AUTH_CLIENT_SECRET_RESOURCE: ${{ secrets.GATEWAY_AUTH_CLIENT_SECRET_RESOURCE }}
OBSERVABILITY_SERVICE_NAMESPACE: ${{ vars.OBSERVABILITY_SERVICE_NAMESPACE }}
OBSERVABILITY_TRACE_PROJECT_ID: ${{ vars.OBSERVABILITY_TRACE_PROJECT_ID }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.OBSERVABILITY_OTLP_ENDPOINT }}
POLICYENGINE_OTEL_GOOGLE_AUDIENCE: ${{ vars.OBSERVABILITY_OTLP_GOOGLE_AUDIENCE }}
- name: Resolve exact Cloud Run staging candidate
id: candidate
run: bash .github/scripts/resolve_cloud_run_candidate_state.sh >> "$GITHUB_OUTPUT"
Expand Down Expand Up @@ -497,6 +501,10 @@ jobs:
GATEWAY_AUTH_AUDIENCE: ${{ secrets.GATEWAY_AUTH_AUDIENCE }}
GATEWAY_AUTH_CLIENT_ID: ${{ secrets.GATEWAY_AUTH_CLIENT_ID }}
GATEWAY_AUTH_CLIENT_SECRET_RESOURCE: ${{ secrets.GATEWAY_AUTH_CLIENT_SECRET_RESOURCE }}
OBSERVABILITY_SERVICE_NAMESPACE: ${{ vars.OBSERVABILITY_SERVICE_NAMESPACE }}
OBSERVABILITY_TRACE_PROJECT_ID: ${{ vars.OBSERVABILITY_TRACE_PROJECT_ID }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.OBSERVABILITY_OTLP_ENDPOINT }}
POLICYENGINE_OTEL_GOOGLE_AUDIENCE: ${{ vars.OBSERVABILITY_OTLP_GOOGLE_AUDIENCE }}
- name: Resolve exact Cloud Run production candidate
id: candidate
run: bash .github/scripts/resolve_cloud_run_candidate_state.sh >> "$GITHUB_OUTPUT"
Expand Down
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ migration revisions, read
When adding or moving API v2 route, service, or database-access modules, read
`docs/engineering/skills/v2-code-organization.md`.

When changing correlation identifiers, telemetry transport, spans, stage
names, logging, or observability failure handling, read
`docs/engineering/skills/observability.md`.

When modifying the `Makefile`, read
`docs/engineering/skills/repository-maintenance.md`.

Expand Down
3 changes: 3 additions & 0 deletions changelog.d/3847.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Route API v1 structured logs, traces, and metrics through the explicit
policyengine-observability 3.x runtime and propagate request context to
the simulation entry service.
3 changes: 3 additions & 0 deletions docs/engineering/skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ Current skills:
- `github-prs.md`: PR workflow and migration PR handoff expectations.
- `migration_contracts.md`: API v2 migration route contracts, route-group
metadata, generated migration artifacts, and quality guards.
- `observability.md`: identifier ownership, HTTP and simulation transport,
persistence, registered runtime stages, trace boundaries, and failure
isolation.
- `repository-maintenance.md`: mandatory Makefile target and `.PHONY`
maintenance rules.
- `testing.md`: focused test commands and dependency boundaries for migration
Expand Down
122 changes: 122 additions & 0 deletions docs/engineering/skills/observability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Observability engineering rules

Read this file before changing request correlation, calculation correlation,
logging, traces, metrics, simulation requests, or runtime stage names.

## Identifier registry

Use each identifier for its defined scope:

| Identifier | Scope | Created by | Durable |
| --- | --- | --- | --- |
| `request_id` | One HTTP request | HTTP request instrumentation | No |
| `observability_id` | One complete household calculation or society report | The first service that accepts the calculation | Yes for asynchronous reports |
| `submission_claim_id` | One attempt to acquire ownership of a simulation submission | API v1 economy service | Only as submission metadata |
| `job_id` | One annual simulation job | Simulation API | Yes, as functional job state |
| `batch_job_id` | One budget window simulation job | Simulation API | Yes, as functional batch state |
| `evaluation_id` | One Stage 12 comparison report | Stage 12 report construction | Yes, as functional report state |
| `simulation_execution_id` | One Stage 12 baseline or reform simulation | Stage 12 coordinator | Yes, as functional simulation state |

`observability_id` is a canonical UUID string used only to query diagnostic
records. Do not use it for idempotency, cache ownership, authorization,
database identity, routing, or calculation behavior. Do not create one for
health checks, metadata reads, invalid requests, missing reports, or ordinary
status requests.

## HTTP lifecycle

Transport `observability_id` only in
`X-PolicyEngine-Observability-Id`. HTTP middleware validates an incoming value
and stores it as a candidate. Middleware must not bind it or generate a new
value for every request.

A calculation boundary calls `start_observability_id`. This uses an already
bound value, then a valid incoming candidate, and otherwise creates a UUID. It
binds the selected value to the request and observability runtime. Household
calculation routes call it only after request validation. On a cache miss, the
route constructs and validates the PolicyEngine situation, binds the identifier
before the successful normalization span ends, and reuses that prepared
simulation for the calculation. Pre-acceptance validation must not emit a run
stage span that cannot carry the selected identifier. A successful cache hit
binds the identifier after the cache returns. A request that fails situation
parsing must never call `start_observability_id`.

For a new economy report, acquire submission ownership before calling
`start_observability_id`. Persist the selected identifier in the report state
written after submission. A request that reads existing report state calls
`restore_observability_id`; a persisted value is authoritative even when the
polling request supplies a different header. Older records with a null value
remain null. Never create a replacement identifier while polling.

The HTTP response contains the header only when the request started a
calculation, continued one synchronously, or restored an existing report
identifier.

## Simulation client transport

The simulation HTTP client reads identifiers from request context. Its request
hook sends `request_id` and any bound `observability_id` in their respective
headers. API v1 never replaces its selected identifier with a value from an
HTTP response.

Do not add `observability_id` to a simulation JSON body, `_telemetry`, or
execution result data class. The simulation API carries it through HTTP headers,
persists it beside functional job state, and transports captured trace context
to Modal as a separate function argument.

## Runtime stages

All API v1 span names for supported calculation configurations are defined in
`policyengine_api.observability.stages`. Runtime code imports the applicable
`StagePlan` and calls `plan.name(Stage.VALUE)`. Do not add span name string
literals in route or service code.

When adding a calculation configuration or stage:

1. Add it to `RunConfiguration` or `Stage`.
2. Add it to the applicable plan in `RUN_STAGE_REGISTRY`.
3. Import that plan in runtime code.
4. Add focused tests for the stage and identifier lifecycle.

## Trace boundaries

HTTP instrumentation carries W3C trace context across synchronous calls. The
simulation API carries that trace context through asynchronous Modal dispatch.
The submission and worker work can therefore form one distributed trace.
Native ASGI instrumentation must start request spans with the matched route
template. Never use the unresolved request path as a span name.

A later polling request starts a new trace. Its logs and spans share the
persisted `observability_id` with the submission trace. Measure a complete
report by querying all diagnostic records with that identifier, then use the
registered stage names to break down elapsed time.

When an economy report selects or restores its identifier inside a nested
stage, reapply the identifier after that stage exits so each containing economy
span receives it. HTTP completion handling must reapply a bound identifier
before ending the server request span. Keep these calls behind local exception
boundaries so a runtime failure cannot change the response.

Stage 12 authoritative and comparison executions use the same
`observability_id` as the report that dispatched them. Their `evaluation_id`
and simulation execution identifiers remain separate functional identifiers.

## Failure behavior

Invalid observability configuration must fail during build or deployment
validation. After a service starts serving application traffic, logging,
tracing, metrics, context binding, and export failures must not alter an
application result or HTTP status.

Normalize all identifiers received from callers or downstream services. Keep a
local exception boundary around runtime context binding because observability
package failures must not escape into request processing.

Focused tests must cover:

- identifier creation at calculation submission boundaries;
- HTTP propagation to the simulation API;
- persistence and restoration during polling;
- persisted values taking precedence over polling headers;
- older records with null identifiers;
- observability failures leaving application results unchanged.
Loading
Loading