diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..4b2f5cd --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,51 @@ +# Dependabot configuration. +# +# This repo had NO dependency watching until 2026-09-09, which is the +# same gap that produced the Sentinel AI agent's three production +# breakages in two days: mcp 2.x renaming a symbol, Starlette removing +# on_startup, and a ceiling added to stop the second one silently +# pinning the service to a Starlette line with 7 advisories. All three +# surfaced on a rebuild of a repo nobody had rebuilt since June. +# +# `pip-audit --strict` in the Test workflow catches a *known advisory* +# in what is already pinned. It does nothing about a lockfile quietly +# ageing out of support. That is what this is for. +# +# NO AUTO-MERGE WORKFLOW HERE, DELIBERATELY. +# +# This repo now deploys to Fly on push to master. GitHub does not +# trigger `on: push` workflows for commits pushed with GITHUB_TOKEN, so +# an auto-merge action would land a bump on master WITHOUT deploying it +# — master and production drift apart, and nothing reports an error. +# That is a real, observed bug in Sentinel-Command (four frontend bumps +# merged and never shipped on 2026-09-09). A human merge triggers the +# deploy normally, so these PRs are reviewed and merged by hand until +# that repo's PAT fix is proven. +# +# Extra reason to be careful here: this service is Postgres-native and +# its tests need a real Postgres container, so a driver bump that only +# misbehaves against the real engine will not show up anywhere else. +version: 2 +updates: + # Python (uv) — pyproject.toml + uv.lock live at the repo root. + - package-ecosystem: "uv" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + commit-message: + prefix: "build(deps)" + labels: + - "dependencies" + + # GitHub Actions — pinned action versions age out the same way. + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "ci" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1c78a6d..075a21b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,10 +1,15 @@ -name: Test +name: Test & Deploy -# Deploy automation is deferred deliberately, same rationale as -# Sentinel-License-Service's workflow of the same name: a human should -# be watching the first-ever deploy of new infrastructure (including, -# for this service, the first-ever Postgres instance in this family). -# This workflow only covers lint + dependency audit + tests. +# Tests on every push and PR; deploys to Fly on pushes to master. +# +# Deploy automation used to be deferred here so a human could watch the +# first-ever deploy of new infrastructure. That rationale expired once +# the service was live, and leaving it deferred created a worse problem: +# fly.toml became a file that did nothing. A scale-to-zero change was +# merged with CI green on 2026-09-09 and simply never reached Fly — +# the app kept running always-on until someone noticed and deployed by +# hand. Config that silently doesn't apply is more dangerous than no +# config, so this now ships. # # Unlike License-Service's version, this one needs a real Postgres # service container — JSONB isn't SQLite-portable, and this service is @@ -63,3 +68,30 @@ jobs: - name: Run tests run: uv run pytest -v + + deploy: + name: Deploy to Fly.io + runs-on: ubuntu-latest + needs: test + # Push-only: a PR runs the tests above but never ships. + if: github.event_name == 'push' + # Serialize so two quick pushes don't race on the machine update. + concurrency: + group: deploy-sentinel-sync + cancel-in-progress: true + steps: + - uses: actions/checkout@v7 + + - uses: superfly/flyctl-actions/setup-flyctl@master + + # --ha=false: Fly provisions TWO machines by default and this + # service needs one. It did exactly that on the 2026-09-09 manual + # deploy and the extra machine had to be scaled away by hand. + # + # No --strategy override: this app has no volume, so the default + # rolling strategy works. (Command Center needs `immediate` + # because its web machine mounts a single-attachment volume.) + - name: flyctl deploy + run: flyctl deploy --remote-only --ha=false --yes + env: + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}