Expand description
Per-plan limits and the request envelope (paid-path plan S3, gaps G4 and G5).
Until now the server had no abuse envelope at all: no quota, no
timeout, no concurrency bound — safe only because the one caller
was the operator. This module is the table every limit is read
from, keyed by the plan recorded on the key (see crate::keys),
so a limit lives in exactly one place instead of being a literal
scattered through the handlers that enforce it.
Two plans exist. self-hosted is the operator’s own keys: every
limit is None, the envelope does not apply — an operator is
trusted with their own box. hosted is the paid tier from the
plan: US$19/month per key, 25 active indexes, 10,000 queries per
month, a 50,000-file cap per index, and the ingest caps S2 shipped
as interim constants (now this table’s hosted row). A plan name
the table does not know — an admin can type anything after
--plan — resolves to the hosted row, never the operator’s:
an unrecognized plan must fail toward the tenant envelope, not
toward unlimited.
“Per month” is the UTC calendar month: the metering log’s
timestamps are UTC, a key’s usage in a month is the count of its
records whose ts falls in that month, and the quota resets when
the UTC month rolls over. There is no rolling window and no
per-key anniversary — the count is the log, so the rule had to be
one a reader of the log can apply with a text search.
Two bounds are not per-plan because they protect the host, not a
tenant: GLOBAL_MAX_CONCURRENT_QUERIES caps concurrent query
requests across all keys. A query loads its whole graph into RAM
(~121 MB at 46k nodes by the project’s scale gate; a hosted index
at the 50,000-file cap extrapolates to roughly 600 MB), and the
Aetheris host has 4 vCPUs and ~10 GB of headroom shared with a
local-model stack — 4 concurrent loads is one per vCPU, worst
case ≈2.4 GB of graph RAM, and more concurrency would only queue
CPU-bound work behind itself.
Structs§
- Envelope
- The process-wide envelope state: the global query semaphore and one index-operation semaphore per key.
- Plan
Limits - One plan’s limits, resolved.
Noneis always “no limit” — the field is absent from the envelope, not set to a large number, so an unlimited plan pays no bookkeeping for limits it does not have.
Constants§
- GLOBAL_
MAX_ CONCURRENT_ QUERIES - Concurrent query requests admitted across all keys and plans — the graph-load semaphore of gap G5. See the module docs for the host arithmetic. Requests past the cap wait for a slot rather than being refused: a query is cheap to queue and expensive to retry, the opposite of an index operation.
- HOSTED_
MAX_ ACTIVE_ INDEXES - Active indexes a hosted key may hold at once (the plan’s offer).
- HOSTED_
MAX_ CONCURRENT_ INDEX_ OPS - Index operations one hosted key may run at a time. One, on purpose: the permit is held from the active-index quota check through the ownership record, so a key’s creations are serialised and the quota cannot be raced past; and on a shared 4-vCPU box, one tenant’s extraction never crowds out another’s queries.
- HOSTED_
MAX_ FILES_ PER_ INDEX - Files one hosted index may be built from (the plan’s repo cap).
- HOSTED_
MAX_ QUERIES_ PER_ MONTH - Metered queries a hosted key may make per UTC calendar month.
- HOSTED_
QUERY_ TIMEOUT_ SECS - Wall-clock budget for one hosted query. The scale gate’s queries answer in well under a second at 46k nodes; 30 s is generous headroom over the largest graph the hosted file cap admits, while still bounding how long one request may hold a graph-load slot.
Functions§
- plan_
limits - The limits a plan name resolves to. Only
self-hostedis unlimited;hosted— and any name the table does not know — gets the tenant envelope (see the module docs for why unknown fails toward hosted). - query_
envelope - Middleware over the query routes: the global concurrency cap and the caller’s per-plan query timeout.