Bring your own token store

Your A2A tokens stay in the store you register. The PWA stores them in localStorage by default. Server or enterprise deploys can swap that for a backend they control — file, Keychain, Vault, KMS, HSM — as long as it passes the conformance suite.

The interface

One typed contract. Hash-only storage. All methods async so backends can hit disk, IPC, or the network.

interface A2ATokenStore {
  readonly kind: string;
  list(workspaceId): Promise<A2AClientToken[]>;
  issue(workspaceId, input): Promise<{ client, rawToken }>;
  revoke(workspaceId, id): Promise<void>;
  verify(workspaceId, rawToken, now?): Promise<A2AClientToken | null>;
}

Register your backend at boot

import { configureA2ATokenStore } from "@aarmos/core/a2a/token-store";
import { createEncryptedFileStore } from "@aarmos/core/a2a/adapters/encrypted-file";

configureA2ATokenStore(await createEncryptedFileStore({
  dir: "/var/lib/aarmos/a2a",
  passphrase: process.env.AARMOS_A2A_PASSPHRASE!,
}));

Everything downstream — the settings UI, the A2A adapter, verifyA2AToken — routes through the store you registered. There is no fallback path that writes to disk behind your back.

Writing your own adapter

Most adapters only need to persist and load a list of records. Use makeRecordStore and you inherit the issue / revoke / verify / expiry / hashing logic for free:

import { makeRecordStore } from "@aarmos/core/a2a/token-store";

export const myStore = makeRecordStore({
  kind: "my-backend",
  load: (ws) => myBackend.get(ws),      // returns A2AClientToken[]
  save: (ws, clients) => myBackend.put(ws, clients),
});

Conformance suite (mandatory)

Every adapter — ours or yours — must pass the same suite. It pins the invariants that make BYOS safe: round-trip, verify, expiry, tenant isolation, revocation, and hash-only storage.

import {
  runA2ATokenStoreConformance,
  formatConformanceResults,
} from "@aarmos/core/a2a/conformance";

const results = await runA2ATokenStoreConformance(() => myStore);
console.log(formatConformanceResults(results));
if (results.some((r) => !r.ok)) process.exit(1);

What we ship in-box

  • localStorage — default, browser / PWA.
  • encrypted-file — Node, AES-GCM + PBKDF2-SHA256 (200k iters). Same primitives as the on-device vault.
  • memory — ephemeral, for tests.

Keychain, Vault, KMS, and HSM adapters ship on request as part of the Enterprise tier — or write your own against the interface above.