Documentation
¶
Overview ¶
Package svcorg resolves — and memoizes — the organization a verified service token acts on behalf of (cloud-api → commerce per-org billing).
WHY THIS EXISTS (money-critical, incident 2026-07-04):
The service-token auth branch used to call org.GetOrCreate("Name=", slug) on EVERY request, on the caller's cancelable HTTP request context. Under concurrent load (many orgs, e2e first-touch of NEW orgs, the 15-min auto-recharge cron all sharing commerce's single SQLite writer), that read-then-maybe-create serialized behind the writer; when the wait exceeded cloud-api's request deadline the query returned "context canceled", the auth branch fell through to the legacy per-org-token path, tried to Peek the 64-hex service token as a JWT ("Invalid Segments"), and 401'd — so cloud-api's balance gate fail-secured to 402 and real customers could not run paid inference.
This package removes the per-request datastore hit from the steady state and decouples resolution from the caller's deadline:
- CACHE: an LRU keyed by org slug with a short TTL. A hit does ZERO datastore work — the common path (org already exists) touches no SQLite at all.
- SINGLEFLIGHT: a cache miss for a slug collapses concurrent resolvers into ONE datastore GetOrCreate, so a burst of first requests for a brand-new org (the e2e create-storm) performs exactly one create, not N contending writes.
- DETACHED + BOUNDED: resolution runs on a fresh context.Background() with its OWN timeout, so a slow resolve fast-fails inside commerce instead of being canceled mid-create by an upstream deadline (which is what left the writer wedged and cascaded to the confusing 401).
It memoizes ONLY the (slug → *Organization) mapping. It does not change org derivation, permissions, or per-org isolation — the caller still scopes every downstream query by the returned org's Name. The money gate is untouched.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrResolveFailed = errors.New("svcorg: could not resolve organization for service token")
ErrResolveFailed wraps any failure to resolve/provision the org for a verified service token. Callers MUST treat it as retryable (HTTP 503) — never as a bad credential — because the service token itself was already verified; only the backing store hiccuped.
Functions ¶
func Invalidate ¶
func Invalidate(slug string)
Invalidate drops a slug from the cache so the next Resolve re-reads it. Call after mutating an org's identity fields (Name/Enabled/Live) so a change is not masked by the TTL window. Safe to call for an absent slug.
func Resolve ¶
func Resolve(slug string) (*organization.Organization, error)
Resolve returns the organization named slug for a VERIFIED service-token request, creating it if it does not yet exist. The slug MUST already be validated by the caller (non-empty, not bearer-shaped) — this function trusts it and never re-derives policy from it.
Steady state (slug cached, unexpired) does NO datastore work. A miss resolves under singleflight on a detached, bounded context so concurrent misses for the same slug share one GetOrCreate and a slow store fast-fails with ErrResolveFailed instead of blocking on the caller's deadline.
Types ¶
This section is empty.