diff --git a/.dockerignore b/.dockerignore index 344df40..3cd3af9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -35,3 +35,6 @@ docs/ .idea/ *.swp .DS_Store + +# Sibling repo checked out into the workspace by CI — must never reach the image. +/aurora-sensor-agent/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77c0b07..c5f7da5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -158,6 +158,14 @@ jobs: - uses: actions/checkout@v7 with: fetch-depth: 0 # need the base commit for the oasdiff comparison + # Cross-repo telemetry drift needs the device repo. Set the DEVICE_REPO variable (e.g. + # owner/aurora-sensor-agent) to enable it; without it the job runs the in-repo checksum + # guard only and stays green, so a fork with no variables set still passes. + - if: vars.DEVICE_REPO != '' + uses: actions/checkout@v7 + with: + repository: ${{ vars.DEVICE_REPO }} + path: aurora-sensor-agent - uses: ./.github/actions/setup-python-uv # 1. Drift: the served schema must equal the committed contracts/openapi.json. - name: OpenAPI drift check @@ -166,6 +174,11 @@ jobs: # checksums (and, when the device repo is checked out alongside, be byte-identical to its # source). The OpenAPI gate in reverse — spec §8 rule 4. - name: Telemetry contract drift check + env: + # Set: the sibling checkout above lands at /aurora-sensor-agent. + # Unset (a fork): falls back to ../, which is absent in CI, so the script skips the + # cross-repo half and keeps the in-repo checksum guard. ../ is also the local-dev layout. + AURORA_DEVICE_REPO: ${{ vars.DEVICE_REPO != '' && 'aurora-sensor-agent' || '../aurora-sensor-agent' }} run: uv run python scripts/check_telemetry_contract.py - name: Publish OpenAPI as a build artifact uses: actions/upload-artifact@v7 diff --git a/.gitignore b/.gitignore index 0ba980f..7e156f3 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ _*.log .vscode/ *.swp .DS_Store + +# Sibling repo checked out into the workspace by CI (cross-repo contract drift gate). +/aurora-sensor-agent/ diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 0000000..88b9597 --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1,15 @@ +# Gitleaks findings reviewed and accepted, one fingerprint per line. +# +# Scope note: on a pull request the gitleaks action scans only the pushed commits, so these +# historical findings are invisible there. On workflow_dispatch — and for anyone who clones the +# repo and runs `gitleaks detect` — it scans the full history and surfaces them. This file exists +# so a full-history scan is clean too, rather than the repo merely appearing clean because of +# where the scan happened to look. +# +# Each entry must be a reviewed false positive, never a real credential that has been rotated: +# a committed secret stays in the history and must be revoked at the source, not ignored here. + +# tests/integration/test_webhooks_api.py:27 — the webhook subscription fixture's signing secret, +# the literal "0123456789abcdef". A sequential hex string written to be readable in test output; +# it signs nothing outside the test process. Flagged by the generic-api-key rule on entropy alone. +7a2b78bba835061243e0d8b24acdffd09ffec1d0:tests/integration/test_webhooks_api.py:generic-api-key:27 diff --git a/README.md b/README.md index 2e22ce9..e4e18e2 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,9 @@ make stack-up # Postgres + Redis + the API on http://localhost:8000 make seed # demo users, clinic, clinician, service ``` -Demo login: `admin@aurora-clinic.com` / `password123` (local demo only). +Demo login: `admin@aurora-clinic.com` / `password123` (local demo only). The browser demo uses +different addresses (`admin@aurora.test`) because it runs on the mock API; this one rejects the +reserved `.test` TLD. | Tool | How | Notes | |---|---|---| diff --git a/docs/KNOWN_GAPS.md b/docs/KNOWN_GAPS.md index c48f5ad..7e5a4ef 100644 --- a/docs/KNOWN_GAPS.md +++ b/docs/KNOWN_GAPS.md @@ -73,6 +73,11 @@ CI/CD in milestone 11. ## CI/CD: what could not be executed in this environment (milestone 6) +**Every gate that can run without a cloud account runs on every pull request** — lint, typecheck, +unit, integration against real Postgres and Redis, contract, coverage, build, security scanning, +and CodeQL. The list below is only the deploy path, which needs infrastructure this environment +does not have. + The workflows are complete and statically valid (`actionlint` + `yamllint` pass locally, wired into `make ci-local`). What could not be *run* here, and why: diff --git a/pyproject.toml b/pyproject.toml index 58eac68..d697e30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,8 +32,8 @@ dependencies = [ [project.optional-dependencies] dev = [ - "pytest>=8.3,<9", - "pytest-asyncio>=0.24,<0.25", + "pytest>=9.0.3,<10", + "pytest-asyncio>=1.0,<2", "pytest-cov>=5.0,<8", "pytest-mock>=3.14,<4", "pytest-xdist>=3.6,<4", diff --git a/uv.lock b/uv.lock index 38a935a..3d42f4f 100644 --- a/uv.lock +++ b/uv.lock @@ -131,8 +131,8 @@ requires-dist = [ { name = "pydantic", extras = ["email"], specifier = ">=2.9,<3" }, { name = "pydantic-settings", specifier = ">=2.5,<3" }, { name = "pyjwt", specifier = ">=2.9,<3" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3,<9" }, - { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.24,<0.25" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.3,<10" }, + { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=1.0,<2" }, { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=5.0,<8" }, { name = "pytest-mock", marker = "extra == 'dev'", specifier = ">=3.14,<4" }, { name = "pytest-xdist", marker = "extra == 'dev'", specifier = ">=3.6,<4" }, @@ -2125,7 +2125,7 @@ wheels = [ [[package]] name = "pytest" -version = "8.4.2" +version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -2134,21 +2134,22 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] [[package]] name = "pytest-asyncio" -version = "0.24.0" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/6d/c6cf50ce320cf8611df7a1254d86233b3df7cc07f9b5f5cbcb82e08aa534/pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276", size = 49855, upload-time = "2024-08-22T08:03:18.145Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/31/6607dab48616902f76885dfcf62c08d929796fc3b2d2318faf9fd54dbed9/pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b", size = 18024, upload-time = "2024-08-22T08:03:15.536Z" }, + { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, ] [[package]]