Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ jobs:
- name: Run ruff format check
run: uv run ruff format --check projects/*/src libs/*/src

- name: Run ruff check
run: uv run ruff check .

quality-guards:
name: Quality guards
# Full CI runs only on the lowest unmerged pull request in a stack.
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ To add or change a rule, read `docs/engineering/skills/rules-registry.md`.
- `sim.quality.every-project-tested`: Every project and library with unit tests runs them in the PR test matrix. (doc-only, not checked: follow it yourself)
- `sim.quality.ruff-format-src`: Source code under projects/*/src and libs/*/src is formatted with ruff format. (blocking: ruff format)
- `sim.quality.ruff-format-tests`: Test code is formatted with ruff format too; CI currently checks only src directories. (doc-only, not checked: follow it yourself)
- `sim.quality.ruff-lint`: Python code passes ruff check with the rules selected in ruff.toml (ruff's defaults, made explicit); CI does not run ruff check yet. (advisory: ruff check (E4, E7, E9, F), not blocking)
- `sim.quality.ruff-lint`: All Python code passes ruff check with every rule family selected in ruff.toml, with no ratchet; families are added one themed group at a time until the selection is ALL. (blocking: ruff check (E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, UP, W))
- `sim.quality.tests-pass`: Each project's unit tests pass in its own locked environment. (blocking: CI job `test`)

### CI workflows and scripts
Expand Down
1 change: 1 addition & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Made CLAUDE.md and the Copilot instructions thin pointers to AGENTS.md, required workflow logic longer than one line to live in tested and shellchecked scripts, and registered the tracking-issue rule
- Added a rules registry (rules/registry.toml) with a standard-library tool that validates it, checks every rule is enforced where it claims, and renders docs/generated/rules.md and the Rules block in AGENTS.md
changed:
- Ran ruff check in CI with the first group of rule families (imports, pyupgrade and other safe auto-fixes) and fixed every finding
- Moved the multi-line `run` blocks of the simulation deployment workflows into single-line steps and tested scripts without changing their behaviour or outputs, and enforced the one-line `run` rule with an empty baseline
- Moved multi-line `run` blocks in the PR, image smoke and client publishing workflows into single-line steps and tested scripts, tested the dependency-update scripts, fixed the shellcheck finding in modal-precompute.sh, and enforced the CI-script testing and shellcheck rules with empty baselines
- Added CODEOWNERS for the rule machinery, pinned CI's ruff to 0.15.22 with an explicit rule selection, and documented that agents never bypass branch rules
Expand Down
17 changes: 8 additions & 9 deletions docs/generated/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ Every rule that governs `policyengine-sim-api` is listed here with how it is enf

| Tier | enforced | ratcheted | advisory | doc-only | Total |
| --- | ---: | ---: | ---: | ---: | ---: |
| mechanical | 29 | 0 | 1 | 14 | 44 |
| mechanical | 30 | 0 | 0 | 14 | 44 |
| metric | 0 | 0 | 0 | 0 | 0 |
| judgment | 0 | 0 | 0 | 0 | 0 |

**Blocking:** 29 of 44 rules.
**Blocking:** 30 of 44 rules.

## Gaps

Expand All @@ -35,7 +35,7 @@ Every rule that governs `policyengine-sim-api` is listed here with how it is enf

### Advisory (checked but not blocking)

- `sim.quality.ruff-lint`: Python code passes ruff check with the rules selected in ruff.toml (ruff's defaults, made explicit); CI does not run ruff check yet.
- None.

### Unproven (no proof that the checker rejects a bad example)

Expand Down Expand Up @@ -233,7 +233,7 @@ flowchart LR
| `sim.quality.every-project-tested` | Every project and library with unit tests runs them in the PR test matrix. | mechanical | doc-only | no | nothing | — | — |
| `sim.quality.ruff-format-src` | Source code under projects/*/src and libs/*/src is formatted with ruff format. | mechanical | enforced | yes | ruff format | `pr.yml:lint` | — |
| `sim.quality.ruff-format-tests` | Test code is formatted with ruff format too; CI currently checks only src directories. | mechanical | doc-only | no | nothing | — | — |
| `sim.quality.ruff-lint` | Python code passes ruff check with the rules selected in ruff.toml (ruff's defaults, made explicit); CI does not run ruff check yet. | mechanical | advisory | no | ruff check (E4, E7, E9, F) | — | — |
| `sim.quality.ruff-lint` | All Python code passes ruff check with every rule family selected in ruff.toml, with no ratchet; families are added one themed group at a time until the selection is ALL. | mechanical | enforced | yes | ruff check (E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, UP, W) | `pr.yml:lint` | — |
| `sim.quality.tests-pass` | Each project's unit tests pass in its own locked environment. | mechanical | enforced | yes | CI job `test` | `pr.yml:test` | — |

```mermaid
Expand All @@ -244,18 +244,17 @@ flowchart LR
e_ruff_format["ruff format"]
j_CI_pr_yml_lint["CI pr.yml:lint"]
r_sim_quality_ruff_format_tests("sim.quality.ruff-format-tests (doc-only)")
r_sim_quality_ruff_lint("sim.quality.ruff-lint (advisory)")
e_ruff_check__E4__E7__E9__F_["ruff check (E4, E7, E9, F)"]
local_only>"not run in CI"]
r_sim_quality_ruff_lint("sim.quality.ruff-lint (enforced)")
e_ruff_check__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__UP__W_["ruff check (E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, UP, W)"]
r_sim_quality_tests_pass("sim.quality.tests-pass (enforced)")
e_CI_job_test["CI job test"]
j_CI_pr_yml_test["CI pr.yml:test"]
r_sim_quality_every_project_tested -.-> gap_unenforced
r_sim_quality_ruff_format_src --> e_ruff_format
e_ruff_format --> j_CI_pr_yml_lint
r_sim_quality_ruff_format_tests -.-> gap_unenforced
r_sim_quality_ruff_lint --> e_ruff_check__E4__E7__E9__F_
e_ruff_check__E4__E7__E9__F_ -.-> local_only
r_sim_quality_ruff_lint --> e_ruff_check__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__UP__W_
e_ruff_check__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__UP__W_ --> j_CI_pr_yml_lint
r_sim_quality_tests_pass --> e_CI_job_test
e_CI_job_test --> j_CI_pr_yml_test
```
Expand Down
3 changes: 1 addition & 2 deletions libs/policyengine-fastapi/fixtures/ping/shared.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
"""Shared fixtures for ping endpoint tests."""

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
import pytest

from policyengine_fastapi import ping
from policyengine_fastapi.health import HealthRegistry

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,16 @@

from .observability import (
ObservabilityConfig as ObservabilityConfig,
)
from .observability import (
SimulationStage as SimulationStage,
)
from .observability import (
TracerCaptureMode as TracerCaptureMode,
)
from .observability import (
build_observability as build_observability,
)
from .observability import (
get_observability as get_observability,
)
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from .jwt_decoder import JWTDecoder

__all__ = ["JWTDecoder"]
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import Any
import logging

import jwt
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
import jwt
import logging

# Based on the auth0 article: https://auth0.com/blog/build-and-secure-fastapi-server-with-auth0/

Expand Down Expand Up @@ -35,8 +35,7 @@ def __call__(
if self.auto_error:
LOG.info("Invalid auth token: Throwing 4XX error")
raise err
else:
LOG.info("Ignoring invalid auth token")
LOG.info("Ignoring invalid auth token")
return None

def _validate(self, token: HTTPAuthorizationCredentials | None):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pathlib import Path
from typing import Any, Callable, Generator
from collections.abc import Callable, Generator
from typing import Any

from sqlalchemy import Engine, StaticPool, create_engine
from sqlmodel import Session

Expand Down
6 changes: 2 additions & 4 deletions libs/policyengine-fastapi/src/policyengine_fastapi/exit.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
"""

import logging
from collections.abc import Callable
from contextlib import asynccontextmanager
from functools import wraps
from typing import Any, Callable, TypeVar, cast

from fastapi import FastAPI
from typing import Any, TypeVar

log = logging.getLogger(__name__)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from typing import Callable, Optional
from pydantic import BaseModel, Field
from collections.abc import Callable

from pydantic import BaseModel


class ProbeStatus(BaseModel):
name: str
healthy: bool
message: Optional[str] = None
message: str | None = None


class SystemStatus(BaseModel):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,52 @@

from .config import (
ObservabilityConfig as ObservabilityConfig,
)
from .config import (
parse_header_value_pairs as parse_header_value_pairs,
)
from .contracts import (
SimulationCompositeTraceResponse as SimulationCompositeTraceResponse,
)
from .contracts import (
SimulationLifecycleEvent as SimulationLifecycleEvent,
)
from .contracts import (
SimulationRunSummary as SimulationRunSummary,
)
from .contracts import (
SimulationTimelineEntry as SimulationTimelineEntry,
)
from .contracts import (
TracerArtifactManifest as TracerArtifactManifest,
)
from .contracts import (
VersionStageMetricResponse as VersionStageMetricResponse,
)
from .correlation import (
generate_observability_id as generate_observability_id,
)
from .correlation import (
stable_config_hash as stable_config_hash,
)
from .emitters import (
Observability as Observability,
NoOpObservability as NoOpObservability,
)
from .emitters import (
NoOpSpan as NoOpSpan,
)
from .emitters import (
Observability as Observability,
)
from .provider import (
build_observability as build_observability,
)
from .provider import (
get_observability as get_observability,
)
from .stages import (
SimulationStage as SimulationStage,
)
from .stages import (
TracerCaptureMode as TracerCaptureMode,
)
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from hashlib import sha256
import json
from hashlib import sha256
from typing import Any
from uuid import uuid4

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import annotations

from collections.abc import Mapping
from contextlib import AbstractContextManager
from dataclasses import dataclass, field
from typing import Protocol
from typing import Any, Mapping
from typing import Any, Protocol

from .config import ObservabilityConfig
from .contracts import SimulationLifecycleEvent, TracerArtifactManifest
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from .console import export_ot_to_console as export_ot_to_console
from .gcp import (
GCPLoggingInstrumentor as GCPLoggingInstrumentor,
)
from .gcp import (
export_ot_to_gcp as export_ot_to_gcp,
)
from .console import export_ot_to_console as export_ot_to_console
from .instrumentor import (
FastAPIEnhancedInstrumenter as FastAPIEnhancedInstrumenter,
)
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
from opentelemetry import metrics, trace
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import (
ConsoleMetricExporter,
PeriodicExportingMetricReader,
)
from opentelemetry.sdk.resources import (
SERVICE_NAME,
SERVICE_INSTANCE_ID,
Resource,
)

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
BatchSpanProcessor,
ConsoleSpanExporter,
)

from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import (
PeriodicExportingMetricReader,
ConsoleMetricExporter,
)
from policyengine_fastapi.exit import exit


Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import logging
import os
from opentelemetry.sdk.resources import Resource

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

from opentelemetry import metrics
from opentelemetry import metrics, trace
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

from policyengine_fastapi.exit import exit

Expand Down Expand Up @@ -96,10 +94,10 @@ def instrument(self):


def export_ot_to_gcp(resource: Resource):
from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
from opentelemetry.exporter.cloud_monitoring import (
CloudMonitoringMetricsExporter,
)
from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter

"""
configure opentelemetry to directly export to gcp cloudtrace/metrics
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from fastapi import FastAPI
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

from .middleware import Middleware
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor


class FastAPIEnhancedInstrumenter:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from dataclasses import dataclass
from time import time
from typing import Any

from fastapi import FastAPI, Request
from fastapi.routing import APIRoute
from opentelemetry import metrics
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from fastapi import FastAPI, APIRouter
from fastapi import APIRouter, FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel

from policyengine_fastapi.health import HealthRegistry, HealthStatus


Expand Down
5 changes: 3 additions & 2 deletions libs/policyengine-fastapi/tests/test_exit.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from unittest.mock import Mock, call
from policyengine_fastapi.exit import exit

import pytest
from policyengine_fastapi.exit import exit


@pytest.mark.asyncio
Expand All @@ -14,7 +15,7 @@ async def test_lifecycle_executes_callbacks__when_app_ends():

exit()(callback)

async with exit.lifespan() as resource:
async with exit.lifespan():
app()

parent.assert_has_calls([call.app(), call.callback()])
2 changes: 1 addition & 1 deletion libs/policyengine-fastapi/tests/test_observability.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime, UTC
from datetime import UTC, datetime

from policyengine_fastapi.observability import (
NoOpObservability,
Expand Down
Loading
Loading