Files
helexa/cortex.example.toml
rob thijssen bc74e0e95f
Some checks failed
CI / Format (push) Successful in 38s
CI / CUDA type-check (push) Successful in 1m39s
CI / Clippy (push) Successful in 2m26s
CI / Test (push) Successful in 4m49s
CI / Build cortex SRPM (push) Has been skipped
CI / Build neuron SRPM (push) Has been skipped
CI / Publish cortex to COPR (push) Has been skipped
CI / Publish neuron to COPR (push) Has been skipped
CI / Bump version in source (push) Has been skipped
build-prerelease / Package helexa-bench RPM (push) Blocked by required conditions
build-prerelease / Resolve version stamps + change detection (push) Successful in 32s
build-prerelease / Build neuron-blackwell (push) Successful in 1m40s
build-prerelease / Build neuron-ada (push) Successful in 2m19s
build-prerelease / Build neuron-ampere (push) Successful in 2m22s
build-prerelease / Lint (fmt + clippy) (push) Successful in 2m49s
build-prerelease / Build cortex binary (push) Successful in 3m0s
build-prerelease / Test (push) Successful in 4m25s
build-prerelease / Package cortex RPM (push) Successful in 1m32s
build-prerelease / Package helexa-neuron-ada RPM (push) Successful in 1m50s
build-prerelease / Package helexa-neuron-ampere RPM (push) Successful in 1m49s
build-prerelease / Package helexa-neuron-blackwell RPM (push) Successful in 1m54s
build-prerelease / Build helexa-bench binary (push) Successful in 2m12s
build-prerelease / Publish to rpm.lair.cafe (unstable) (push) Has been cancelled
feat(#47 phase 1a): EntitlementProvider trait + local/static provider
Stage 1's build seam (#50): the interface auth, metering, and budget
enforcement all hang off, with a local/static provider so the A0
amplification fix can land before any upstream clearing house exists.
The future helexa-upstream client (#57) is just another impl.

- cortex-core::entitlements: Principal {account_id, key_id}, CapWindow
  (Balance | Rolling{seconds}), Reservation handle, BudgetSnapshot,
  AuthError/BudgetError, and the async EntitlementProvider trait
  (resolve / reserve / settle / release / snapshot). BudgetError carries
  the window semantics so callers pick the #63 code (rate_limit_exceeded
  + Retry-After vs insufficient_quota) without the provider touching HTTP.
- cortex-core::config: [entitlements] section on GatewayConfig
  (require_auth + [[entitlements.keys]] with account_id, optional key_id,
  hard_cap, window). Additive + serde(default) — anonymous/uncapped when
  omitted, so existing setups are unaffected.
- cortex-gateway::entitlements_local: LocalEntitlementProvider. Budget
  math serialized under one Mutex so spent+reserved can never exceed a
  hard cap under concurrency (the #52 guarantee); rolling windows reset
  lazily; uncapped keys (no hard_cap) always reserve but still meter.
- CortexState gains Arc<dyn EntitlementProvider> + require_auth, built in
  from_config. Not yet consumed by the request path — auth middleware is
  1b (#49), enforcement is 1d (#52).
- cortex.example.toml documents the section; test GatewayConfig literals
  updated for the new field.

6 provider unit tests (resolve, unknown-key, round-trip, balance/rolling
over-cap codes, uncapped infra key). Local fmt/clippy/test all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:00:05 +03:00

93 lines
3.1 KiB
TOML

# cortex.example.toml — example configuration
#
# Copy to cortex.toml and adjust for your environment.
#
# Environment variable overrides use CORTEX_ prefix with __ separators:
# CORTEX_GATEWAY__LISTEN=0.0.0.0:31313
# Path to the model catalogue (limits, cost, pinning, aliases, feasibility).
# Defaults to the packaged location below; uncomment to override for a
# non-packaged / local run.
# models_config = "/etc/cortex/models.toml"
[gateway]
listen = "0.0.0.0:31313"
metrics_listen = "0.0.0.0:31314"
[eviction]
strategy = "lru"
# Restart neurons after this many load/unload cycles to defragment VRAM.
# Set to 0 to disable.
defrag_after_cycles = 50
# -- Nodes ---------------------------------------------------------------
# Each [[nodes]] entry declares a neuron daemon in the fleet.
# Models are discovered by polling the neuron's /models endpoint.
# Pinned models (see models.toml) are never evicted.
[[nodes]]
name = "gpu-large"
endpoint = "http://gpu-large.internal:8080"
vram_mb = 49152 # e.g. 2x RTX 4090 (48 GB combined)
pinned = [
"your-org/large-model",
]
[[nodes]]
name = "gpu-medium"
endpoint = "http://gpu-medium.internal:8080"
vram_mb = 24576 # e.g. RTX 4090 (24 GB)
pinned = [
"your-org/medium-model",
]
[[nodes]]
name = "gpu-small"
endpoint = "http://gpu-small.internal:8080"
vram_mb = 12288 # e.g. RTX 3060 (12 GB)
pinned = [
"your-org/embedding-model",
]
# -- Entitlements (multi-tenant governance, #47) -------------------------
# Identity + per-key token budgets. Omit this section entirely for the
# legacy single-operator behaviour: requests are anonymous and uncapped.
#
# The local/static provider below is the source of truth for accounts,
# keys, and hard caps until the upstream clearing house exists. Identity
# rides standard bearer auth only — clients send
# Authorization: Bearer <key>
# no custom headers or body fields.
[entitlements]
# Reject unauthenticated requests with 401 invalid_api_key. Leave false
# (allow-anonymous) during rollout; flip to true once keys are issued.
require_auth = false
# One entry per API key.
[[entitlements.keys]]
key = "sk-example-rolling" # the bearer token the client sends
account_id = "team-research" # billable account (keys may share one)
key_id = "research-ci" # stable label for ledger/metrics (optional)
hard_cap = 5_000_000 # hard token cap over the window
# Rolling window that resets — over-cap requests get 429 rate_limit_exceeded
# + Retry-After, so well-behaved clients (opencode/AI SDK) back off and retry.
window = { kind = "rolling", seconds = 3600 }
[[entitlements.keys]]
key = "sk-example-balance"
account_id = "team-research"
key_id = "research-prepaid"
hard_cap = 20_000_000
# Hard balance, no reset — exhaustion returns 429 insufficient_quota
# (the client surfaces and stops). This is the default when `window` is
# omitted. Never 402.
window = { kind = "balance" }
[[entitlements.keys]]
key = "sk-example-infra"
account_id = "operator"
key_id = "infra"
# No hard_cap → uncapped operator infra key (own fleet, own use). Still
# metered for visibility.