edge

package
v1.801.413 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package edge is the runtime-mutable store for the cloud edge ("gateway role") policy: the CORS allowlist, the pre-auth per-client-IP flood cap, and the authenticated per-org rate ceiling. It is a LEAF package (stdlib + the Hanzo SQLite driver only, no import of the root cloud package) so BOTH consumers can use it without an import cycle:

  • the edge middleware (package cloud, middleware_edge.go / middleware_ratelimit.go) reads the effective policy live, per request, so an operator's change takes effect without a redeploy;
  • the /v1/gateway HTTP subsystem (clients/gateway) serves GET/PUT over the SAME store, IAM-scoped.

SCOPES. There are two, keyed by org in one encrypted per-tenant SQLite file:

  • PLATFORM policy — the row stored under the admin org (cfg.AdminOrg). It holds the pre-auth edge knobs (CORS origins, per-IP cap + window) that have no tenant at evaluation time (CORS preflight + the anonymous-flood cap run BEFORE identity). Only a SuperAdmin may write it. It is layered over the static boot defaults (env/flags), so an un-provisioned deployment behaves exactly as the static config until an operator PUTs an override.
  • PER-ORG policy — a tenant's own row, holding its self-service edge config: OrgRPM (authenticated rate ceiling), CacheTTLSec + CachePaths (edge-cache TTL, default and per-path), and Methods (accepted-method allowlist). An org admin writes its own; a SuperAdmin may write any. An unset field inherits the platform default, then the static default.

Fail-soft: every resolver (Platform/OrgRPM/CacheTTL/Methods) returns the static/platform default on any store error, so a policy-store outage never takes the edge down. Writes fail loud (an unavailable store returns an error to the PUT handler).

Index

Constants

View Source
const (
	// ModeShadow scores and records; it never refuses. The DEFAULT.
	ModeShadow = "shadow"
	// ModeLive enforces the scorer's action.
	ModeLive = "live"
)

The abuse gate's two postures. They are values, not booleans, because "off" and "watching" are different states and a deployment must be able to tell which one it is in.

View Source
const (
	// CredSession is a validated bearer that is not an opaque key: a browser or
	// CLI session minted by IAM for a person.
	CredSession = "session"
	// CredSecret is an sk- key: a machine credential issued to a principal.
	// It may not be shipped to a browser, so possession attributes.
	CredSecret = "secret"
	// CredPublishable is a pk- key: org-only by design, shipped in client
	// bundles, and therefore trivially copied. It names a tenant, not a caller.
	CredPublishable = "publishable"
	// CredAnonymous is no credential, or one the identity boundary refused.
	CredAnonymous = "anonymous"
)

The credential classes. A syntactic fact about HOW a caller authenticated — the credential's own shape plus whether the identity boundary validated it. The classification of a REQUEST is cloud's (it holds the request); the vocabulary and the lane rule are here, where the lanes are counted, so there is one answer to "what lane is this" rather than one per reader.

View Source
const (
	AgencyAgent   = "agent"
	AgencyHuman   = "human"
	AgencyBot     = "bot"
	AgencyUnknown = "unknown"
)

The lanes. Values, not booleans, because "we could not tell" is a distinct state from "we decided it is a bot", and collapsing them is how a false positive becomes a blocked customer.

View Source
const (
	// StrainClear — below the ceiling. Every caller is measured.
	StrainClear = "clear"
	// StrainFull — at the ceiling. A new caller is admitted only if a sweep frees
	// a slot first; the callers already here are unaffected.
	StrainFull = "full"
	// StrainRefuse — the ceiling turned a caller away inside this window. That
	// caller is UNMEASURED: its counts are zero because nothing was counted, not
	// because nothing happened, and the gate is told so rather than treating it
	// as a caller making its first request.
	StrainRefuse = "refuse"
	// StrainBlind — the request carried NO identity at all: no credential the
	// boundary validated, and no client address. There is nothing to count it
	// against, so it is counted only as traffic and nothing is ever held against
	// it.
	//
	// This is a DEPLOYMENT fact, not an attack: it is what a plane looks like when
	// the client address never arrives — an edge that terminates the connection
	// without passing the peer on (a TCP load balancer with no PROXY protocol, an
	// ingress that does not forward). The alternative spelling — key every
	// unidentifiable caller under the empty address — would put the entire
	// internet in ONE row, make it look like the worst credential-stuffing run
	// ever recorded, and enforce one verdict against everybody. "We cannot tell
	// who this is" must not be spelled the same way as "this is caller X".
	StrainBlind = "blind"
)

The states of a scope's ceilings, graded. A bound that binds MUST be readable: a control that quietly stops measuring is worse than no control, because the numbers it keeps publishing look like an answer.

View Source
const MaxBytes = 128 << 20

MaxBytes is the whole sensor's memory ceiling. Every admission charges its entry's published size against it and an admission that would exceed it is REFUSED — never satisfied by removing something else — so this is the number the process cannot pass, not an estimate of what it usually costs.

128 MiB against a pod that requests 6 GiB: a sensor that can be a measurable fraction of the process it protects is a liability, and a sensor whose ceiling is a product of three counts (tenants × callers × bytes) is not a ceiling at all — that spelling published 60 GB and called it a bound.

View Source
const MaxCacheTTLSec = 7 * 24 * 60 * 60

MaxCacheTTLSec bounds a cache TTL (7 days) so a fat-fingered PUT can't pin a stale edge response indefinitely.

Variables

This section is empty.

Functions

func Lane added in v1.801.381

func Lane(class string, p Pattern) string

Lane classes a request from its credential class and the pattern its caller has been showing. Pure and total: same inputs, same lane, no clock, no I/O — which is what makes the table test the whole specification.

It is computed HERE, inside the observation that produced the pattern, because the lane is DERIVED from the counts: a caller cannot state it, and a gate that tried to pass it in had to pass it before it knew the counts, which is how the lane split came to report "unknown" for every request ever made.

The bot rule is deliberately CONJUNCTIVE. Unattributable alone is not bot: every first request from every new integration is unattributable. It takes an abuse SHAPE as well — many credentials from one address (stuffing), a wall of refusals (guessing), or a path sweep (scraping) — and then the caller is judged, not the anonymity.

Types

type Hold added in v1.801.381

type Hold struct {
	// Action is the enforced action: challenge, restrict or block.
	Action string
	// Reason is the scorer's short cause, carried for the audit record.
	Reason string
	// Decision is the scorer's decision id, so a held enforcement is traceable
	// back to the judgement that produced it.
	Decision string
	// Until is when the hold lapses and the scorer is asked again.
	Until time.Time
}

Hold is a verdict the gate is enforcing without re-asking. A held verdict is the reason an attack does not cost one screen per request.

type Pattern added in v1.801.381

type Pattern struct {
	// Requests is how many requests this caller made in the window.
	Requests int
	// Failures is how many of them ended 401 or 403.
	Failures int
	// Paths is the approximate number of distinct paths it touched, saturating at 64.
	Paths int
	// Peers is the approximate number of distinct credentials this client IP
	// presented in the window, saturating at 64 — the stuffing signature.
	Peers int
	// Lane is the lane this request was classed into, derived from Class and the
	// counts above by Lane. It is an OUTPUT: the caller does not state it and the
	// gate does not compute it a second time.
	Lane string
	// Strain is what the scope's ceiling did to THIS observation: "" when the
	// caller was measured, StrainRefuse when the ceiling was full of live callers
	// and this one could not be admitted — so every count above is zero because
	// nothing was measured. A gate reads it to avoid treating an unmeasured caller
	// as a caller making its first request.
	Strain string
	// Rise names a strain grade the FIRST time this scope reaches it, and is ""
	// on every other observation. It exists so a degraded sensor produces one log
	// line per grade change instead of one per request, without this package
	// holding a logger.
	Rise string
}

Pattern is what the sensor knows about one caller right now. Every count is over the rolling window — no scores, no thresholds, no judgement.

type Policy

type Policy struct {
	// CORSOrigins is the PLATFORM-scope CORS allowlist EdgeCORS admits: an exact
	// origin, a bare host, or a "*.host" wildcard. Writable only by a SuperAdmin —
	// CORS is evaluated before identity, so it has no tenant to scope to.
	CORSOrigins []string `json:"cors_origins,omitempty"`
	// PerIPRPM is the PLATFORM-scope pre-auth flood cap: requests EdgeRateLimit
	// admits per WindowSec from one client IP. SuperAdmin-only, same reason.
	PerIPRPM int `json:"per_ip_rpm,omitempty"`
	// WindowSec is the window PerIPRPM is counted over, in seconds. SuperAdmin-only.
	WindowSec int `json:"window_sec,omitempty"`

	// OrgRPM is the org's OWN authenticated rate ceiling, requests per minute, as
	// ScopeRateLimit enforces it. Unset inherits the platform default, then the
	// static boot default.
	OrgRPM int `json:"org_rpm,omitempty"`
	// CacheTTLSec is the org's default edge-cache TTL for its responses, in seconds;
	// 0 means no caching. Unset inherits the platform default.
	CacheTTLSec int `json:"cache_ttl_sec,omitempty"`
	// CachePaths overrides CacheTTLSec per path PREFIX (key "/v1/models" → seconds).
	// The longest matching prefix wins.
	CachePaths map[string]int `json:"cache_paths,omitempty"`
	// Methods is the allowlist of HTTP methods the edge accepts for this org. Empty
	// means all are accepted.
	Methods []string `json:"methods,omitempty"`
	// Mode is the abuse gate's posture for THIS scope: "shadow" scores traffic and
	// records the verdict without acting on it, "live" enforces it. Unset means
	// shadow.
	//
	// It is the one per-org field that does NOT inherit. Every other field here
	// layers a platform default under the org's own value, which is right for a
	// default: a tenant that sets no rate ceiling should get the platform's. Mode
	// is not a default, it is an ARMING DECISION — it is what makes a statistical
	// judgement start refusing real traffic — and inheriting it means arming one
	// scope arms every tenant that never asked for it, without a write to their
	// row and without anything in their config changing. So a tenant is live only
	// if that tenant's OWN row says live, and the platform row's mode governs
	// exactly one scope: the anonymous lane, which has no tenant of its own.
	//
	// It is also not self-service. Writing it requires SuperAdmin (see the
	// /v1/gateway config op): the subject of an abuse control does not get to
	// switch the control off.
	Mode string `json:"mode,omitempty"`

	// UpdatedAt is the unix second this policy row was last written. Server-stamped;
	// a client-supplied value is ignored.
	UpdatedAt int64 `json:"updated_at,omitempty"`
	// UpdatedBy is the validated user id that wrote this policy row. Server-stamped;
	// a client-supplied value is ignored.
	UpdatedBy string `json:"updated_by,omitempty"`
}

Policy is the edge policy for one scope. Zero-valued fields mean "inherit" (from the static default, then the platform policy) — so a PUT that sets only OrgRPM leaves the platform CORS/per-IP untouched. Every field is ENFORCED by a consumer; there is no stored-but-ignored knob. Every field carries its OWN doc comment rather than sharing a section header, because zipdoc lifts a field's comment into the published schema property and a header lifted onto three fields would document the GROUP where the FIELD goes.

func (*Policy) Normalize

func (p *Policy) Normalize()

Normalize canonicalizes free-form input in place (methods → upper-case, trimmed) so a config round-trips in one stable shape. Applied at the write boundary, before Validate.

func (Policy) Validate

func (p Policy) Validate() error

Validate checks structural bounds on the client-settable fields, returning a human-readable error for a 400. It is the ONE validation gate shared by every writer, so the store never persists an incoherent policy.

type Signal added in v1.801.381

type Signal struct {
	// Org is the VERIFIED tenant; "" for a caller with no tenant — the anonymous
	// lane, which is one scope for the whole internet.
	Org string
	// Cred is the fingerprint of a credential the identity boundary VALIDATED,
	// and "" when it validated none. It is the ONLY thing that can key a caller,
	// because it is the only credential fact the caller does not choose: we minted
	// it, to a named principal, and we can revoke it. A request whose credential
	// did not validate has no identity beyond where it came from, and is keyed on
	// its address.
	Cred string
	// Presented is the fingerprint of whatever credential the request carried,
	// valid or not. It is counted only as SPREAD — how many distinct credentials
	// one address tried, which is the stuffing signature — and is never a key: a
	// value the caller picks per request cannot be an identity.
	Presented string
	// IP is the client address, resolved by cloud.ClientIP from the socket peer
	// and our own forwarding hops. It is the anonymous caller's only identity.
	IP string
	// Path is the request path, hashed into the spread word (never stored).
	Path string
	// Class is the credential class the identity boundary's answer implies:
	// session, secret, publishable or anonymous. It selects the lane together with
	// the pattern; it is not itself a lane.
	Class string
}

Signal is one observation: the facts about a request that the sensor counts. It carries credential FINGERPRINTS, never a credential — see cloud.Fingerprint.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store persists Policy per org to one encrypted SQLite file and resolves the effective platform / per-org policy for the edge middleware, cached with a short TTL. A nil db (SQLite unavailable at boot) degrades to STATIC-ONLY: reads return the static default, writes error — the edge never goes down.

func New

func New(dataDir, adminOrg string, static Policy) (*Store, error)

New opens (or creates) the deployment's gateway policy database under dataDir and returns a Store layered over the static boot defaults. On any open/migrate error it logs nothing here (the caller owns logging) and returns a static-only Store plus the error, so the caller can wire the edge middleware with a working fallback regardless.

func (*Store) CacheTTL

func (s *Store) CacheTTL(org, path string) int

CacheTTL returns the edge-cache TTL (seconds) for org+path: the org's own longest-matching cache_paths prefix wins, else its default CacheTTLSec, else the platform default, else 0 (no caching). Cached with a short TTL, fail-open (0). The edge cache middleware reads this live, per request, so an operator's PUT takes effect without a redeploy (mirrors OrgRPM / Platform).

func (*Store) Close

func (s *Store) Close() error

Close releases the underlying handle (nil-safe for a static-only Store).

func (*Store) Effective

func (s *Store) Effective(org string) Policy

Effective returns the read-back view for org: the platform edge policy (CORS + per-IP + window) with the org's OWN per-org config (rate ceiling, cache TTL / per-path overrides, method allowlist) overlaid. This is what GET /v1/gateway/config returns — a tenant sees the platform edge policy in force plus its own configuration.

func (*Store) Get

func (s *Store) Get(ctx context.Context, org string) (Policy, bool, error)

Get returns the raw stored policy for org (found=false when none). A static-only store always reports not-found.

func (*Store) Methods

func (s *Store) Methods(org string) []string

Methods returns the allowlist of HTTP methods the edge accepts for org (nil = all allowed): the org's own list wins, else the platform default. Fail-open (nil). The edge method-guard reads this live.

func (*Store) Mode added in v1.801.381

func (s *Store) Mode(org string) string

Mode returns the abuse gate's posture for org: THAT ORG'S OWN ROW, else shadow. Cached with the same short TTL as every other per-org resolver, and fail-soft to SHADOW — a policy-store outage must not be the reason a tenant starts being refused.

IT DOES NOT INHERIT, and that is the point. It used to fall back to the platform row, so arming the one lane that has no tenant — the anonymous lane, which is where a bad bot calls from — armed every tenant in the estate at the same time: one PUT, and a statistical judgement began enforcing against customers whose own config still said nothing and whose operators were never asked. An arming decision that reaches a tenant it was not written for is not a default, it is an accident waiting for a scorer to have a bad day.

An EMPTY org is the anonymous lane, and it resolves to the PLATFORM row — which is that lane's OWN row, not an inherited one: a caller with no tenant still has to be governed by something, and the platform scope is what governs a request that has no tenant at evaluation time. A SuperAdmin arms it by targeting the reserved admin org (PUT /v1/gateway/config?org=<adminOrg> {"mode":"live"}), since the admin org's row IS the platform row. One mechanism, one scope per write.

func (*Store) OrgRPM

func (s *Store) OrgRPM(org string) int

OrgRPM returns the authenticated per-org rate ceiling (requests/min) for org: the org's own row wins, else the platform default's OrgRPM, else 0 (no policy limit). Cached with a short TTL, fail-open (0). Called per-request by ScopeRateLimit (post-identity).

func (*Store) Platform

func (s *Store) Platform() Policy

Platform returns the effective PLATFORM policy — the admin-org row merged over the static defaults — cached with a short TTL and fail-open to the static default. Called per-request by EdgeCORS/EdgeRateLimit (pre-identity).

func (*Store) Put

func (s *Store) Put(ctx context.Context, org string, p Policy) (Policy, error)

Put upserts the policy for org (merged over any existing row so a partial write is additive) and invalidates the resolver cache. Errors on a static-only store — a write must never silently vanish.

func (*Store) PutPlatform

func (s *Store) PutPlatform(ctx context.Context, p Policy) (Policy, error)

PutPlatform upserts the PLATFORM policy — the row under the admin org — merged over any existing platform row. This is the ONLY write that may touch the pre-auth edge knobs (CORS, per-IP cap); the /v1/gateway subsystem gates it on SuperAdmin. Targeting the admin org explicitly (not the caller's possibly org-switched X-Org-Id) is what makes a SuperAdmin's platform PUT land on the platform row regardless of which tenant they are currently viewing.

type Traffic added in v1.801.381

type Traffic struct {
	// contains filtered or unexported fields
}

Traffic is the sensor. Safe for concurrent use; every method takes the one lock for a handful of arithmetic operations, which is the same cost profile as the token buckets already on this path.

func NewTraffic added in v1.801.381

func NewTraffic() *Traffic

NewTraffic returns an empty sensor. The hash seed is per-process and never leaves it, so a fingerprint or path bit cannot be reproduced off-box.

func (*Traffic) Deny added in v1.801.381

func (t *Traffic) Deny(org string, now time.Time)

Deny records that the gate refused a request. It bumps ONLY the refusal counter: the request itself was already counted by Observe on the way in, and counting it twice would make an org's own report say it sent more traffic than it did — the sort of number a customer notices before we do.

func (*Traffic) Fail added in v1.801.381

func (t *Traffic) Fail(s Signal, now time.Time)

Fail records that a request ended 401 or 403. Called on the way OUT, because the outcome is not known on the way in — and it is the outcome, not the attempt, that separates a client with a stale token from one guessing tokens.

func (*Traffic) Held added in v1.801.381

func (t *Traffic) Held(s Signal, now time.Time) (Hold, bool)

Held returns the verdict in force for this caller, if one has not lapsed.

func (*Traffic) Hold added in v1.801.381

func (t *Traffic) Hold(s Signal, h Hold, d time.Duration, now time.Time)

Hold pins a verdict to a caller for d, so an attack costs one screen rather than one per request. d is clamped to holdCap: enforcement without a fresh judgement is bounded, always. Every string is clamped at this door, so a held verdict has a published size.

A hold on a caller the sensor could not admit is not stored — there is nowhere to put it — and the gate simply asks again next request. Enforcement is never silently downgraded: what is stored is enforced, and what could not be stored was answered by the scorer on this request anyway.

func (*Traffic) Lapsed added in v1.801.381

func (t *Traffic) Lapsed(s Signal, now time.Time) bool

Lapsed reports whether a verdict WAS in force for this caller and has since expired. It is what stops a hold from buying an attacker a free minute at a time: a caller the scorer refused is asked about again on its next request after the hold ends, whether or not the local pattern still looks unusual.

That distinction matters because the scorer sees more than the sensor does. A verdict reached from the org's own history — prior accounts on this device, a spend curve, a chargeback — leaves no trace in a rolling minute of request counts, so waiting for the local pattern to re-trip would wait forever.

The record is dropped by Release, which the gate calls once the scorer allows the caller again. So this is true exactly between "a hold ended" and "the scorer said it is fine now".

func (*Traffic) Observe added in v1.801.381

func (t *Traffic) Observe(s Signal, now time.Time) Pattern

Observe counts one request, classes it into a lane, and returns what is now known about its caller. Called on the way IN, before the handler runs, so the gate decides on the pattern that includes this request.

func (*Traffic) Release added in v1.801.381

func (t *Traffic) Release(s Signal)

Release drops any held verdict for this caller — the operator's undo, and the gate's own acknowledgement that a lapsed hold has been re-judged.

func (*Traffic) Screen added in v1.801.381

func (t *Traffic) Screen(org, refusal string, now time.Time)

Screen records that the org's traffic was put to the scorer once, and whether an answer came back: refusal is "" for a scored verdict and names the failure otherwise (cloud.RiskVerdict.Refusal).

It is the BILLABLE UNIT of the risk product, counted here as well as metered, because the two answer different questions: the ledger answers "what is owed" and only once the SKU carries a price, while this answers "how much judgement did this org consume" from the first request.

The split is what makes a dark scorer visible. An unanswered screen allows ordinary traffic, so a scorer that has silently stopped answering looks exactly like a quiet day — unless the count of questions that got no answer is a number on the org's own report, next to the ones that did.

func (*Traffic) View added in v1.801.381

func (t *Traffic) View(org, mode string, now time.Time) TrafficView

View returns org's live picture. It reads ONE scope's tables and cannot reach another's — not because it filters, but because it never holds a reference to anything else.

The scan happens under the lock and the SORT does not. Every request on the plane needs that same lock to be observed, so holding it across an n·log n comparison of a full table would put the report on the critical path of all the traffic it is reporting on.

type TrafficCaller added in v1.801.381

type TrafficCaller struct {
	// Cred is the caller's key: a credential fingerprint (a per-process one-way
	// digest, not a key) for a validated caller, and "ip:<addr>" for one that
	// presented no credential we could validate.
	Cred string `json:"cred"`
	// Requests is its request count in the window.
	Requests int `json:"requests"`
	// Failures is how many ended 401 or 403.
	Failures int `json:"failures"`
	// Paths is the approximate number of distinct paths it touched (max 64).
	Paths int `json:"paths"`
	// Action is the verdict currently held against it, if any.
	Action string `json:"action,omitempty"`
	// Reason is why that verdict was reached.
	Reason string `json:"reason,omitempty"`
	// HeldUntil is when the held verdict lapses, unix seconds.
	HeldUntil int64 `json:"held_until,omitempty"`
}

TrafficCaller is one caller's line in the view.

type TrafficView added in v1.801.381

type TrafficView struct {
	// Org is the scope this view was taken for — the validated principal's own,
	// never a value the caller supplied. Empty names the anonymous lane, the one
	// scope that has no tenant.
	Org string `json:"org"`
	// WindowSec is the span the counts cover, in seconds.
	WindowSec int `json:"window_sec"`
	// Mode is the abuse gate's posture for this scope: "shadow" records the scorer's
	// action without enforcing it, "live" enforces it.
	Mode string `json:"mode"`
	// Requests is how many requests this scope made in the window.
	Requests int `json:"requests"`
	// Denied is how many of them the gate refused.
	Denied int `json:"denied"`
	// Screens is how many of them were put to the scorer — the billable unit of
	// the risk product. Counted from the first request, whatever the SKU costs.
	Screens int `json:"screens"`
	// Unscored is how many of those screens got NO answer — the scorer was absent,
	// stuck, slow, erroring or silent. An unanswered screen allows ordinary
	// traffic, so this is the number that separates "a quiet day" from "the judge
	// stopped answering and nothing said so".
	Unscored int `json:"unscored,omitempty"`
	// Strain is what this scope's ceilings are doing: "clear" below them, "full"
	// at them, "refuse" once a caller has been turned away inside this window —
	// which means that caller is UNMEASURED and the numbers here are a sample
	// rather than a census. It is reported rather than logged because the
	// alternative — a bound that degrades a scope silently — is the failure this
	// design exists to rule out. No other scope can move it.
	Strain string `json:"strain"`
	// Tracked is how many callers this scope holds state for right now, and
	// Ceiling is the most it may hold. Tracked == Ceiling is the fact a bound
	// that binds cannot hide.
	Tracked int `json:"tracked"`
	// Ceiling is the most callers this scope may hold at once.
	Ceiling int `json:"ceiling"`
	// Refused is how many callers this scope's ceilings turned away in the window.
	Refused int `json:"refused,omitempty"`
	// Blind is how many requests in the window carried no identity to attribute
	// them to — no validated credential and no client address. Non-zero on a
	// public plane means the client address is not reaching this process (a TCP
	// load balancer with no PROXY protocol in front of it, typically), so this
	// scope's callers cannot be told apart and nothing can be held against them.
	Blind int `json:"blind,omitempty"`
	// Lanes is the request count per lane — agent, human, bot, unknown. This is
	// the split that separates a customer's automation from a scraper.
	Lanes map[string]int `json:"lanes"`
	// Callers is the scope's busiest callers this window. A credentialed caller
	// appears as a FINGERPRINT — a per-process one-way digest: enough to recognise
	// the same caller across requests, never enough to reconstruct the credential.
	Callers []TrafficCaller `json:"callers"`
}

TrafficView is one scope's live edge picture. Every number in it was counted under that scope's own tables; there is no aggregate here that another tenant contributed to.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL