You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Rewritten 2026-08-15. The original issue bundled nine acceptance criteria.
Five of them landed as side effects of other work, and its cost model was wrong:
the dominant cost of a cold /sessions read was the SQL visibility predicate,
not Slack round trips (#751). What follows is only what is still undone.
Conversation audience is cached independently of viewer-specific decisions, with request coalescing — the audiences cache in packages/control-plane/src/http/slack-session-access.ts.
1. Resolve viewer membership as one snapshot per credential
checkMembership() still paginates conversations.members once per external scope.
A viewer with 200 private-channel scopes spends ~200 member walks on a cold read, and
the per-principal lease is short (allow 120s, deny 30s), so continued browsing re-spends
them. Nothing warms this half: the warmer observes resource facts, and membership is
per-principal.
Direction, unchanged from the original proposal:
Group candidate scopes by credentialId + credentialRevision + realmKey, and load the bot and its secret once per group.
For groups holding restricted scopes, paginate with the existing bot token:
then intersect the returned conversation ids with the candidate external scopes.
Preserve the current policy exactly: ordinary full workspace members read public-channel sessions without channel membership; private channels and MPIMs require membership; guests and external / Slack Connect identities require membership even for public channels; DMs keep the private owner-identity path and stay out of the snapshot.
slackCall() turns any non-2xx into http_<status>, so a 429 becomes a short-lived unknown and the next read retries into the same limit. Handle Retry-After while
preserving the fail-closed and degraded behavior.
Identity and OAuth constraints
Unchanged, and they still govern the snapshot design. Do not introduce a Feishu-style
identity bridge or reuse a console Slack OAuth user token — Slack user identity is
already the workspace-qualified (team_id, user_id), directly comparable with the
linked console identity. Sign in with Slack OIDC scopes cannot be mixed with Web API
scopes in the same OAuth flow: https://docs.slack.dev/authentication/sign-in-with-slack/
The existing bot template already grants channels:read, groups:read, mpim:read,
and users:read, so this requires no new scope and no user reauthorization.
Acceptance criteria
Membership work is performed once per bot credential/viewer snapshot, not once per external scope.
A 200-restricted-scope regression test proves one paginated users.conversations snapshot is reused across all scopes, with provider-call-count assertions on both the cold and warm paths.
Public-channel behavior is unchanged for full members, guests, and external users; private channels and MPIMs stay fail-closed.
Revoked credentials, deleted conversations, missing scopes, provider outages, and rate limits preserve the current definitive-denial vs. degraded-unknown distinction.
HTTP 429 honors Retry-After.
Scope 201+ remains eligible; no provider optimization reintroduces a visibility ceiling.
No console Slack Web API user token, second OAuth flow, new identity bridge, or additional bot scope is introduced.
Slack Connect behavior is verified with a bot token and a foreign-workspace user before rollout.
Priority
Lower than when this was filed. Re-measure the cold-visit budget after #793–#799 before
starting; that measurement, not this issue's original estimate, should decide whether the
snapshot is worth building.
Already landed — do not redo
/sessionsand/sessions/facetsreads share in-flight provider work — perf(cp): stop re-resolving session provider access per request #739 (resolver memo, short request snapshot, single-flight).audiencescache inpackages/control-plane/src/http/slack-session-access.ts.users.infois one call per (realm, principal) instead of one per scope, cached under the answering credential and its revision — fix(slack): designate one workspace-check credential per realm #777.conversations.infohalf of a cold visit is handled by verdict-split serving leases plus an activity-driven warmer — docs(designs): session access for the infrequent visitor #792 (design), feat(cp): verdict-split serving leases for session-access resource facts #794, feat(cp): warm session-access resource facts from session activity #798.What is left
1. Resolve viewer membership as one snapshot per credential
checkMembership()still paginatesconversations.membersonce per external scope.A viewer with 200 private-channel scopes spends ~200 member walks on a cold read, and
the per-principal lease is short (allow 120s, deny 30s), so continued browsing re-spends
them. Nothing warms this half: the warmer observes resource facts, and membership is
per-principal.
Direction, unchanged from the original proposal:
Group candidate scopes by
credentialId + credentialRevision + realmKey, and load the bot and its secret once per group.For groups holding restricted scopes, paginate with the existing bot token:
then intersect the returned conversation ids with the candidate external scopes.
Preserve the current policy exactly: ordinary full workspace members read public-channel sessions without channel membership; private channels and MPIMs require membership; guests and external / Slack Connect identities require membership even for public channels; DMs keep the private owner-identity path and stay out of the snapshot.
Slack documents that a bot token plus the
userparameter returns the conversationsshared by the bot and that user:
https://docs.slack.dev/reference/methods/users.conversations/
2. Honor
Retry-Afteron HTTP 429slackCall()turns any non-2xx intohttp_<status>, so a 429 becomes a short-livedunknownand the next read retries into the same limit. HandleRetry-Afterwhilepreserving the fail-closed and
degradedbehavior.Identity and OAuth constraints
Unchanged, and they still govern the snapshot design. Do not introduce a Feishu-style
identity bridge or reuse a console Slack OAuth user token — Slack user identity is
already the workspace-qualified
(team_id, user_id), directly comparable with thelinked console identity. Sign in with Slack OIDC scopes cannot be mixed with Web API
scopes in the same OAuth flow:
https://docs.slack.dev/authentication/sign-in-with-slack/
The existing bot template already grants
channels:read,groups:read,mpim:read,and
users:read, so this requires no new scope and no user reauthorization.Acceptance criteria
users.conversationssnapshot is reused across all scopes, with provider-call-count assertions on both the cold and warm paths.Retry-After.Priority
Lower than when this was filed. Re-measure the cold-visit budget after #793–#799 before
starting; that measurement, not this issue's original estimate, should decide whether the
snapshot is worth building.