The daemon branched on AUTHSOME_DEPLOYMENT_MODE and ran two parallel
implementations of nearly every ownership concept. "Local and hosted
behave the same" was asserted in prose (ADR 0006) but never enforced in
code, so the paths were free to drift, and the synthetic local principal
let a second local identity silently inherit the admin principal.
Collapse to one flow, identical for every deployment: authsome init
registers an identity and gets back a browser claim URL; the user
registers email+password (first principal becomes admin); that principal
confirms the claim; PoP calls are then authorized.
- Remove AUTHSOME_DEPLOYMENT_MODE, get_deployment_mode(),
LOCAL_PRINCIPAL_EMAIL, the Local*/Hosted* resolver and bootstrap
classes, and the AuthService(deployment_mode=...) parameter.
- OwnershipResolver and IdentityBootstrapService become single concrete
classes (the former hosted, claim-based implementations).
- Admin gating is purely role-based: non-admin principals are blocked in
every deployment (previously implicitly allowed in local mode).
- The server UI always requires a hosted browser session; remove the
vestigial HealthResponse.mode field.
- CLI ensure_identity_ready was already mode-agnostic; it now prints the
claim URL to stderr for headless use.
Add ADR 0007 recording the decision; amend ADR 0006 and CONTEXT.md.
BREAKING CHANGE: existing local installs have an unclaimed identity under
local@authsome.internal and are rejected until the user registers a
principal (email+password) and claims the identity.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entire-Checkpoint: d31cf246f6f2
Implement ADR 005 by routing audit events through OpenTelemetry with a server-owned SQLite exporter and query endpoint. Implement ADR 006 by persisting principal roles, assigning first principal admin, and enforcing admin-only routes at the FastAPI dependency layer.
Entire-Checkpoint: df60966364f1
Replaces the flat FernetEncryptionWrapper + EncryptionConfig model with a
proper envelope encryption scheme:
- MasterSecretResolver: unified resolution order (env → file → keyring →
auto-generate) under a single AUTHSOME_MASTER_KEY env var; no separate
passphrase vs raw-key distinction — both go through Argon2id
- DekManager: generates a random 256-bit DEK, wraps it with an
Argon2id-derived KEK (AES-256-GCM), and stores the wrapped record in the
KV store under __vault_meta__:__dek__ so it works with any KV backend
- AesGcmEncryptionWrapper: drop-in BaseEncryptionWrapper using AES-256-GCM
per-value encryption via closure; replaces FernetEncryptionWrapper
- Vault: simplified — no longer owns crypto or lifecycle; receives an
already-encrypted AsyncKeyValue; close() removed (caller manages store)
- EncryptionConfig removed from ServerConfig and models __all__
- Health route updated to report crypto_source from Vault properties
- Tests rewritten with fixtures and SimpleStore (in-memory); no DiskStore
BREAKING CHANGE: existing Fernet-encrypted vaults cannot be read back;
migration requires re-importing credentials.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Entire-Checkpoint: a778dfa71075
Enables authenticated access to sites like X/Twitter and LinkedIn that
use browser session cookies rather than OAuth or API keys.
Core logic lives entirely in auth/ — BrowserFlow.run_login() reads
Chrome's on-disk SQLite cookie database via browser-cookie3 (macOS
Keychain / Linux GNOME Keyring / Windows DPAPI), opens the site in the
user's default browser if no valid session exists, then polls until the
required auth cookies appear. No separate Chrome profile, no Playwright.
- auth/browser_cookies.py: read_chrome_cookies(), cookies_are_valid(),
normalize_jsessionid() with lazy browser-cookie3 import
- auth/flows/browser.py: BrowserFlow (begin/resume/refresh) + static
run_login() for CLI use
- auth/models/: AuthType.BROWSER, FlowType.BROWSER, BrowserConfig,
ExtractRule, ConnectionRecord.credentials field
- server/: register flow, header rendering, export branch, BrowserAction
schema, _session_response wiring (~35 lines total)
- cli/main.py: 8-line elif block calling BrowserFlow.run_login()
- Bundled providers: x-browser, linkedin-browser
- 30 new tests in tests/auth/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 3753e27e5941
Phases A–I of TODOS.md:
- Move all five registry classes (IdentityRegistry, PrincipalRegistry,
VaultRegistry, IdentityClaimRegistry, PrincipalVaultBindingRegistry) from
identity/ to server/registries.py. Domain model types stay in identity/.
- Break identity/local.py → cli/ coupling: inline _read/_write_active_identity_handle
helpers so identity/ imports nothing from cli/.
- Remove VaultRegistry + get_server_home imports from auth/service.py; refactor
revoke() to accept explicit vault_ids from the server layer.
- Move proxy_routes() and _build_route_entry() out of AuthService into
server/proxy_catalog.py; proxy route delegates to build_proxy_routes().
- Move AuthService from auth/service.py to server/credential_service.py so
auth/ becomes a true leaf (no vault/, audit/, or server/ imports). Update
all 10+ import sites in server/, cli/ test files, and the top-level __init__.
- Remove global AuthService(identity="server") from app.py. Refactor /ready
to resolve the local identity at request time and use app.state.vault directly.
- Remove vault.home property from Vault; replace all callers with store.home.
- Update AGENTS.md architecture table to reflect new file locations.
All 293 tests pass. ruff and ty clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 5c8526a4e7d2
Replace authsome.actors with authsome.identity, splitting into four
focused sub-modules:
identity/local.py — IdentityMetadata, keys, DID helpers, create_identity
identity/proof.py — PoP JWT creation and validation
identity/registry.py — daemon IdentityRegistry (Handle → DID)
identity/principal.py — ClaimStatus, PrincipalRegistry, VaultRegistry,
IdentityClaimRegistry, PrincipalVaultBindingRegistry
Update all imports across src/ and tests/. Move tests/actors/test_registry.py
to tests/identity/test_registry.py. Delete the actors/ package.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 88222782ec3b
- Add ClaimStatus enum (PENDING/ACCEPTED/REJECTED) to IdentityClaimRecord
with accept_claim() and reject_claim() on IdentityClaimRegistry
- HostedOwnershipResolver.resolve() now gates vault access on ACCEPTED;
PENDING raises 403, REJECTED raises 403 with distinct message
- ensure_claimed_identity() auto-accepts on UI form submission — the form
IS the approval act in this release; PENDING is transit state only
- Remove AuthService vault_id fallback (vault_id or identity); _coll now
raises ValueError if vault_id is None, making missing wiring explicit
- /ready endpoint resolves vault_id via ownership_resolver instead of
constructing AuthService with the identity handle as a stopgap
- create_auth_service() requires explicit vault_id parameter
- Rewrite ADR 0003 and update UBIQUITOUS_LANGUAGE.md to reflect opaque
VaultId design (single segment, not principal_handle/vault_handle)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 8f56337e3615
Profile is now a pure credential namespace scoped by the Identity handle,
not an independently managed entity. The two were always 1:1 but modelled
as separate concepts; this commit makes that explicit.
Key changes:
- Add `active_identity` to GlobalConfig; `ensure_local_identity` reads it
and errors hard if the configured handle is missing on disk (no silent
re-creation)
- `authsome init` writes `active_identity` to config after registration
- Collapse `registered: bool` + `registration_status: str` on
IdentityMetadata into a single `registered: bool`; add `mark_registered()`
- Remove vestigial `Identity` dataclass, `DEFAULT_IDENTITY`, `current()`
- Remove `name`, `description`, `owner_email` from IdentityMetadata (deferred
to hosted version)
- Remove `ProfileMetadata`, `ProfileNotFoundError`, `create_profile`,
`list_profiles`, `get_profile`, `set_default_profile`, `default_profile`
shim in save_config — profile is a side-effect of identity registration
- Add hard-error test for missing configured identity
- Update UBIQUITOUS_LANGUAGE.md, AGENTS.md architecture section, and
ADR 0002 to reflect current code (fix stale AuthsomeContext, AuthClient,
config.json path, ProfileMetadata references)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 614885258a45
Make the daemon identity registry authoritative for protected PoP requests and remove implicit default-profile identity behavior.
Store daemon-owned state under server/ while keeping CLI signing identity files under identities/.
BREAKING CHANGE: Existing implicit default-profile installs must run authsome init again; profile:default credentials are not migrated.
Entire-Checkpoint: 557455816515