Skip to content

Every identity is a platform-namespaced id

Context

Configuration scopes, grants and memory all key on who or where a request came from. Slack user and channel ids are opaque strings that look nothing like a Cloudflare Access subject, an HTTP bearer's name or a schedule. If each surface passed its raw native id into the core, a rule written for one surface would silently match or miss on another, and every store would need to know which platform an id came from.

Decision

Ids carry their platform as a prefix, and that prefixed string is the one identity every later decision keys on:

PrefixMeaning
slack:U…, slack:C…, slack:C…:<ts>Slack user, channel, thread
http:<subject>, http:<channel>, http:<channel>:<thread>HTTP ingress caller and its channel / thread
mcp:<subject>MCP caller
access:<sub>, access:svc:<common_name>Cloudflare Access user and service token
cli:localthe local CLI
schedule:<name>, agent:<name>a schedule or agent acting as a principal

Grants in configuration, per-user and per-channel config scopes, thread stickiness and memory scopes (user:slack:U…, channel:slack:C…) all use these ids. A new adapter brings its own prefix and nothing else changes.

Consequences

  • One resolver reads grants for an id, so two surfaces resolving the same person get the same permissions.
  • Adding user, channel and repo scopes to memory was a change to the scope derivers, not to the store's schema, because the key shape was already there.
  • The HTTP ingress mirrors Slack's channel and thread shape rather than inventing its own, so everything that works per thread on Slack works per thread over HTTP.
  • Ids are strings everywhere, so nothing in the type system stops a caller from passing a bare U0123. The adapters are the only place raw ids exist, and tests on each adapter pin the prefix.

Alternatives rejected

  • Raw platform ids with a separate platform field. Two fields to keep together in every store, every rule and every log line.
  • Internal numeric ids with a mapping table. A mapping store that every surface must consult before it can do anything, for no benefit the prefix does not already provide.

Pattern

A rules table over namespaced principals. The id is the correlation identifier that ties a request together across surfaces and stores.