diff --git a/.github/workflows/manifest.yml b/.github/workflows/manifest.yml new file mode 100644 index 0000000..8d9775e --- /dev/null +++ b/.github/workflows/manifest.yml @@ -0,0 +1,22 @@ +name: manifest + +# The bundled adapter manifest must match the one seamless-auth-api main +# publishes. The schedule catches API route changes that land between PRs here. +on: + push: + branches: [main] + pull_request: + schedule: + - cron: '17 6 * * *' + workflow_dispatch: + +permissions: + contents: read + +jobs: + drift: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + - run: scripts/check-manifest.sh diff --git a/AGENTS.md b/AGENTS.md index 298e472..ef21533 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -55,6 +55,7 @@ Tooling is [uv](https://docs.astral.sh/uv/). Development runs on Python 3.11, th | Types | `uv run mypy` (strict) | | Tests | `uv run pytest` | | Conformance | see README, "Conformance" (both reference apps) | +| Manifest | `scripts/check-manifest.sh` (fix with `scripts/sync-manifest.sh`) | CI runs the checks on Python 3.11 to 3.14. 3.11 resolves Django 5.2 LTS and later versions the current Django, so both are covered. Do not use language or standard library features newer than @@ -106,5 +107,8 @@ bump the version, edit the changelog, or tag by hand: `release-please-config.json`), or `uv sync --locked` fails on main after every release. - If a publish fails after the release is tagged, fix the cause on main, then publish that tag by hand: `gh workflow run release.yml -f tag=vX.Y.Z`. It checks the tag matches `pyproject.toml`. +- The bundled manifest must match the one seamless-auth-api main publishes. The `manifest` + workflow checks it on every PR, on `main` and daily. Update it with `scripts/sync-manifest.sh` + in a `fix:` commit so the new routes ship in a patch release. Pre-1.0: a breaking change is a minor bump, and 1.0 is a deliberate decision, not a side effect. diff --git a/scripts/check-manifest.sh b/scripts/check-manifest.sh new file mode 100755 index 0000000..cdb4349 --- /dev/null +++ b/scripts/check-manifest.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env sh +# Fails when the bundled adapter manifest differs from the one the auth API +# publishes. The bundled copy is what the adapter serves whenever the live fetch +# fails, so a route missing from it answers 404 in that case. apiVersion is +# ignored: it changes on every API release, routes do not. +# +# scripts/check-manifest.sh [source] +# +# source defaults to seamless-auth-api main. Fix a failure with +# scripts/sync-manifest.sh. +set -eu + +source="${1:-https://raw.githubusercontent.com/fells-code/seamless-auth-api/main/adapter-manifest.json}" +bundled="$(dirname "$0")/../src/seamless_auth/manifest.json" + +dir="$(mktemp -d)" +trap 'rm -rf "$dir"' EXIT + +case "$source" in + http://*|https://*) curl -fsSL "$source" -o "$dir/upstream.json" ;; + *) cp "$source" "$dir/upstream.json" ;; +esac + +normalize() { jq -S 'del(.apiVersion) | .routes |= sort_by(.path, .method)' "$1"; } +normalize "$bundled" > "$dir/bundled.norm" +normalize "$dir/upstream.json" > "$dir/upstream.norm" + +if cmp -s "$dir/bundled.norm" "$dir/upstream.norm"; then + echo "Bundled manifest matches $source" + exit 0 +fi + +routes() { jq -r '.routes[] | "\(.method) \(.path)"' "$1" | sort; } +routes "$bundled" > "$dir/bundled.routes" +routes "$dir/upstream.json" > "$dir/upstream.routes" + +echo "Bundled manifest (src/seamless_auth/manifest.json) differs from $source" >&2 +echo "Missing from the bundled copy:" >&2 +comm -13 "$dir/bundled.routes" "$dir/upstream.routes" | sed 's/^/ /' >&2 +echo "Only in the bundled copy:" >&2 +comm -23 "$dir/bundled.routes" "$dir/upstream.routes" | sed 's/^/ /' >&2 +echo "Full difference:" >&2 +diff -u "$dir/bundled.norm" "$dir/upstream.norm" >&2 || true +echo "Run scripts/sync-manifest.sh to update it." >&2 +exit 1 diff --git a/src/seamless_auth/manifest.json b/src/seamless_auth/manifest.json index 5eda92a..05919e8 100644 --- a/src/seamless_auth/manifest.json +++ b/src/seamless_auth/manifest.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "apiVersion": "0.19.4", + "apiVersion": "0.21.1", "session": { "subject": "sub", "token": "token", @@ -9,6 +9,15 @@ "refreshTtl": "refreshTtl" }, "routes": [ + { "method": "GET", "path": "/admin/applications", "credential": "access" }, + { "method": "POST", "path": "/admin/applications", "credential": "access" }, + { "method": "DELETE", "path": "/admin/applications/{id}", "credential": "access" }, + { "method": "GET", "path": "/admin/applications/{id}", "credential": "access" }, + { "method": "PATCH", "path": "/admin/applications/{id}", "credential": "access" }, + { "method": "PUT", "path": "/admin/applications/{id}/keys", "credential": "access" }, + { "method": "GET", "path": "/admin/applications/{id}/resources", "credential": "access" }, + { "method": "PUT", "path": "/admin/applications/{id}/resources", "credential": "access" }, + { "method": "POST", "path": "/admin/applications/{id}/secret", "credential": "access" }, { "method": "GET", "path": "/admin/auth-events", "credential": "access" }, { "method": "GET", "path": "/admin/auth-events/export", "credential": "access" }, { "method": "GET", "path": "/admin/auth-events/integrity", "credential": "access" }, @@ -51,6 +60,11 @@ "credential": "access" }, { "method": "GET", "path": "/admin/reports/authentication-coverage", "credential": "access" }, + { "method": "GET", "path": "/admin/resources", "credential": "access" }, + { "method": "POST", "path": "/admin/resources", "credential": "access" }, + { "method": "DELETE", "path": "/admin/resources/{id}", "credential": "access" }, + { "method": "GET", "path": "/admin/resources/{id}", "credential": "access" }, + { "method": "PATCH", "path": "/admin/resources/{id}", "credential": "access" }, { "method": "GET", "path": "/admin/review-accounts", "credential": "access" }, { "method": "GET", "path": "/admin/sessions", "credential": "access" }, { "method": "GET", "path": "/admin/sessions/{userId}", "credential": "access" }, @@ -219,6 +233,11 @@ { "method": "POST", "path": "/system-config/oauth-providers", "credential": "access" }, { "method": "DELETE", "path": "/system-config/oauth-providers/{id}", "credential": "access" }, { "method": "PATCH", "path": "/system-config/oauth-providers/{id}", "credential": "access" }, + { + "method": "POST", + "path": "/system-config/oauth-providers/{id}/discovery", + "credential": "access" + }, { "method": "GET", "path": "/system-config/policy", "credential": "access" }, { "method": "DELETE", "path": "/system-config/policy/draft", "credential": "access" }, { "method": "PUT", "path": "/system-config/policy/draft", "credential": "access" },