01The credential is a pointer, not a claim
Opaque keys with a public prefix and identifier; nothing about what the key may do travels inside it. Revocation is possible at all only because of this.
ADR-0122 views, in reading order. Every view ships three ways: an HTML page, an SVG that re-opens in diagrams.net fully editable, and draw.io source.
Twenty-two views in seven acts, for the service that issues a developer platform's API keys, verifies one on every request, and takes one away faster than whoever found it on GitHub can use it. Read them in order: the set is one argument, and its single load-bearing claim — that a credential carries no authority of its own — is made in act 1 and honoured or contradicted by every view after it.
The credential carries no authority of its own. It is an opaque pointer into revocable state, and verification is a local read of a replicated, staleness-bounded projection of that state.
A developer platform hands its customers a string and then has to live with it. The string ends up in a CI variable, a mobile binary, a pasted Slack message and, eventually, a public commit. On the day it does, the only question that matters is how long it stays useful to whoever found it — and the answer is decided years earlier, by whether the platform chose a credential it can take back. Meanwhile every one of the platform's 250,000 requests per second has to check that string, which means the check has to be cheap enough that nobody is ever tempted to skip it under load.
Issuance is a rare, transactional write into a strongly consistent, tenant-partitioned store, and it returns the secret exactly once. What is stored is a keyed digest whose pepper lives in a hardware key store the database cannot reach. Revocation is appended to a separate, ordered, append-only log with RPO 0 and acknowledged only after a durable multi-region write. That log is published as a feed and applied as an additive overlay onto a projection held inside every calling service, beside a snapshot that warms a cold one. Verification is then a memory lookup and an overlay check on localhost: no network hop, no call to the control plane, and a staleness figure returned on every response. Past a declared staleness ceiling a verification point stops honouring privileged scopes rather than continuing to answer from a projection it can no longer prove is fresh.
Opaque keys with a public prefix and identifier; nothing about what the key may do travels inside it. Revocation is possible at all only because of this.
ADR-01A library or sidecar holds a projection of active credentials and a revocation overlay. The request path makes no call to the control plane, ever.
ADR-02Separate from credential replication, RPO 0, acknowledged only after a multi-region quorum write, and never undone by a restore or a cache repopulation.
ADR-05Every response carries how far behind the feed it is; past 120 seconds a point refuses write and admin scopes rather than answering from state it cannot vouch for.
ADR-07HMAC-SHA-256 with a Cloud HSM pepper, not Argon2id. 160 bits of platform entropy is not a password, and 250,000 verifications per second cannot afford a memory-hard KDF.
ADR-09Versioned pepper with dual-version verification, so a full database copy is answered by rotating one key rather than re-issuing every customer's credentials.
ADR-10A flat, additive vocabulary; no principal may issue a credential wider than itself; a scope change is a new version, not an edit.
ADR-13Lossy, lagging and cheap, because promoting it to an authorization input would make the hot path a durable write at full request rate.
ADR-15Auto-revoke on a confirmed partner match; anomaly-only response is opt-in, because a false positive is an outage the customer did not cause.
ADR-17A canary issues, verifies, revokes and re-verifies a credential continuously, from a project this platform's operators cannot deploy to.
ADR-19A credential service outlives the platform it was built for, because every customer integration depends on the format of the string. These are the properties that should survive a change of scale, of cloud, and of the people who built it.
Nothing in ADR-01 depends on Spanner, on Google Cloud or on HMAC. The claim — that authority is looked up rather than carried — survives replacing every component underneath it, and it is the property a future team would most easily throw away without noticing.
Scale changes what the architecture looks like; it does not change what the service is judged on. A team in ten years with a different store, a different bus and ten times the traffic will still be asked how long a revoked key stays useful, and the canary will still answer.
Because every response carries it, any future replacement of the propagation mechanism — filter, push, pull, something not yet invented — is testable against the same contract. The interface outlives the mechanism.
It is not enforced by a policy that can be relaxed or an ACL that can be misconfigured. A future operator cannot accidentally make secrets recoverable without changing the issuance path itself, which is a visible, reviewable act.
Removals are rejected at the gate rather than deprecated by convention, which means a vocabulary ten years and four owners later is still one a customer's five-year-old integration can rely on.
The staleness ceiling, the overlap window and the projection-fits-in-memory assumption are each named, each measurable, and each carry a stated condition that would flip them. They are designed to be overturned by data rather than defended.
Every number here is a stated assumption from the requirement, chosen to be argued with. The right-hand column names the view where the mechanism that meets it is drawn.
| Quality | Target | How it is met | View |
|---|---|---|---|
| Verification availability | ≥ 99.99% monthly, per region | Active-active verification in every region, with no per-request dependency on the control plane or the credential store. | 16 |
| Verification latency | p50 ≤ 0.3 ms · p99 ≤ 3 ms in-process | Memory lookup of a keyed digest plus an overlay check on localhost; a keyed HMAC rather than a memory-hard KDF. | 12 |
| Verification throughput | 250,000/s · 5× burst for 120 s | Verification scales with the calling fleet; no coordination between verification points. | 08 |
| Control-plane availability | ≥ 99.9% monthly | Leader region with warm standbys; issuance failing is survivable in a way verification failing is not. | 16 |
| Revocation propagation | p50 ≤ 2 s · p99 ≤ 10 s · ceiling 60 s | Ordered append-only log published as a push feed with a 5-second poll fallback and a heartbeat. | 14 |
| Bulk revocation | 100,000 credentials within 5 min | Tenant epoch bump: one ordered write and one message, whatever the number of credentials. | 14 |
| Leak response | p99 ≤ 60 s from partner notification | Greppable prefixes, a one-bit partner endpoint, and a per-tenant response policy executed without a human in the loop. | 15 |
| RPO — revocation state | 0 | Multi-region quorum write before the revocation is acknowledged; never undone by replication catch-up or restore. | 10 |
| RPO — credential records | ≤ 5 s | Multi-region Spanner configuration; a lost issuance is recoverable by re-issuing, a lost revocation is not. | 10 |
| RTO — verification path | 0 | Regional loss is a capacity event; every region verifies independently from its own projection. | 16 |
| RTO — control plane | ≤ 15 min | Warm standby regions; failover promotes a standby without changing the verification path. | 16 |
| Audit retention | 400 days hot · 7 years cold | Hash-chained records in BigQuery, tiered to locked Cloud Storage; the trail outlives the credential. | 10 |
| Time to first API call | ≤ 2 min at p50 | Self-service issuance with scope presets, one-time secret display and environment encoded in the prefix. | 04 |
Three of this architecture's claims are cheap to test and expensive to be wrong about. A prototype that proves these can be built on and one that skips them is a demonstration of issuance, which is the easy half.
| Risk | If it lands | Response |
|---|---|---|
| Full projection does not fit | The memory-per-point assumption fails at a few thousand verification points, forcing a demand-filled cache and reintroducing a cold path to the store. | Measure in the prototype. The fallback is a partitioned projection by key-prefix shard, which keeps the no-cold-path property at the cost of routing. |
| Fail-closed is politically unsurvivable | The first time a feed outage refuses writes platform-wide, the pressure to fail open will be enormous, and failing open silently undoes the architecture. | Scope-graded degradation is the compromise already designed in. Any move beyond it should require the same review as removing revocation entirely. |
| Central scope vocabulary becomes a bottleneck | Product teams route around it with catch-all scopes, and least privilege quietly stops being true. | Watch the rate of new scopes and the share of credentials holding an admin-grade scope. A rise in the second is the signal, not a complaint from a team. |
| Partner leak detection is the only detection | Keys leaked to private repositories, logs and support tickets are invisible, and the platform's containment metric looks better than its containment. | Own-corpus scanning is in the must-have tier for exactly this reason; report detection source split rather than a single leak count. |
| Anomaly detection is turned on by default under pressure | A false positive revokes a live customer integration, and the trust cost exceeds the leak it prevented. | Opt-in is an architectural decision, recorded in ADR-17 with its reasoning, so that turning it on by default is a visible reversal rather than a configuration change. |
Twenty decisions make up this architecture. Everything else across the twenty-two views is convention, sizing or consequence. Each record states the forcing question, the context that makes it hard, what was decided, how it is realised on Google Cloud, the options weighed, what the choice buys and costs, the conditions that would flip it, why it should still be right in ten years, and the lesson that transfers beyond this platform.
Status of this document. This is a design, not a report on a running system. The rates, latencies, volumes, retentions and windows are the requirement's stated assumptions for a mid-sized developer platform — 400,000 tenants, 2.5 million live credentials, 250,000 verifications per second across three regions — invented to be defensible and arguable rather than absent. They are to be replaced by measured telemetry before build, and three of them in particular are the first numbers real data should overturn: the 120-second staleness ceiling, the 7-day rotation overlap, and the assumption that a full credential projection fits in every verification point's memory.
What a credential is, and what it costs to check one.
ADR-01The credential is an opaque pointer, not a self-contained claim ADR-02Verification is a local read, shipped as a library and a sidecar ADR-03A full projection at every verification point, not a demand-filled cache ADR-04Invalid answers are constant in shape and cost, from a closed set of reasonsHow a key stops working everywhere, and how quickly that can be proved.
ADR-05Revocation is an independent, append-only, quorum-acknowledged log ADR-06Push feed with a poll fallback and a heartbeat, not a probabilistic filter ADR-07Staleness is published on every response, and bounded by a scope-graded fail-closed ceiling ADR-08Bulk revocation is a tenant epoch bump, not N revocationsWhat is stored, what is not, and what a stolen database is worth.
ADR-09A fast keyed digest with an HSM-held pepper, not a memory-hard KDF ADR-10The pepper is versioned, so a compromise is a rotation and not a re-issuance ADR-11The secret is returned once and is unrecoverable by constructionWhat a credential carries, who may widen it, and what the platform refuses to decide.
ADR-12The service reports what a credential carries and never what it permits ADR-13A flat, additive, centrally owned scope vocabulary ADR-14No principal may issue a credential wider than itself, checked at issuance ADR-15last_used_at is an observation, never an authorization inputFinding a leaked credential, and what the platform is allowed to do about it on its own.
ADR-16Greppable prefixes and a partner endpoint that returns one bit ADR-17Auto-revoke on a confirmed leak; anomaly-only response is opt-in and reversibleWhere it runs, how a change reaches it, and how its central claim is kept honest.
ADR-18Verification is active-active everywhere; the control plane has one leader ADR-19A revocation canary, run from a project the platform's operators cannot deploy to ADR-20Config artefacts are immutable, ring-rolled, and refused rather than fallen back fromEvery capability and what it is realised with, the origin of that choice, the alternative that was weighed, and the record that argues it. Where the requirement stays vendor-neutral, this table is where the stack commits.
| Capability | Choice | Origin | Credible alternative | Why this one | Record |
|---|---|---|---|---|---|
| Credential record store | Cloud Spanner, multi-region nam-eur-asia1, tenant-partitioned | Google Cloud | Regional PostgreSQL with logical replication | Strong consistency across regions with no application-level conflict handling; the same instance carries the revocation log, whose RPO 0 is the requirement that justifies the cost. | ADR-05 |
| Revocation log | Append-only Spanner table with a monotonic sequence and quorum commit | This design | A status column on the credential record | Revocation needs its own durability and ordering, independent of credential replication and immune to a restore. | ADR-05 |
| Revocation distribution | Pub/Sub global topic, 5-second poll fallback, 10-second heartbeat | Google Cloud | Cuckoo filter distributed to every point | Push latency with poll self-healing, and a positive liveness signal without which a dead channel is indistinguishable from a quiet one. | ADR-06 |
| Verification point | Envoy ext_authz sidecar plus Go and Java libraries | Open source | Central verification service behind a load balancer | Verification must cost a memory lookup rather than a network hop, or callers will cache it badly and skip it under load. | ADR-02 |
| Projection warm-up | Regional Cloud Storage snapshot every 60 s, replayed forward from its sequence | This design | Demand-filled cache reading Spanner on a miss | Removes the cold path entirely, so a fleet restart is a download rather than a load spike against the credential store. | ADR-03 |
| Shared projection for thin clients | Memorystore for Redis, per region | Google Cloud | Each thin client holding its own projection | A bounded escape hatch for callers that genuinely cannot hold state, with its own p99 ≤ 15 ms budget and a reported call rate. | ADR-02 |
| Digest and pepper custody | HMAC-SHA-256 under a Cloud HSM pepper, versioned | Google Cloud | Argon2id over the secret | 160 bits of platform entropy is not a password; the defence is custody of the pepper, and the digest must be affordable 250,000 times a second. | ADR-09 |
| Envelope encryption | Cloud KMS CMEK per tenant over Spanner and BigQuery | Google Cloud | Provider-managed default encryption | Tenant-scoped key hierarchies make a customer's crypto-shred a real operation rather than a promise. | ADR-11 |
| Usage counters | Cloud Bigtable, asynchronous aggregation | Google Cloud | Counters in the credential store | last_used_at is an observation, so it may be lossy and lagging — which is exactly what keeps it off the hot path. | ADR-15 |
| Audit store | BigQuery hot tier, hash-chained records, tiered to locked Cloud Storage | Google Cloud | A write-once ledger service | Tamper evidence from per-tenant chaining plus object-lock retention, with query cost visible to whoever runs the query. | ADR-12 |
| Console and management API | GKE behind a global load balancer, IAP for the console, Cloud Armor at the edge | Google Cloud | Cloud Run for both | The control plane and the sidecar share a runtime and a release pipeline, which keeps one projection implementation rather than two. | ADR-18 |
| Identity | Platform IdP over OIDC for humans, GKE workload identity for machines | Google Cloud | Service account keys | A credential service that needed a long-lived bootstrap credential of its own would not have solved its own problem. | ADR-14 |
| Leak detection intake | Unauthenticated partner endpoint behind Cloud Armor, hard per-partner quota | This design | Authenticated partner integration with a shared secret | Partners cannot hold a platform credential per customer; the endpoint returns one bit and structurally cannot leak anything else. | ADR-16 |
| Config and vocabulary delivery | Signed immutable artefacts, ring rollout, last-good pinning | This design | Live config pushed from a database | A poison config reaches every verification point at once, which makes it the fastest available way to take the platform down. | ADR-20 |
| Assurance | Revocation canary in a separate project and region, dead-man alerting | This design | Propagation metrics emitted by the platform itself | A platform cannot be the only judge of whether it can still revoke; the canary is the only signal that catches a silent propagation failure. | ADR-19 |
Does a long-lived developer credential carry its own scopes and expiry, verified by a signature, or is it an opaque handle that must be looked up?
Where does verification execute — in the calling process, in a sidecar beside it, or in a central service the caller calls?
Does a verification point hold every active credential, or only the ones it has seen?
What does verification return when a credential is unknown, revoked, expired or constrained out — and how much does the answer disclose?
Is a revocation a status column on the credential record, or a separate structure with its own durability guarantee?
How does a revocation reach thousands of independent verification points, and how is the mechanism's own failure detected?
What does a verification point do when it can no longer prove that its projection is fresh?
What happens when a customer needs every credential issued before a certain moment to stop working at once?
How is a credential secret stored so that a stolen database is worthless, without making verification unaffordable?
What actually happens on the day the credential store is exfiltrated?
Can the platform ever show a customer their credential secret again?
Does the credential service make authorization decisions, or only supply the inputs to them?
What shape is the scope vocabulary, who owns it, and may an entry ever be removed?
What stops key creation from being a privilege-escalation path?
Should a credential that has not been used for ninety days expire automatically?
How does a repository host tell the platform it has found a live credential, without the platform disclosing anything to an unauthenticated caller?
What is the platform allowed to do on its own when it believes a credential is compromised?
What is the topology, and does every component need the same recovery objective?
How does anyone know that revocation still works today?
How does a scope vocabulary or policy change reach every verification point without being able to break them all at once?
The terms this package uses in a specific way, and what each one is doing in the architecture.
| Package | What it is | What it does here | Considered instead |
|---|---|---|---|
| Key identifier (kid) | The public, non-secret portion of a credential that names the record. | Lets a log line, an error message and a scanner refer to a credential without holding its secret. | Logging a truncated secret, which is a secret. |
| Prefix | A fixed leading string encoding credential class and environment, for example sk_live_. | Makes a credential greppable by scanning partners and makes a test key failing against live legible in the error. | An opaque random string, which no scanner can recognise. |
| Pepper | A secret key held outside the credential store, mixed into every digest. | Makes a stolen database worthless without a second compromise. | A per-record salt, which is stored beside the record and therefore stolen with it. |
| Projection | A verification point's in-memory copy of active credentials and revocations. | Turns verification into a memory lookup with no network dependency. | A cache, which implies demand-filling and a cold path. |
| Revocation overlay | The additive, monotonic set of revocations applied on top of a projection. | Lets revocations propagate independently of, and faster than, credential state. | Updating the credential record, which couples the two. |
| Staleness | How far behind the revocation feed a projection currently is. | Returned on every response and compared against a declared ceiling; the basis of the fail-closed rule. | A TTL, which expresses an intention rather than a measurement. |
| Staleness ceiling | The bound past which a verification point stops honouring privileged scopes. | Converts an unbounded silent failure into a bounded, visible one. | Serving indefinitely, which is what every cache does by default. |
| Tenant epoch | A monotonic counter per tenant; credentials issued before the current value are invalid. | Makes bulk revocation cost one write and one message regardless of how many credentials it kills. | N individual revocations, whose propagation time scales with the incident. |
| Scope grade | A classification of each scope as read, write or admin, carried in the vocabulary. | Lets the staleness rule degrade by consequence rather than by an out-of-band list. | A hand-maintained list of sensitive scopes, which drifts from the vocabulary. |
| Verification point | Any process that can answer a verification: a library, a sidecar, or the remote API. | The unit that holds a projection, tracks staleness and applies the feed. | "The verification service", which implies a central component this design does not have. |
| Revocation canary | A synthetic credential issued, verified, revoked and re-verified continuously in production. | Makes propagation p99 a measurement rather than a claim in a document. | Instrumenting the feed, which cannot detect a subscriber that silently stopped applying. |
| Live-or-not endpoint | The unauthenticated partner interface that confirms whether a candidate string is a live credential. | Lets a scanning partner act without the platform disclosing tenant, scopes or owner. | A lookup API, which would hand an unauthenticated caller the blast radius. |
svg/<view>.svg or drawio/<view>.drawio in draw.io Desktop or at app.diagrams.net to edit. The SVG carries the diagram inside it, so it is both the picture and the source. This folder is self-contained — copy it whole and every link still resolves.