Commit Graph
100 Commits
Author SHA1 Message Date
inference-bot 3ee385de12 Encrypt member chat keys at rest (AES-256-GCM)
Member feedback: keys stored in plaintext in the portal DB are a
key-compromise incident waiting to happen. Members.key_token (the chat
key) is now encrypted with AES-256-GCM using a KEK from the PORTAL_KEK
env var. Plaintext exists only in memory when a key is served (dashboard,
LibreChat key feed, LiteLLM management). Startup migration encrypts
existing rows idempotently; fail-closed when the KEK is absent.
Member-managed API keys were already reveal-once (hash-only at rest).
2026-10-28 16:48:58 -06:00
inference-bot ac3d79a1df manifest: pin dockerImage to 20261028-a0d15e3 (sweep legacy-token cleanup) 2026-10-28 13:54:04 +00:00
inference-bot a0d15e30d8 sweep: drop the legacy WEBHOOK_TOKEN fallback (cutover is complete)
ADMIN_TOKEN_LEGACY is "0" and verified: ADMIN_TOKEN returns 200 on /admin/*,
WEBHOOK_TOKEN returns 403. The fallback in sweep.sh would therefore turn
"ADMIN_TOKEN unset" into a confusing mid-run 403 rather than the clear abort
that already exists three lines below it. Same removal in the digest script.

Not a behaviour change in normal operation — ADMIN_TOKEN is set (len 64) and
already preferred — but it removes a foot-gun that could mask a broken
configuration, and leaves the code honest about which credential is live.
2026-10-28 13:53:25 +00:00
inference-bot e773c5097f manifest: pin dockerImage to 20261028-0ae1404 (carbon unit fix) 2026-10-28 04:35:05 +00:00
inference-bot 0ae1404ab5 metrics: fix carbon unit conversion (Wh -> kWh is /1e3, not /1e6)
The carbon column was 1000x too low: 2.9 gCO2e for 55 days and 14.35 kWh,
where 14.35 kWh at the factor table's own intensities must be ~2.9 kg. Energy
was right, so nothing looked broken unless you did the arithmetic — 938 Wh in a
day cannot be 0.2 g of CO2.

Adds a tripwire in impact_for(): if the implied grid intensity falls outside
5-500 gCO2e/kWh, it prints a warning rather than quietly returning numbers. The
factors themselves span 24-250, so a blend outside that band means a unit or
factor error, not a cleaner grid.
2026-10-28 04:34:50 +00:00
inference-bot 997d047323 manifest: pin dockerImage to 20261028-8e9906d (impact column fixes) 2026-10-28 04:34:06 +00:00
inference-bot 8e9906d844 metrics: fix the impact columns — group by model_group, never write a zero
Two bugs from the first deploy, both caught by reading the numbers back:

1. Tokens were summed per raw `model`, which carries the UPSTREAM name
   (openai/deepseek-v4.1-flash), while the factor table is keyed on our
   provider aliases (greenpt/deepseek-v4.1-flash). Every model therefore
   matched nothing. Now grouped on model_group, the same convention
   aggregate_model_usage uses, with model as the fallback for old rows.

2. Every day was written as energy_wh = 0.0 / co2e_g = 0.0 — a false claim that
   the co-op used no energy, and precisely the fail-open this module is written
   to avoid. A day with no measured tokens now yields NULL for all three impact
   columns, and impact_for() refuses to compute from an empty total.

The backfill is idempotent, so re-running it overwrote the zero rows.
2026-10-28 04:33:50 +00:00
inference-bot 89593cfa74 manifest: pin dockerImage to 20261028-af610fe (energy and carbon metrics) 2026-10-28 04:32:20 +00:00
inference-bot af610fe25f metrics: energy and carbon per day, from measured GreenPT factors
Implements the approach Nathan settled on in co-op/support#26: per-token
estimates based on what GreenPT publishes, rather than waiting on the upstream
LiteLLM `impact` passthrough.

- New daily columns: out_tokens, energy_wh, co2e_g.
- Tokens come from OUR OWN spend logs (completion_tokens per model per day), so
  member-visible impact does not depend on any client persisting an `impact`
  object. GreenPT's metric counts OUTPUT tokens only — that is the unit — so
  input tokens are excluded deliberately.
- Factors live in one table with a source per row. The greenpt/* rows are
  MEASURED: fitted from live calls to their API across prompt sizes
  (energy = overhead + marginal x output tokens, R2 >= 0.999; carbon from the
  grid intensity their own emissions imply — ug/mWh and g/kWh are the same
  ratio). Everything else falls back to a documented order-of-magnitude proxy,
  so a day's total is never silently missing models.
- Migration: CREATE IF NOT EXISTS never adds columns, so both the script and
  the app ALTER TABLE on connect.

Refining a factor is a data edit in IMPACT_FACTORS, not a code change.
2026-10-28 04:32:05 +00:00
inference-bot 5a9b0d2b0d manifest: pin dockerImage to 20261028-27d30a1 (broker metrics route fix) 2026-10-28 04:17:18 +00:00
inference-bot 27d30a1af2 portal: fix the broker metrics route (decorator landed on the wrong handler)
My earlier patch inserted the new handler between the existing
/broker/model-leaderboard decorator and its function, leaving two routes
pointing at model-leaderboard and none at /broker/metrics, which 404'd. The
dashboard's co-op leaderboard was being served the wrong payload.

Caught by the post-deploy check (the 404 on a route whose code was present in
the container), not by the build. Adds a pre-build route-table assertion —
every path must be followed by its intended handler — so this shape of mistake
fails before a build rather than after a deploy.
2026-10-28 04:17:03 +00:00
inference-bot acd01afc99 manifest: pin dockerImage to 20261028-4ffadd2 (broker metrics route) 2026-10-28 04:15:19 +00:00
inference-bot 4ffadd223c metrics: share the series with members through the broker
Members asked to see how the co-op is doing, not just their own usage. The
series was admin-only; now the same query serves both surfaces.

- metrics_series(days) holds the query once; /admin/metrics/series and the new
  /broker/metrics both call it (no second copy to drift).
- /broker/metrics is broker-authenticated and returns AGGREGATE, NON-PERSONAL
  figures only: co-op size, requests served, spend. Same class as
  /broker/model-leaderboard, and the same rule — no per-member figure passes
  through the broker.
2026-10-28 04:15:03 +00:00
inference-bot 6ab0554d00 manifest: pin dockerImage to 20261028-1cf78c4 (membership history fix) 2026-10-28 03:56:20 +00:00
inference-bot 1cf78c4e56 metrics: membership history from list-join dates, not welcome_sent_at
The backfill's first real curve was wrong: built on welcome_sent_at it showed
34 members on 19 October, when the co-op had ~65. Most members were provisioned
before the portal sent welcome emails, so that field lags by dozens of people —
and a chart built on it would have misrepresented the co-op's growth.

Membership now comes from Listmonk's per-subscription subscription_created_at
on the members list, which the portal's nightly reconcile keeps mirroring
active members. That reproduces the real curve (13 members mid-September → 71
today). Past days are the cumulative joins of today's members — an honest floor,
documented as such, since members who left are absent.
2026-10-28 03:56:06 +00:00
inference-bot c5d5fa2fac manifest: pin dockerImage to 20261028-27c32fc (listmonk count fix) 2026-10-28 03:55:03 +00:00
inference-bot 27c32fc75f metrics: count LIST membership status, not the subscriber's own status
The first live run recorded newsletter_subs = 0 — a wrong zero, the exact
fail-open this module documents against. Cause: Listmonk's subscriber record
carries status enabled/blocked, while the LIST membership carries
subscription_status confirmed/unconfirmed. Filtering on the subscriber's own
status matched nothing and returned a silent 0.

Now counts memberships whose subscription_status is confirmed (verified: 71,
matching the list), and returns NULL when the endpoint yields no rows at all —
"not measured" rather than "nobody is subscribed". The daily job is the
regression test: it must record 71, not 0.
2026-10-28 03:54:42 +00:00
inference-bot 80de75672c manifest: pin dockerImage to 20261028-70ae435 (daily metrics snapshot) 2026-10-28 03:53:39 +00:00
inference-bot 70ae435867 metrics: daily snapshot of members and usage, backfillable, plus a read route
The co-op had no longitudinal data: every number was an ad-hoc query, so
nothing compounded and no single source owned "how many members do we have,
and when did that change". That cost us a wrong figure in the newsletter ("six
new members" — actually 21; the six were forum joins).

New: metrics_snapshot.py writes one row per day into the portal's SQLite store
(the same members.db), and GET /admin/metrics/series returns them oldest-first
for the panel and dashboard to chart later.

  - Cumulative columns (members_active, members_total, members_new,
    newsletter_subs, matrix_space_members) are counts AS OF that day;
    spend_usd/requests are THAT day's traffic.
  - FAIL CLOSED PER METRIC: an unreadable source yields NULL, never a zero — a
    zero reads as "nobody" and would bake a lie into a chart. Failures print to
    stdout, and a scheduler task's stdout lands in the app log.
  - Listmonk's cached subscriber_count is NOT trusted (it reads 142 for a list
    holding 71): the count comes from paging the subscribers endpoint.
  - Member growth comes from welcome_sent_at, the provisioning timestamp —
    the closest thing the portal stores to a join date. Manual/founder members
    have no welcome email, so backfilled days are a documented LOWER BOUND;
    today's row carries the exact count.
  - Runs as an in-app scheduler task (addons.scheduler, 03:15 UTC) rather than
    an agent cron: member-facing plumbing should not depend on the agent VM.
  - Backfill is idempotent; member growth is reconstructible back to
    September and spend/requests back to the gateway's first request.

Charts land later, on the surfaces, once the shape of the series is reviewed.
2026-10-28 03:52:49 +00:00
inference-bot 403a773f53 manifest: pin dockerImage to 20261027-452a27a (member-set guard + access-log redaction) 2026-10-27 05:04:44 +00:00
inference-bot 452a27a778 portal: guard against an implausible member list; redact the webhook token from logs
Two fixes, both filed rather than deferred.

1. member-portal#2 — the sweep deactivates every member whose email is missing
   from the Open Collective member list, and the existing fail-safe only covers a
   fetch that RAISES. A fetch that SUCCEEDS while returning nothing or far less
   than usual is unguarded, and is what a narrowed token scope, an expired
   credential with partial access, or a renamed Membership tier produces (the
   fetch filters on the tier's name). That path deactivates the membership
   instead of failing. fetch_members() now checks the result against the local
   active count before any caller can act on it: empty, or at or below half of
   local, raises — so the sweep's fail-safe takes over and members stay active.
   Skipped when we have no active members of our own (fresh install).

   Correction to the issue as filed: listmonk_reconcile reads the local members
   table, not this fetch, so the newsletter path was never exposed to it. The
   sweep is the only destructive consumer.

2. ops/security#2 — OC's generic webhooks cannot send headers, so the webhook
   secret must travel in the URL path, and uvicorn wrote it into the access log
   on every delivery. A logging filter on uvicorn.access and on the root handler
   replaces the token with <redacted>. The token cannot be kept out of the URL;
   it can be kept out of the retention.

Both pieces are unit-tested in isolation before shipping: the guard's decision
function over eight cases, and the filter against a real access-log record, an
unrelated path (no over-redaction), and a missing dependency (must not break
logging).
2026-10-27 05:04:05 +00:00
inference-bot c2369034ee manifest: pin dockerImage to 20261027-5720a63 (security fixes: admin token split, credit-pack order verification, revoke on lapse) 2026-10-27 03:34:29 +00:00
inference-bot 5720a63958 security: separate admin token, verify credit-pack orders, revoke access on lapse
Member disclosure, three findings (fixes 1-3 of 5):

1. WEBHOOK_TOKEN was both the Open Collective webhook URL token and the
   X-Admin-Token for every /admin/* route, so anything that learned the webhook
   URL (OC admins, OC logs, this app's access logs) gained portal admin. The URL
   token is forced by OC's generic webhooks, which cannot set headers — the reuse
   is not. Introduce ADMIN_TOKEN, header-only. ADMIN_TOKEN_LEGACY=1 (default)
   still accepts WEBHOOK_TOKEN and logs a warning, so the admin panel and the
   scheduler keep working; set ADMIN_TOKEN everywhere, then flip it to 0 and
   rotate. sweep.sh and the digest script now send ADMIN_TOKEN.

2. The credit-pack path fell back to the webhook payload's own amount when OC
   could not resolve the order, so a forged POST naming its own totalAmount
   minted credits for any slug. The payload is now a hint only: an unresolvable
   order is refused, logged loudly, and emailed to the operator for manual
   reconciliation. Transient OC failures hold instead of granting blind.

3. Lapsing did not revoke access: both deactivation sites (lapse webhook, nightly
   sweep) discarded the key token deactivate_member() returns. Member keys carry
   no max_budget of their own, so they inherit the team's — zero the team budget
   on deactivation, which blocks every key under it. Key deletion is deliberately
   NOT used: provisioning reuses the stored sk- token on reactivation, and
   get_or_create_team does not update an existing team's budget, so the two
   renewal paths now re-sync the budget explicitly. Failures are loud (log +
   alert email); deactivation still completes.
2026-10-27 03:31:40 +00:00
inference-bot 178a4aa360 loomio: sanitize usernames instead of falling back to another invalid one
The username fix rejected a dot-containing OAuth uid and then substituted the
email local part, which carries the same dots — so Loomio answered 422
("Usernames must contain lower case letters, numbers, underscores, and internal
hyphens only") on every sweep, for the one member whose Cloudron username has a
dot. Normalize instead: disallowed characters become underscores, and leading
and trailing hyphens/underscores are stripped. Also log the Loomio user id
rather than the member's email.
2026-10-26 16:07:23 +00:00
inference-bot e24c272e2e org-link fix: INSERT bound 4 values to 3 placeholders (500 on every link); slug = org slug 2026-10-21 21:16:59 -06:00
inference-bot d8d80d9594 newsletter reconcile: Members list mirrors ACTIVE members
Root cause of test accounts receiving the newsletter: LISTMONK_API_USER was
set EMPTY in the portal env - env-present beats the code default, so every
listmonk_sync authenticated with an empty user and 403'd silently. Fixed both
the env (set to portal-sync) and the code (empty env falls back to default).

New: listmonk_reconcile() - the Members list must mirror ACTIVE members.
Stale subscribers are removed: members marked inactive are taken off the list
(record kept as suppression marker); addresses that are not members at all
(test/deleted accounts) are deleted outright. Wired as step 4 of the nightly
membership sweep + POST /admin/newsletter/reconcile for on-demand runs.
2026-10-21 16:32:36 -06:00
inference-bot e4da4da2c6 pagination fix: OC MemberCollection has NO cursor pageInfo — use offset+totalCount pagination (verified against the live API); the cursor query 400'd, taking the sweep and org endpoints down 2026-10-20 23:30:32 -06:00
inference-bot 42c664c076 OC member queries: cursor pagination via fetch_all_member_nodes() — members(limit:100) silently hid everyone past member #100 (same class as the Cloudron 25-per-page trap). Helper RAISES on any page failure so callers' fail-safes apply (never a partial list — the sweep would deactivate everyone missing from one); fetch_members returns [] on failure (sweep skips), fetch_org_backers propagates (sweep keeps linked accounts active), webhook payer lookup and /admin/org-link wrapped. 2026-10-20 23:26:18 -06:00
inference-bot fed4a7005e audit fixes: two latent NameErrors in the org code — webhook org branch referenced undefined org_name (crashed on an unlinked org's first payment), org-links GET referenced undefined name (crashed when a linked org's dues show inactive) 2026-10-20 23:21:51 -06:00
inference-bot d87cbf9650 admin overview: expose matrix_mxid (dashboard-set external handle) per member 2026-10-20 23:01:24 -06:00
inference-bot dcffbb1b98 org-links GET: remaining name-column reference 2026-10-20 22:57:40 -06:00
inference-bot 394cccfbcb org links: members table has no name column — drop it from org SQL/unpackings (names come from OC/Cloudron) 2026-10-20 22:55:55 -06:00
inference-bot 72614f8f19 Organizational memberships: OC org contributors (type ORGANIZATION) never expose an email, so all three automated paths missed them (webhook email lookup, nightly sweep, /join). Add: members.org_slug link column + fetch_org_backers() (org BACKER/Membership nodes); sweep keeps linked accounts active while the org's dues are active and deactivates on lapse (fail-safe keeps active if the org fetch errors); webhook re-activates linked accounts on org dues and emails the operator for unlinked orgs; POST /admin/org-link (link + full provision incl. Cloudron signup link) and GET /admin/org-links (live dues status); admin overview gains an 'org' field. 2026-10-20 22:45:37 -06:00
inference-bot 5425efee1f overview: normalize float-residue spend (e.g. -5.55e-17 after cycle reset) to 0 so the panel never renders -0.00 2026-10-19 11:42:58 -06:00
inference-bot fd13bc6ea2 broker/matrix-mxid GET: local_mxid_for_email was async-def but never awaited — response contained a coroutine and FastAPI 500'd ('coroutine object is not iterable'), so the dashboard silently showed no registered Matrix ID. Make it a plain sync def (blocking urllib). 2026-10-19 10:47:49 -06:00
inference-bot 97ebe69553 portal: /admin/librechat/keys — dedicated-token feed of member chat keys for the sandbox's key auto-load 2026-10-18 23:15:38 -06:00
inference-bot 7f04920327 broker: retire the old LiteLLM key before re-creating a same-name key
LiteLLM requires unique key aliases, so re-creating a key with an existing
(email, name) pair 400s ('alias already exists') — breaking key rotation
from the dashboard. The stored LiteLLM hash lets us retire the old key
first; the ON CONFLICT upsert then tracks the new key.

Surfaced by the LibreChat sandbox work: the portal chat-key rotation for
an SSO member failed with exactly this 400.
2026-10-17 18:00:49 -06:00
Inferencebot 003689868f Remove PublicAI; Regolo becomes the Apertus provider; emoji = feature tier (🌱 green for greenpt+regolo), qwen-image via image feature only 2026-10-16 10:48:10 -06:00
Inferencebot ced82b09d0 portal: filter image-gen models from chat picker by name (gateway strips mode from /v1/models) 2026-10-16 10:26:04 -06:00
Inferencebot 5091c9e644 portal: add Regolo models to picker (Apertus 70B, Qwen 3.5 122B, Qwen Image); exclude image-gen from chat list 2026-10-16 10:24:27 -06:00
Inferencebot 106b563748 sync: invite members to MATRIX_EXTRA_ROOMS too (restricted rooms aren't federation-discoverable until joined) 2026-10-14 16:40:35 -06:00
Inferencebot 24f0c0f745 fix: import urllib.parse on the retire path (NameError broke the sweep) 2026-10-14 10:55:30 -06:00
Inferencebot b2689d9cd1 Matrix sync: revoke stale local-account invites too (directory hygiene + one-identity completeness) 2026-10-14 10:54:24 -06:00
Inferencebot b342ca0ee3 README: document membership sweep + rag refresh scheduled tasks 2026-10-12 19:33:29 -06:00
Inferencebot 49638ba36a rag_refresh: retry attach up to 3x (transient 400 while file processes) 2026-10-12 19:29:17 -06:00
Inferencebot 49376813ae rag_refresh: parse OWUI files-list {items:[...]} shape; unfiltered list + client-side name filter 2026-10-12 19:26:06 -06:00
Inferencebot bca11a1657 Move docs-RAG refresh into the portal container (Gitea raw API + scheduler addon, 3:30am daily) 2026-10-12 19:23:00 -06:00
Inferencebot cb04958460 scheduler addon belongs under addons (manifest schema), not top level 2026-10-12 18:23:50 -06:00
Inferencebot b58929076e Restore nightly membership sweep scheduler (3am daily) — was silently missing from manifest 2026-10-12 18:21:27 -06:00
Inferencebot 81b640ba74 matrix-mxid: return invite_sent so the dashboard can confirm the invite is on its way 2026-10-12 09:01:22 -06:00
Inferencebot e56d0456bb Chat models: silent auto-RAG via legacy FC (no knowledge tool, no narration) 2026-10-10 13:41:20 -06:00
Inferencebot 92e7ddec05 Matrix sync: retire local @username:inference.coop accounts when a member sets a dashboard MXID (one identity per member) 2026-10-10 13:29:26 -06:00
Inferencebot 05feed5f24 Strengthen docs-KB steering: firm negative instruction after soft hint proved insufficient 2026-10-09 22:44:00 -06:00
Inferencebot 2eab2abb0d Overview: separate admin/operator accounts into their own category (excluded from member counts) 2026-10-08 15:08:21 -06:00
Inferencebot a7a283d552 Steer docs-KB tool usage: params.system hint to query only co-op-relevant questions 2026-10-08 09:38:05 -06:00
Inferencebot 32420f60f4 Friendly names: tinfoil/glm-5.3 (replaces deprecated flash variant) 2026-10-07 22:07:44 -06:00
Inferencebot 1a227642d9 Attach co-op docs knowledge collection to every model (auto-RAG, no KB browsing wandering) 2026-10-07 10:52:08 -06:00
Inferencebot e18cd4b703 Provisioning: add new members to Open WebUI docs-RAG access group at signup 2026-10-07 10:38:32 -06:00
Inferencebot 2a5277007e Model list: also filter embedding models out of the chat picker 2026-10-07 09:44:15 -06:00
Inferencebot 3d258e9a2d Loomio sync: only invite activated members (avoid invite email before account setup) 2026-10-07 09:19:24 -06:00
inference-bot 38005127cb Expose credits_remaining (purchased minus cycle overspend) in broker usage + admin overview 2026-10-03 13:59:29 -06:00
inference-bot 052a109040 Fix credit model: allowance is a floor, credits add on top (max_budget = allowance + credits, not reduced by cycle overspend) 2026-10-03 13:06:02 -06:00
inference-bot 563881d63c Matrix: member-set extra MXID (matrix_mxid column + broker get/set + sync prefers it) 2026-10-03 10:48:13 -06:00
inference-bot 3cdc250d52 Chat model picker: value-tier emoji + friendly names in /v1/models (🔒 private / 🌱 green / 📖 public), id stays canonical 2026-10-02 22:30:11 -06:00
inference-bot 1fe0e47802 Update code-comment example (green-r -> glm-5.3-flash) 2026-10-02 22:04:54 -06:00
inference-bot 29823e87d5 Matrix mxid lookup: paginate Cloudron users API (25/page) — was missing page 2+ 2026-10-02 13:03:14 -06:00
inference-bot f86ead8653 Matrix invite: honor 429 retry_after_ms (3 attempts) 2026-10-02 12:29:41 -06:00
inference-bot 07a1f2ad4e Fix Cloudron users API response shape ({users: [...]}) 2026-10-02 12:28:39 -06:00
inference-bot 82ada8c95b Matrix: invite all active members to co-op Space + General room on every sync (SSO usernames via Cloudron lookup; MATRIX_EXTRA_MXIDS for federated accounts) 2026-10-02 12:27:45 -06:00
inference-bot cddd9b6e3c Loomio username fix: allow hyphens in uids (was falling through to email local-part for inference-admin/bot) 2026-10-02 08:41:03 -06:00
inference-bot ef253d01a0 Loomio: fix auto-generated usernames after sync (b3 server API; prefer Cloudron uid, fall back to email local-part) 2026-10-02 08:36:10 -06:00
inference-bot 070c6525cd Broker usage: expose all_time_spend (member's lifetime total from SpendLogs) in both DB and HTTP paths 2026-10-01 16:07:10 -06:00
inference-bot 7af02f6466 Model aggregation: order by requests (usage) not spend — matches leaderboard bars and member-facing intent 2026-10-01 15:56:53 -06:00
inference-bot 19c04f37fa Fix days-filter in aggregate_model_usage: COALESCE(startTime,'') is invalid SQL (empty-string not a timestamp) — windowed queries always failed to empty 2026-10-01 15:54:58 -06:00
inference-bot 69beb9c002 Broker: co-op-wide model leaderboard endpoint for member dashboard (aggregate, anonymized) 2026-10-01 15:35:57 -06:00
inference-bot 778a7f8fd2 Rate-limit the key-injector proxy path (120/min) — only unthrottled sensitive route left 2026-10-01 09:51:57 -06:00
inference-bot 6f0c728513 Overview: expose all_time_spend per member + total_all_time_spend (SpendLogs aggregate) 2026-09-30 22:22:17 -06:00
inference-bot e00da486b5 Fix broker 403 for members beyond Cloudron's default 25-user page: paginate all /users reads (per_page=100) 2026-09-30 07:46:03 -06:00
inference-bot 64d3c6aa83 Broker: resolve uid- identities (OIDC sub) to email — email-invited users have no username 2026-09-29 21:53:46 -06:00
inference-bot b93b208659 Broker usage: expose allowance and credits buckets to dashboard 2026-09-27 22:39:54 -06:00
inference-bot b7d239d9b6 Reset dates follow real billing: webhook re-pins budget_reset_at on every Membership payment (+30d); DB write (API drops the field) 2026-09-27 22:38:05 -06:00
inference-bot 9b1d9edde4 Variable-amount credit pack: grant tip-free contribution amount via public OC order API 2026-09-27 22:17:48 -06:00
inference-bot 315247e5db Tier resolution via OC tiers API (TierId->face value+name, cached); credits at face value, not total-with-tip; remove debug payload log 2026-09-27 22:14:56 -06:00
inference-bot f163a5b375 Fix tier detection: OC webhook carries TierId only; parse tier name from order description 2026-09-27 22:04:35 -06:00
inference-bot a85369341c TEMP: log full OC webhook payload to diagnose tier field shape 2026-09-27 21:58:25 -06:00
inference-bot 8a320bfed7 Held credit packs: record non-member purchases, notify buyer, auto-grant on join, admin endpoints 2026-09-27 21:54:34 -06:00
inference-bot df740db283 Expose manual-member flag in admin overview (uses existing MANUAL_MEMBERS set) 2026-09-27 21:18:43 -06:00
inference-bot 5fa2449037 Membership tied to Membership tier: sweep+webhook filter, credit packs HELD for non-members, MANUAL_MEMBERS for legacy 2026-09-27 21:17:03 -06:00
inference-bot 63acea289f Non-expiring credits: two-bucket budget model, ledger, OC credit-pack webhook, admin endpoints 2026-09-27 19:10:50 -06:00
inference-bot 0c3c12999c Remove tracked bytecode; ignore __pycache__ 2026-09-27 11:15:02 -06:00
inference-bot 54fa4d3fc2 Filter audio models out of chat-facing /v1/models (visible to API users only) 2026-09-27 11:00:53 -06:00
inference-bot c7ed245939 Read usage aggregations from LiteLLM Postgres directly (HTTP fallback kept) 2026-09-26 16:39:23 -06:00
inference-bot 4600a69cac Fix dashboard 500s: raise httpx read timeout on /team/list + /spend/logs calls 2026-09-26 16:21:11 -06:00
inference-bot 2ff8193c55 Dashboard usage: prefer model_group alias (provider/model names) — fixes support #5 2026-09-26 16:14:33 -06:00
inference-bot 5c269bf243 Support ?days=N filter on /admin/model-usage 2026-09-26 16:09:38 -06:00
inference-bot a48f11c654 Prefer model_group alias in model-usage aggregation 2026-09-26 15:33:24 -06:00
inference-bot 810b878014 Add /admin/model-usage endpoint (co-op per-model spend/tokens/requests) 2026-09-26 15:31:07 -06:00
inference-bot e92e29bac0 Commit key-purge migration DML (backfill fingerprint, purge plaintext idempotently) 2026-09-24 08:02:39 -06:00
inference-bot c213a489e9 API keys revealed once: store fingerprint (last-4) + hash, purge plaintext 2026-09-24 08:00:02 -06:00
inference-bot 6564508e1d Remove per-member model allowlist: members access all gateway models (empty models = all allowed) 2026-09-23 20:10:23 -06:00