Add 12h price/visibility change delay for public community endpoints (#13800) - #1
aikhusus2025-ctrl wants to merge 123 commits into
Conversation
…linations#13531) * Round balances using POLLEN_BILLING_PRECISION * Use POLLEN_BILLING_PRECISION for balance updates Update balance calculation to use rounding with defined precision. * Add ledger precision tests for quest rewards This test verifies that quest rewards and usage charges maintain 8-decimal precision in the ledger. * Refactor update statement for bucket balance * fix(billing): round the two Stripe credit paths too The PR rounds every deduction and reward claim to ledger precision but leaves both pack-purchase credits unrounded, so any checkout or auto top-up reintroduces the float drift it just removed. - stripe-webhooks.ts: checkout session credit - stripe-billing.ts: auto top-up invoice credit Forward-only, like the rest of the PR — existing dirty balances such as 10.316053619999998 are not backfilled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkpGbmYzzesyaGNxewSCyy --------- Co-authored-by: voodoohop <t.haferlach@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: voodoohop <thomash@pollinations.ai>
* feat: add account settings
* fix: preserve deleted account reward records
* fix: hard-delete accounts without resetting quests
* fix: secure account deletion and quest history
* fix: align account deletion legal notice
* fix: refine signed-out navigation
* fix: close mobile drawer from account menu
* refactor: dedup quest rewards on the GitHub id in the key alone
- Adds shared rewardKey(); quest checks, admin grants, and coupon redemption
now build one key shape instead of three hand-rolled variants
- Drops rewards.github_id — the key already carries the identity; migration
0050 re-keys quest:{id}:user:{userId} rows to quest:{id}:github:{githubId}
- Makes user.github_id unique so one GitHub identity cannot hold two accounts
- Fixes grants and coupons still keying on the account id, which let a user
delete, re-signup, and redeem the same coupon twice
- Collapses perSubject into perUser (byte-identical key) and drops the
Connected accounts panel — GitHub is the only sign-in
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: keep migration 0050 as drizzle-kit output plus the backfill
- Restores drizzle-kit's own statement order; the only hand-written part is
the INSERT, which re-keys per-person rewards (drizzle generates a plain
column copy and never emits data migrations)
- Qualifies the select as `r`.`id`, ambiguous once the user join is added
- drizzle-kit generate reports no schema changes: snapshot and journal match
shared/db/better-auth.ts
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: split the reward re-key into a drizzle custom migration
- 0050 is `drizzle-kit generate --custom`: one UPDATE that re-keys per-person
rewards from the account id to the GitHub id
- 0051 is `drizzle-kit generate` verbatim, no hand edits — it drops
rewards.github_id and adds the unique index on user.github_id
- Re-keying first means the old unique index on idempotency_key is still in
place, so a collision aborts before anything is dropped or rebuilt
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: trim redundant markup from the account page
- Drops the "profile details come from GitHub" footnote, which restates the
@username link rendered above it
- Drops the class-less <div> wrapping each Section's children; Section framed
already lays children out in a flex column with gap-5, so the inner mt-4/mt-5
spacing goes with it
- Uses user.image directly instead of an `|| ""` local
- Drops text-[10px] from the sign-in consent line; size="micro" is already 10px
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: key quest rewards on the GitHub user id
- Adds shared rewardKey(); quest checks and admin grants build one key shape
instead of hand-rolled variants
- Drops rewards.github_id — the key already carries the identity. 0050
(drizzle --custom) re-keys quest:{id}:user:{userId} rows to
quest:{id}:github:{githubId}; 0051 (drizzle generate, verbatim) drops the
column and makes user_id nullable so rewards outlive the account
- Makes user.github_id unique — one GitHub identity cannot hold two accounts
- Collapses the perSubject quest scope into perUser; it emitted a
byte-identical key
- Quest status resolves rewards left by a previous account with the same
identity, and the leaderboard skips rewards whose account is gone
Verified against production data: 19,242 rewards, 18,889 re-keyed, 0 duplicate
GitHub ids, 0 accounts without one, 0 key collisions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: drop package-lock churn
- Reverts the reordering npm install produced in a fresh worktree; no
dependency change
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: drop the orphan-reward lookup from quest status
- Reverts account.ts to the plain userId filter, matching /quests/rewards
- A re-registered account now sees the quest as open; the unique key still
swallows the insert, so it cannot be earned twice
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test: assert quest status stays open for a replacement account
- Follows the pollinations#13564 revert: quest status is scoped to the account's own
rewards again
- Re-earning is still a no-op, asserted by the recordRewards check above
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: show retained quests as already earned
* refactor: reuse claimed UI for retained quests
* revert: drop the retained-quest display
A user who deletes their account, re-registers, and then goes looking for
quests is not worth code in three files. The unique reward key from pollinations#13564
already stops them re-earning anything; all this added was a nicer label on
the card they can no longer complete.
- Reverts the widened reward queries in /account/quests and /quests/rewards
to the plain userId filter
- Drops previouslyEarnedQuestIds from the API and the frontend state that
threaded it
- The replacement account sees the quest as open; earning it again is a
silent no-op, as asserted by the recordRewards check in the test
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: voodoohop <t.haferlach@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Record how the MCP Cloudflare Worker at mcp.pollinations.ai deploys: - deploy-applications.yml triggers on production branch pushes - discover.cjs matches apps/mcp via deploy.json watch globs - Path filters cover both apps/** and packages/mcp/** - Production gating via guard-production-source.yml - No staging MCP deploy (thin proxy; staging at gateway level) Fixes pollinations#13408 Co-authored-by: Karan Ray <karanray06@users.noreply.github.com> Co-authored-by: voodoohop <thomash@pollinations.ai>
…linations#13418) Co-authored-by: voodoohop <thomash@pollinations.ai>
…ions#13588) A 402 means the caller had no Pollen left: the request is rejected at the balance check, before any provider runs. It is billed nothing and generates nothing, but it carries a user_id, so it counted as an active user and as a request. Last week that was 3.1M requests from 3,507 users. The effect was not cosmetic. Reported WAU has been flat for ten weeks — 6,798 to 6,859 — while users who actually got a response fell 5,716 to 3,352. The dashboard showed a stable user base through a 41% decline. - WAU, total requests, text/image requests, community user share and BYOP user share now count served users only. - WAU and BYOP user % cycle to the raw figure, so nothing is lost. - New "Turned away" row: users whose every request hit the Pollen wall. - Per-user metrics (Tokens/user, ARPA, Purchase rate) divide by served WAU, which roughly doubles Tokens/user. - Fixes text_requests and image_requests, which matched event_type 'text' and 'image' where the values are 'generate.text' and 'generate.image', and so had always returned 0. Pipe changes are additive; the only consumer is this dashboard.
* fix: keep tracking alive after malformed SSE chunks * fix: simplify malformed SSE tracking --------- Co-authored-by: voodoohop <t.haferlach@gmail.com> Co-authored-by: voodoohop <thomash@pollinations.ai>
…ations#13523) * fix(fallback): hide private-target oracles and leaky metadata Keep fallbackTarget non-enumerable so cached JSON never ships it. Collapse other-owner private/deactivated fallback 400s into the same "does not exist" as a missing row. Do not fail over an edits request onto a generations-only target. Fixes pollinations#12890 * fix(enter): type fallback inputModalities as ModelInputModality CI typecheck failed: readonly string[] is not assignable to the normalizeCommunityEndpointInputModalities parameter. --------- Co-authored-by: voodoohop <thomash@pollinations.ai>
…ollinations#13522) * fix(gen): prefer defined flat-rate prices in preflight Tinybird avg_cost_usd was blocking community image models whose defined completionImagePrice is a few cents. Use the model definition for flat-rate / per-image models and keep Tinybird only for token-priced ones. Fixes pollinations#12747 * fix(stats): median Tinybird cost and drop poisoned preflight averages The 33.34 estimate was the 7-day mean of rows that billed completionImagePrice * OpenAI output_tokens (~0.01 * 3334). Request-priced community images now log 1 unit, but the mean stays wrong until those rows age out, and token-priced models have the same outlier problem. Use medianIf in public_model_stats, and ignore Tinybird in preflight only when it is missing or >10x the defined per-request price. * style: apply biome wrap and compact array for preflight files CI failed biome check --write on model-stats.ts and byop-markup.test.ts. --------- Co-authored-by: voodoohop <thomash@pollinations.ai>
Co-authored-by: pollinations-ai[bot] <pollinations-ai[bot]@users.noreply.github.com>
Co-authored-by: pollinations-ai[bot] <pollinations-ai[bot]@users.noreply.github.com>
* feat: add Azure GPT transcription models * test: update transcription pricing snapshot
Fixes pollinations#13559 Co-authored-by: pollinations-ai[bot] <247793354+pollinations-ai[bot]@users.noreply.github.com> Co-authored-by: MoronDe <112860827+MoronDe@users.noreply.github.com>
…ns#13527) * fix: keep hidden community models callable * fix: codify community monitor hide policy * refactor: unify community model hidden state * fix: use hidden state in community monitor
…ations#13593) * fix(econ): stop booking community sale price as provider cost Community total_cost equals the sale price, but we never pay an upstream — the 75% owner payout is already in model_paid. Counting both made every community row show margin -0.75 per 1.0 sold. Zero community cost in op_pollen_populate, ignore it in the economics reader (covers historical rows), and stop the community connector from mirroring that meter into op_cloud. Fixes pollinations#12895 * fix(econ): normalize community costs at source * chore(kpi): invalidate Tinybird cache --------- Co-authored-by: voodoohop <t.haferlach@gmail.com> Co-authored-by: voodoohop <thomash@pollinations.ai>
Fixes pollinations#13598 Co-authored-by: pollinations-ai[bot] <247793354+pollinations-ai[bot]@users.noreply.github.com> Co-authored-by: momokula123 <227260546+momokula123@users.noreply.github.com>
Co-authored-by: pollinations-ai[bot] <pollinations-ai[bot]@users.noreply.github.com>
…13637) - Folds deploy-enter/gen/media-cloudflare.yml into deploy-cloudflare-production.yml: one `migrate` job gates three parallel deploy jobs, so ordering is deterministic and the cross-workflow queue gap (2-6 min) collapses to the deploy step. - Previously all three workflows applied the same D1 migrations in arbitrary order; whichever ran first moved the schema while the other workers served old code. - `dorny/paths-filter@v3` pinned with `base: ${{ github.ref }}` - without it the action diffs merge-base(main, production)..production, empty after a promotion, so every filter reports false. - `migrate` gated on at least one service deploying, making "migrations applied, no worker updated" unreachable. - Docs regeneration gated on the run's `deploy-gen` job rather than the aggregate conclusion. - Runbooks updated for the removed workflow names.
…13641) - Hoists gen's Test/Typecheck into a `test-gen` job that gates `migrate`, leaving `deploy-gen` as install + deploy (~30s with the npm cache). - Closes the window where the deployed gen queries a post-migration schema: `loadGenerationModelRegistry` has no try/catch, so a dropped column fails the whole registry including STATIC_ENTRIES, 500ing all image and text traffic. Measured gap was 3m24s; now inside REGISTRY_TTL_MS = 60_000. - A red gen test now blocks the migration rather than trailing it. - `migrate` uses `!cancelled()` so a skipped `test-gen` does not skip every deploy.
…ollinations#13643) Every request that resolves a model goes through getGenerationModelRegistry, and the community-model D1 query it depends on had no error handling. A single failing SELECT there — most realistically schema skew, where a migration lands before the Worker that understands it — turned a community-catalog problem into a total gen outage: /v1/models, /image/models and every generation request 500. The 60s registry cache does not cover this. It is per-isolate, so cold isolates miss it and load the registry immediately; under production traffic new isolates start continuously. - Catch the community query failure and build the registry from static entries - Cache a degraded registry for 5s, not 60s, so recovery is fast - Test asserts static models still resolve when D1 reports a missing column Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…#13623) * feat(listings): one typed listing table — type + payload envelope A community listing now says what it is (`type`) and carries only what that kind has (`payload`). The fields a kind does not have have nowhere to live, so the rules that used to reject them field by field are gone. - `community_endpoint.type` ∈ proxy | prompt_agent | hosted_agent, plus a `payload` JSON column whose shape the type selects. Migration 0052 adds both and backfills every row from the columns they replace. - Runtime union is `kind: "proxy" | "prompt_agent" | "hosted_agent"`; only a proxy has a `bearerTokenCiphertext`, only a prompt agent has an `agentId`. `isDelegatingEndpoint` is `kind !== "proxy"`. - Create is a Zod discriminated union on `type` instead of a superRefine with a four-entry `agentRejections` array. An agent listing sending a price, a credential, a fallback list, or an RPM cap is an unrecognized key, so a later feature cannot forget to reject it. `type` is inferred from `agentId` for callers written before it existed. - gen reads `type` + `payload` and parses per arm, so each branch is narrowed to the shape its type declares. Legacy columns are still written by Enter and the payload is derived from them, so the two cannot drift; a follow-up drops them once gen ships. - Frontend agent listings are identity only — the model-catalog fetch that existed to fill inherited `inputModalities` is gone. Deploy Enter first: it writes both, and gen only starts reading the payload after its own deploy. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(listings): assert the 0052 backfill types rows and packs payloads Runs the real migration SQL against a scratch table, the pattern pollinations#13529 used for its kind backfill, extended to the payload half. - Both agent kinds, the inert image delegation flag, and two proxy shapes - Exact payloads: a hosted agent keeps a target and drops its credential, a prompt agent keeps nothing, a proxy keeps its nine prices in the right slots - Null input_modalities and fallback_model_ids become null and [], not literal nulls the reader would have to re-normalize Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: unify community listings and agents * fix: simplify typed listing updates * fix: address listing envelope review findings * fix(test): drop the agentTable assertion left behind by the envelope cutover The PR removes the `agent as agentTable` import and the agent fixture insert but keeps `expect(await db.select().from(agentTable)).toHaveLength(0)`, so the test dies with `ReferenceError: agentTable is not defined`. 0053 does `DROP TABLE agent`, so the assertion has nothing left to check. Not caught by CI: enter's suite does not run there, and enter/tsconfig.json excludes test/** from typecheck. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: proxy trusted MCP servers through Gen * feat: accept public FFmpeg input URLs * feat: route registered MCP servers through Gen * refactor: settle MCP usage before telemetry * refactor: remove unused FFmpeg lifecycle routes * refactor: keep FFmpeg constants with its Worker * docs: link hosted MCP registry * refactor: use default stateless MCP transport * refactor: route MCP servers from registry * feat: add private media upload entrypoint * feat: add FFmpeg MCP worker * fix: simplify registered MCP tool names * fix: align MCP proxy billing tracking * ci: deploy MCP workers before Gen bindings * refactor: share MCP usage receipts * fix: redeploy FFmpeg on receipt changes * ci: serialize MCP production rollout * refactor(mcp): separate registry from server implementations * fix: show environment-specific MCP endpoints * fix: simplify MCP agent selector * fix: clarify Pollinations MCP description * fix: complete hosted MCP rollout ordering * fix: finalize hosted MCP proxy contracts
Co-authored-by: pollinations-ai[bot] <pollinations-ai[bot]@users.noreply.github.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
PR Summary by QodoDelay public community model price/visibility changes by 12 hours
AI Description
Diagram
High-Level Assessment
Files changed (23)
|
Code Review by Qodo
1. APIDOCS.md edited in PR
|
| **1. Get an API key** at [enter.pollinations.ai](https://enter.pollinations.ai/keys). Use the key type for your environment: | ||
|
|
||
| - `sk_*` — secret key for backend use (full account access) | ||
| - `pk_*` — publishable key, safe to ship in browsers and mobile apps | ||
| - `sk_*` — secret key for backend use. Never ship it in a browser, mobile app, or repository. | ||
| - `pk_*` App Key — public OAuth client id for BYOP. Use it to obtain a scoped user `sk_*`; do not use raw publishable keys for new browser generation integrations. |
There was a problem hiding this comment.
1. apidocs.md edited in pr 📘 Rule violation ⚙ Maintainability
This PR directly edits APIDOCS.md, which is prohibited for feature PRs because it must be generated from OpenAPI sources. Manual edits will be overwritten and can desync the published API reference from the generation pipeline.
Agent Prompt
## Issue description
`APIDOCS.md` is being edited directly, but compliance requires updating the OpenAPI source files instead and letting `APIDOCS.md` be generated.
## Issue Context
This PR includes multiple content changes inside `APIDOCS.md` (generated output). The correct fix is to revert `APIDOCS.md` changes and apply documentation updates in the OpenAPI source inputs.
## Fix Focus Areas
- APIDOCS.md[14-30]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| "DISCORD_CLIENT_ID": "ENC[AES256_GCM,data:uDis8Q44wsMTgkPDmJPX4firRg==,iv:SlB/nm8nFoEM61zsTscMStXE7+scpQKr+eyPB9qWZ30=,tag:+tZJh4eKrYzui9i+WgVtyw==,type:str]", | ||
| "DISCORD_CLIENT_SECRET": "ENC[AES256_GCM,data:6EjgHFCk08X6aL4U39p1FkOLHi1j+vV/BQryDuDX+Hc=,iv:usjPOmKQDUkTckkgQ6Jz7xuNq7xdAI6r3LbYWbNuGg4=,tag:zBCxkEUU4+wSCVkRUFyxrA==,type:str]", | ||
| "DISCORD_BOT_TOKEN": "ENC[AES256_GCM,data:Mlu0IMvjvvpqNiirxuWwPHRf3S4Vt32c/Ocdr+YRvmlirlSIlrZISqW4PnpqxYvwfKGjDbW3DzayTs0UeCwiVOqT715J+w==,iv:eP5HFG+0zNjaOhSP58vUENgMD1QYvwGb1SmZOilXOyM=,tag:3dEIYyLOorL9DCExmZq1gw==,type:str]", |
There was a problem hiding this comment.
2. Secret file changed in pr 📘 Rule violation ⛨ Security
This PR modifies an encrypted secrets file (enter.pollinations.ai/secrets/staging.vars.json) alongside unrelated feature code changes. Mixing secret rotations/updates with feature work increases review and deployment risk and violates the isolation requirement.
Agent Prompt
## Issue description
An encrypted secret file is modified in the same PR as non-secret feature changes, which is not allowed.
## Issue Context
`enter.pollinations.ai/secrets/staging.vars.json` adds new encrypted Discord credentials, while the PR also changes application code (e.g., community endpoint DB schema).
## Fix Focus Areas
- enter.pollinations.ai/secrets/staging.vars.json[19-43]
- shared/db/better-auth.ts[226-234]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| "--paid-only", | ||
| "Only accept Paid Pollen, for a pay-as-you-go upstream whose cost free Quest Pollen would not cover", | ||
| ) | ||
| .option("--no-paid-only", "Accept Quest or Paid Pollen (default)") |
There was a problem hiding this comment.
12. Cli defaults to paid-only 🐞 Bug ≡ Correctness
Commander assigns true by default for the negated --no-paid-only option, and modelBody forwards that value whenever users omit both flags. Creating or updating a model therefore unintentionally sets paidOnly: true, contrary to the documented default.
Agent Prompt
## Issue description
The paired positive and negated Commander options cause omitted `--paid-only` input to be forwarded as `paidOnly: true`.
## Issue Context
This affects both create and update because `modelBody` copies any defined `paidOnly` option into API requests.
## Fix Focus Areas
- packages/polli-cli/src/commands/my-models.ts[106-119]
- packages/polli-cli/src/commands/my-models.ts[258-261]
- packages/polli-cli/src/commands/my-models.ts[311-314]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Implements the price/visibility change delay for public community endpoints (quest pollinations#13800).
Changes:
shared/db/better-auth.ts: addpendingPayload,pendingVisibility, andpendingAtcolumns to the community-endpoint model so price/visibility changes to a public model are queued for 12 hours before they become effective.enter.pollinations.ai/src/routes/community-endpoints.ts: queue price/visibility updates (including transitions to public) into the pending columns instead of applying them immediately; the effective value flips once the deadline passes.enter.pollinations.ai/src/routes/community-endpoints/presenter.ts: expose the pending/effective values so the UI shows the scheduled change.gen.pollinations.ai/src/community-models.ts: apply the effective (post-deadline) values when presenting public models.Gives consumers 12 hours of notice before a public model's price or public visibility changes.