Changes
Unclaimed accounts never expire by defaultunclaimed_ttl_seconds is now optional and off by default: an agent operates its account indefinitely whether or not a human ever claims it, and the EXPIRED state is opt-in — reached only when a service deliberately advertises a TTL to garbage-collect abandoned accounts or to meet a data-retention mandate. The spec (§4.4, §6.1, §7.3, Appendix A) reframes expiry as opt-in and warns against setting a TTL on the reflex that “inactive accounts should be reaped” — an AFAuth account is bound to a key its agent holds, not to a human who may have wandered off. In the TypeScript SDK, sweepExpiredAccounts treats unclaimedTtlSeconds as optional and is a no-op when omitted; the published .well-known schema raises the TTL floor to 3600s for services that do opt in. See the account lifecycle and the .well-known/afauth reference.New features
Multi-agent accounts — one account, many devicesattested_only (§9.2) proves a signup carries some valid attestation, but a human runs more than one agent — a PC and a phone are two did:keys. The SDK now models that directly: an account is keyed on an opaque account_id (no longer the agent DID) and holds one or more agent credentials. Because every trust attestation for the same human at one service carries the same pseudonym sub_h (§10.4), a human’s agents are grouped onto one account by (iss, sub_h) (§10.4.4) — like signing into one account from several devices. The second device attaches; it is not rejected. Bucket your free-tier quota / rate limits / bans on the account and a human’s fleet shares one bucket. Agents with no sub_h get singleton accounts; key rotation now keeps account_id stable; introspection returns account_id + agent_did. D1AccountStore (@afauthhq/worker) makes the grouping atomic via a (iss, sub_h) UNIQUE index. See the attestation concept and service SDK overview.New features
SweepableAccountStore for TTL-driven account expiryService operators implementing the TypeScript SDK can now use a built-in helper to expire unclaimed and invited accounts that have exceeded theirunclaimed_ttl_seconds. The new SweepableAccountStore interface and sweepExpiredAccounts helper move the UNCLAIMED → EXPIRED and INVITED → EXPIRED transitions out of bespoke service code, closing a v0.1 conformance gap for the §6.1 EXPIRED state. The bundled MemoryAccountStore and D1AccountStore already implement the new interface. See the account lifecycle and revoke an account guides.Trust-attestation conformance vectorsThe optional §10 trust-attestation surface now has a dedicated conformance corpus, documented as §C.7 in the spec. Implementations that consume trust attestations can verify themselves against the same vectors used by the reference implementations. Core §C.1–§C.6 conformance is unchanged. See the conformance reference and trust attestor concept.v0.2 implementation milestone publishedThe wire protocol stays at afauth_version: 0.1, but the reference implementations have shipped meaningful surface area beyond the original v0.1 scope. The new v0.2 implementation milestone documents what’s included: the service directory, the afauth-trust service, the SDK trust client and defineService API, and CLI v0.4.0.Updates
Worker package: D1 + Durable Object nonce storesThe@afauthhq/worker package’s DurableObjectNonceStore and createNonceDurableObject exports are now documented. Use these to back the §4.3 replay-protection cache with a Durable Object instead of a single-region store when deploying on Cloudflare. See the worker SDK overview and the Cloudflare Worker deployment guide.Account.createdAt exposed via introspectionAccount now carries a createdAt timestamp, surfaced by the §6.5 introspection endpoint. The response body matches the example in the spec, so introspection clients no longer need to special-case a missing field.Bug fixes
Binary request bodies now verify correctly (@afauthhq/server@0.1.1, @afauthhq/agent@0.1.1, @afauthhq/worker@0.1.1)Verification of requests with non-UTF-8 bodies — multipart uploads, ZIPs, protobuf — was failing with 401 invalid_signature against any RFC-9421-conformant agent. Servers read bodies via arrayBuffer() now, and signRequest / Verifier.verify accept Uint8Array bodies directly. String-body callers are unaffected. Update with npm update @afauthhq/server @afauthhq/agent @afauthhq/worker. See verify a request.CLI v0.4.0: correct trust binding path in --helpafauth trust link --help previously printed ~/.config/afauth/trust.json for the binding token, but the binding is written to ~/.afauth/trust.json. The help text now matches behaviour, and afauth --version on source builds reports 0.4.0 instead of the stale 0.3.1. See the trust command reference.