Date: 2026-07-20 Status: Accepted Target: v0.30.0
Auth is passkey-only. Handler.Middleware in server/auth/auth.go gates the API on the qntx_session cookie, which is HttpOnly and lives in an in-memory sync.Map. Scripts, plugins, and CI cannot authenticate.
Add a second auth path: access tokens, presented as Authorization: Bearer <token>.
access_tokens table (db/sqlite/migrations/); on parquet as one object per token under <location>/system/access_tokens/, matching the "small config" shape in ADR-024. Only the SHA-256 hash is stored.qntx_-prefixed. Shown once at creation./auth/tokens (POST create / GET list / DELETE revoke / POST enable). Bearer tokens cannot mint new tokens.Handler.Middleware gains a bearer-header path before the cookie check."the token route will be the way things get to access qntx publicly"
Its own did:key. The 32 bytes are an ed25519 seed rather than only a
secret, so the token has a public half and its holder can sign as it.
minted_by — the auth.root_identities entry whose session issued it.
"speaking on behalf of a user who minted them". Revoking the account revokes
what it minted.
namespace — where it may act, chosen at mint time.
What a token may read and write is not on the token: the roles its DID holds say, through their WRITE and READ lines (ADR-034).
Lookup returns that grant instead of a bool. A bool could carry none of it,
which is why the middleware could only ever say "someone authenticated".
<location>/system/access_tokens/ — not the SQLite scratch.