Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ee5306a
feat(aauth): introspection-aauth, a Person Server and Access Server l…
brightsparc Oct 21, 2026
f67d1b3
fix(aauth): a mission names its approval and person, and federation r…
brightsparc Oct 21, 2026
96651ea
test(aauth): refuse a taken mission hash with a store, not a swapped …
brightsparc Oct 21, 2026
a8b0c0b
feat(aauth): the FastAPI routers take a router of the host's, for its…
brightsparc Oct 21, 2026
20e5288
feat(aauth): routers resolve their server per request, read pending p…
brightsparc Oct 21, 2026
936a82b
feat(aauth): a store transaction yields the unit of work, and decide …
brightsparc Oct 21, 2026
b0feada
refactor(aauth): every wire body and token is a pydantic model, with …
brightsparc Oct 21, 2026
9e10e63
refactor(aauth): the example builds each server with its own builder,…
brightsparc Oct 21, 2026
1335811
feat(aauth): discovery reads only AAuth identifiers over https, follo…
brightsparc Oct 21, 2026
10035ea
feat(aauth): export MissionGrant from person_server
brightsparc Oct 21, 2026
4b1c0ef
refactor(aauth)!: stores insert approvals and pending requests, as th…
brightsparc Oct 21, 2026
45f8310
fix(aauth): collect an approved auth token once, read proposals only …
brightsparc Oct 21, 2026
ef3c239
fix(aauth): verify a signed request at the path its signer sent, stil…
brightsparc Oct 21, 2026
83b2cac
fix(aauth): a pending URL gives its terminal answer once, then answer…
brightsparc Oct 21, 2026
4501373
fix(aauth): a proposal's summary is written without making a decision…
brightsparc Oct 21, 2026
1afb891
fix(aauth): an Access Server's token endpoint and keys must be on its…
brightsparc Oct 21, 2026
db5b721
fix(aauth): a key without alg takes its algorithm from its type
brightsparc Oct 21, 2026
14a56f1
test(aauth): the type guard reads the AST for every way of writing An…
brightsparc Oct 21, 2026
60d1215
fix(aauth): a delivered answer is recorded apart from its decision
brightsparc Oct 21, 2026
8f306f6
refactor(aauth)!: federation steps on PersonServer, typed error class…
brightsparc Oct 21, 2026
1eda313
refactor(aauth)!: the Access Server client takes what it uses; every …
brightsparc Oct 21, 2026
b8bab82
build(aauth): ban relative imports, type-check the package's tests, r…
brightsparc Oct 21, 2026
6116c5a
build(aauth): jscpd skips the re-export __init__ modules
brightsparc Oct 21, 2026
bfe588f
refactor(aauth): no underscore on classes, constants or type variables
brightsparc Oct 21, 2026
a7e3ca8
refactor(aauth)!: person_server.federation becomes access_server_client
brightsparc Oct 21, 2026
94f396b
refactor(aauth): one concept per module
brightsparc Oct 21, 2026
aca284e
feat(aauth): trace federation and log decisions as the platform does
brightsparc Oct 21, 2026
da76e1c
test(aauth): no log field the platform's formatter would drop; jscpd …
brightsparc Oct 21, 2026
8b07b8e
refactor(aauth): fastapi becomes a package, one module per server
brightsparc Oct 21, 2026
4cf1bb1
refactor(aauth): each server's resolver is the generic Resolver
brightsparc Oct 21, 2026
b55fef6
ci: write each suite's coverage table to the run summary
brightsparc Oct 21, 2026
f794426
refactor(aauth): httpsig becomes a package, signing and verification …
brightsparc Oct 21, 2026
6d72db7
docs(examples): build each AAuth app in a factory whose lifespan owns…
brightsparc Oct 21, 2026
3bf5cc0
refactor(aauth): name the in-process transport for what it is
brightsparc Oct 21, 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
55 changes: 54 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ jobs:
run: uv sync --all-extras

- name: Run tests with coverage
run: uv run pytest tests/ -v --cov=introspection_sdk --cov-report=term-missing --cov-report=xml
run: |
echo "## introspection-sdk coverage" >> "$GITHUB_STEP_SUMMARY"
uv run pytest tests/ -v --cov=introspection_sdk --cov-report=term-missing --cov-report=xml \
--cov-report="markdown-append:$GITHUB_STEP_SUMMARY"

- name: Upload coverage report
if: always()
Expand Down Expand Up @@ -205,3 +208,53 @@ jobs:
node-version: "24"
- name: Duplicate code stays under the threshold
run: npx --yes jscpd@5.4.1

aauth:
# packages/introspection-aauth is its own distribution, with its own
# lock, coverage floor and duplicate-code check (its pyproject.toml and
# .jscpd.json).
name: introspection-aauth
runs-on: ubuntu-latest
defaults:
run:
working-directory: packages/introspection-aauth
steps:
- uses: actions/checkout@v7

- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"

- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.11"

- uses: actions/setup-node@v7
with:
node-version: "24"

- name: Install dependencies
run: uv sync --all-extras

- name: Check formatting
run: uv run ruff format --check .

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

- name: Run type checking
run: uv run ty check introspection_aauth tests

- name: Run tests with coverage
run: |
echo "## introspection-aauth coverage" >> "$GITHUB_STEP_SUMMARY"
uv run pytest tests/ -v --cov=introspection_aauth --cov-report=term-missing \
--cov-report="markdown-append:$GITHUB_STEP_SUMMARY"

- name: Check dependencies
run: uv run deptry .

- name: Duplicate code stays at zero
run: npx --yes jscpd@5.4.1
6 changes: 6 additions & 0 deletions .github/workflows/examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ jobs:
restore-keys: |
${{ runner.os }}-uv-examples-

- name: Run the AAuth example
# Every party runs in process; it needs no secrets.
if: inputs.run_examples || startsWith(github.ref, 'refs/tags/v')
working-directory: examples
run: uv run -m introspection_examples.aauth.four_party

- name: Run pattern examples
# The docs' pattern pages show these modules' snippet regions.
# patterns.add_to_your_app and patterns.web_chat also need a service
Expand Down
3 changes: 2 additions & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
".": "0.29.1"
".": "0.29.1",
"packages/introspection-aauth": "0.0.0"
}
36 changes: 33 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,36 @@ uv run ty check
These are enforced by `.pre-commit-config.yaml`. Don't disable hooks with
`--no-verify` — fix the underlying issue.

**No leading underscore on a class, constant or type variable.** Write
`class RouteDocs`, `JSON_OBJECT`, `AnyServer = TypeVar("AnyServer", ...)`,
never `class _Docs` or `_object`. A module keeps such a name private by
leaving it out of its `__all__`, so a module that has private names defines
`__all__`. Private methods and module functions keep their underscore. This
is the rule for new code: about 76 underscore classes and constants remain in
`introspection_sdk/`, `tests/` and `examples/`; rename one when you touch
its module.

**One file, one concept, named for it.** A module holds one class, or one
family of closely related ones, and its name says which: `access_server_client.py`
holds `AccessServerClient`, `directory.py` the `Directory` protocol and its
default, `store.py` the `Store` protocol and its `MemoryStore`. A `models.py`
holds a package's models, and `hooks.py` the protocols a host implements.
Aim for 100 to 300 lines; a module far past that is a sign to split it, the
exceptions being a `models.py` and a class that is legitimately that long, in
which case its module holds that class alone.

**Log and trace as the platform does.** A module that does meaningful work
declares `logger = logging.getLogger(__name__)` and, where it has an
operation worth a span, `tracer = trace.get_tracer(__name__)`, once, after its
imports. A record has a fixed message and its values as `extra` fields:
identifiers and enumerated values, never a token, personal data or a request
body. A library never configures logging or a tracer provider; its host does.

**Imports are absolute.** `from introspection_aauth.protocol.errors import
InvalidToken`, never `from ..protocol.errors import InvalidToken`; ruff's
TID252 enforces it. A test suite imports its own helpers relatively
(`from .world import World`).

## Sync code generation

Every class or function with an `Async*` twin is written once, as async
Expand Down Expand Up @@ -282,9 +312,9 @@ It needs the release tags locally (`git fetch --tags`).
What counts as public: a module's `__all__` when it has one, otherwise every
name without a leading underscore. Imported names are public only through
`__all__`. `_`-prefixed modules (`_http.py`, `_errors.py`,
`otel/_provider.py`, ...) and `_`-prefixed classes, functions and attributes
are internal and free to change. To keep a new helper out of the contract,
give it a leading underscore or leave it out of `__all__`.
`otel/_provider.py`, ...) and `_`-prefixed functions and attributes are
internal and free to change. To keep a new class or constant out of the
contract, leave it out of `__all__`, never prefix it with an underscore.

`-e scripts/griffe_reexports.py` loads a griffe extension that replaces
each public alias whose target lives in `introspection_sdk` with the target
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,14 @@ caller who is not a project administrator only ever gets their own
connections. The routes need `connections:read`, `connections:write` and
`connections:delete`.

## Run an AAuth Person Server or Access Server

[`introspection-aauth`](packages/introspection-aauth) is a separate package
in this repository: an AAuth Person Server and Access Server
(draft-hardt-oauth-aauth-protocol -11) over a store you provide, with
FastAPI routers. `examples/introspection_examples/aauth/four_party.py` runs
the four-party flow in one process.

## Environment variables

```shell
Expand Down
2 changes: 1 addition & 1 deletion examples/introspection_examples/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from dotenv import load_dotenv

from . import api, otel
from introspection_examples import api, otel

load_dotenv()

Expand Down
1 change: 1 addition & 0 deletions examples/introspection_examples/aauth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""AAuth Person Server and Access Server examples (introspection-aauth)."""
Loading
Loading