Skip to content

refactor(backend): remove waitlist, CRM, Gemini Free, free models, GitHub stars and error pages - #60

Merged
patrickbrandao merged 11 commits into
mainfrom
feat/15-remove-saas-backend
Oct 11, 2026
Merged

patrickbrandao merged 11 commits into
mainfrom
feat/15-remove-saas-backend

Conversation

@patrickbrandao

Copy link
Copy Markdown
Owner

Closes #15

Stacked PR. This branch is built on feat/14-remove-billing-backend (PR #52, #14), which already contains #12 (PR #51). Review only the last commit. Merge #51 and #52 first, then retarget this PR to main.

Summary

This PR removes the Manifest Cloud and marketing surfaces from the backend. Several of them made outbound calls of their own.

Removed Outbound call it made
waitlist/ (pivot and Autofix claims), plus the open-CORS pivot route in main.ts/cors-csp-config.ts (applyPivotClaimCors, PIVOT_CLAIM_PATH, PIVOT_CLAIM_CLOUD_ORIGIN) none (it accepted any origin)
crm-metrics/ (and its cloud-only registration in app.module.ts), CRM_METRICS_SECRET none (exported e-mails across tenants)
Managed Gemini Free: routing/managed-free-provider/, common/constants/managed-free-providers.ts, CREDITS_* credits.manifest.build
free-models/ raw.githubusercontent.com/mnfst/awesome-free-llm-apis
github/ api.github.com/repos/mnfst/llm-gateway
error-pages/ (public + Peacock push controllers, seeders, ErrorDiscoveryService), MANIFEST_PUBLIC_STATS, ERROR_PAGE_PUSH_SECRET none

Findings from the issue comments are also addressed:

  • gemini-free and the managedFree flag are removed from packages/shared/src/providers.ts, and the CLI catalog is regenerated (provider-catalog.gen.ts, 6 lines).
  • CREDITS_* is removed from docker/.env.example and packages/backend/.env.example.
  • The free-models metadata that pointed at removed icons was deleted together with free-models/.
  • The plan_request_limit_exceeded leftover is removed from packages/shared/src/error-taxonomy.ts, including the limit_exceeded + 402 special case. M204 no longer appears anywhere outside old migrations and negative assertions.
  • The DROP migration sets SET LOCAL lock_timeout.

Hot path. Only the gemini-free entries are removed from PROVIDER_ENDPOINTS, PROVIDER_CONFIGS, PROVIDER_NON_CHAT and the stripModelPrefix exception in provider-client.ts. Also removed: the one-key limit for managed providers in provider.service.ts, whose general limit is still MAX_KEYS_PER_PROVIDER. No other provider's endpoint, prefix handling, fallback order or tier resolution changes. routing-flow, proxy, proxy-fallback-auth and multi-key-providers stay green.

Migration DropSaasFeatureSchema1803300000000

The migration is new, runs after 1803200000000 and is registered in data-source-definitions.ts. No old migration is edited.

up(), in its own transaction (transaction: 'each'):

  1. SET LOCAL lock_timeout = '10s'. If the migration cannot get a lock it fails fast instead of holding up writes, and it is retried on the next boot.
  2. Deletes the agent_enabled_providers grants, then the tenant_providers rows with LOWER(provider) = 'gemini-free'.
  3. Runs DROP INDEX IF EXISTS "IDX_requests_autofix_healed". This is the CRM-only index, created only in cloud mode by 1802200000000, so on a self-hosted DB it is a no-op that never locks requests.
  4. Runs DROP TABLE IF EXISTS on waitlist_claims and public_error_pages.

down() recreates the two tables (empty) as the earlier migrations created them on the self-hosted path. It does not recreate the CRM index (never part of that path) or the deleted connections.

Decision: existing gemini-free connections are deleted

  • Why: the stored LiteLLM virtual key only worked against credits.manifest.build, and the provider id no longer exists in the endpoint registry, the discovery registry or the shared catalog. Leaving the rows inert would show the dashboard a connection it cannot render and give routing a key it cannot use. Deactivating them would keep the dead credential stored.
  • What survives:
    • agent_messages history keeps provider = 'gemini-free'. Only tenant_provider_id is nulled, by the existing ON DELETE SET NULL FK.
    • Routes (override_route/fallback_routes) that still name gemini-free are left alone. ProviderKeyService.isRouteAvailable reports them unavailable, so resolution falls through to the next fallback, exactly as after a disconnect.
  • Not restorable on revert. This is acceptable because no Thinker install exists yet and importing a Manifest DB is not supported (docs/decisoes-iniciais.md §1).
  • Tested in test/drop-saas-feature-schema-migration.e2e-spec.ts:
    • gemini-free rows in two tenants (one mixed-case) are deleted with their grants;
    • the gemini and openai connections and grants of the same tenant are kept;
    • the history row keeps its label with a null link.

Tests

  • New unit spec: 1803300000000-DropSaasFeatureSchema.spec.ts checks that lock_timeout comes first, that grants are deleted before connections, that every DROP uses IF EXISTS, and the revert shape.
  • New e2e test/drop-saas-feature-schema-migration.e2e-spec.ts:
    • migrating from empty leaves no artifacts;
    • down → up → up (idempotent);
    • a leftover cloud CRM index is dropped;
    • the gemini-free data policy above.
  • New e2e test/removed-saas-routes.e2e-spec.ts:
    • /api/v1/waitlist/{pivot,autofix}/claim, /api/v1/internal/crm-metrics[/signups], /api/v1/github/stars, /api/v1/free-models, /api/v1/public/error-pages[/:slug], /api/v1/internal/error-pages[/clusters] and /api/v1/providers/gemini-free/ensure answer 404;
    • connecting gemini-free answers 400.
  • Updated specs:
    • provider-endpoints: no gemini-free endpoint, and resolveEndpointKey('gemini-free'|'gemini free') is null;
    • provider-client: forwarding to gemini-free throws before any fetch;
    • provider-model-fetcher: no fetcher, no outbound call;
    • shared providers: not registered, alias gone;
    • shared error taxonomy: a 402 limit_exceeded stub is classified limit_exceeded.
  • Deleted: test/waitlist.e2e-spec.ts, test/crm-metrics.e2e-spec.ts and the specs of every removed module.
  • The crm tool namespace fixture in responses-tools.spec.ts/responses-stream-tools.spec.ts is renamed to contacts, so the acceptance grep only matches real leftovers.

Gates (local; CI is still provisional, #38)

Gate Result
npm run lint (root: shared, backend, frontend, cli) 0 errors (391 pre-existing warnings)
Prettier on changed files clean
Backend tsc --noEmit clean
Backend npm run build ok
CLI npm run typecheck clean
Unit: backend 485 suites / 9218 tests passed
Unit: shared 21 suites / 462 tests passed
Unit: cli 14 suites / 325 tests passed
Unit: frontend (vitest) 209 files / 4242 tests passed
Backend e2e --runInBand, Postgres 16 (disposable postgres:16-alpine, removed afterwards), TZ=UTC 60 suites / 484 tests passed
Backend e2e, same, local TZ (-03) 59/60 suites, 482/484 tests. Only failures: ST-07/ST-08 in smoke-test.e2e-spec.ts, the known timezone issue tracked in #53/PR #56
node dist/database/migrate.js on an empty DB 147 migrations applied; no waitlist_claims, public_error_pages or IDX_requests_autofix_healed
npm run migration:revert (reverts DropSaasFeatureSchema1803300000000) both tables restored, CRM index absent
Re-apply with migrate.js applied again, tables gone

Frontend check

grep -rnE "managedFree|gemini-free|free-models|freeModels|/ensure|waitlist|crm|error-pages|github/stars|stargazers" packages/frontend/src has no matches. The dashboard calls nothing removed here (#12 already removed the UI).

Acceptance grep

grep -rniE "waitlist|pivot|crm|credits\.manifest|managed-free|free-models|awesome-free|stargazers|error-pages" packages/backend/src outside database/migrations/ still matches three things:

  • Old migration imports in data-source-definitions.ts, which the issue allows.
  • notifications/emails/doctor-release.tsx (+ spec), the Autofix "waitlist" announcement e-mail. It is explicitly in Remover Autofix/Phoenix do backend #16's scope, so it was left alone.
  • The generic analytics word "pivot" (pivotByKey, "pivot table" scoring keywords and test titles). This is unrelated to the pivot waitlist.

Docs

docs/dev/manifest-guide.md changes:

  • the module tree and the route table no longer list the removed modules and routes;
  • the removed variables are gone;
  • a new note explains the variables, the migration and the gemini-free policy.

Out of scope / follow-ups

qa-docker

This should run before the PR leaves draft, because runtime behavior changed (routes removed, outbound calls removed, migration on boot). Suggested checks:

  • the routes listed above answer 404 on the built image;
  • the container boots and migrates cleanly on a fresh volume;
  • proxy fallback still works.

🤖 Generated with Claude Code

patrickbrandao and others added 4 commits October 10, 2026 15:21
Thinker is self-hosted only, so the inherited Manifest Cloud UI goes.
The frontend leaves first so no screen breaks when #14/#15 drop the
matching backend endpoints.

- Billing/plans: delete PlanPicker, UpgradeSuccessModal,
  UsageLimitBanner, the /upgrade page, the Register ?step=plan picker,
  the plan stores, the plan-based range locks on the analytics pages,
  the plan-limit upgrade CTAs in the request views, the Account billing
  and usage-email sections, and the @better-auth/stripe client plugin
  and dependency.
- API Bot: delete the banner, the sidebar card, the dithered ground,
  their stylesheet and the SVG asset.
- GitHub stars: drop the header star button and its /api/v1/github call.
- Free Models: delete the page, its route, its API client and the assets
  only it used.
- Managed Gemini Free: drop the provider entry, icon alias, background
  /ensure provisioning call and the Calendly key URL.
- Cloud branches: checkIsSelfHosted() is gone and every screen behaves
  as self-hosted (Local providers always listed, local logs copy, CLI
  login pinned to this host, discovery after signup and setup). Drop the
  Manifest Cloud origin mapping, the cloud email card, the
  VITE_MANIFEST_SELFHOSTED read and the Dockerfile ENV that set it.
- Help: drop the Manifest Calendly booking link.
- Remove the dead CSS for all of the above, update the tests and the
  inherited dev guide.

Closes #12

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Billing, Stripe and the Free/Pro plans belong to the SaaS layer that
Thinker drops (docs/decisoes-iniciais.md section 3).

- Delete packages/backend/src/billing (PlanService, billing controller,
  billing emails, request quota window, subscription status) and drop
  BillingModule from the app, proxy and analytics modules.
- Proxy: remove the per-request plan gate (assertWithinRequestLimit) and
  the PLAN_LIMIT_REQUESTS / M204 handling in the controller and the
  exception filter. The proxy no longer answers 402 on its own; a 402
  follows the generic error path.
- Auth: remove the @better-auth/stripe plugin; drop the stripe and
  @better-auth/stripe dependencies from the backend.
- Recording retention: keep only the global path (365-day default or
  REQUEST_RECORDING_RETENTION_DAYS); per-plan retention is gone.
- Tenant entity: drop limit_overrides and billing_email_preferences;
  delete the TenantRequestUsage entity.
- Shared: delete plan-limits and its exports.
- Remove the STRIPE_* / PLAN_LIMIT_* / PLAN_REQUEST_QUOTA_RESET_AT
  variables from .env.example.
- New migration DropBillingSchema1803200000000 drops the quota trigger
  and function, requests.quota_counted, tenant_request_usage,
  billing_email_logs and the two tenant columns. The old
  AddTenantRequestUsage migration keeps only its self-hosted path
  (a no-op up) so it no longer imports deleted billing code.

/api/v1/billing/* no longer exists, and the STRIPE_* and PLAN_LIMIT_*
variables are ignored if set.

Closes #14

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tHub stars and error pages

Remove the Manifest Cloud and marketing surfaces from the backend:

- waitlist/ (pivot and Autofix claims) and its open-CORS pivot route
  (applyPivotClaimCors, PIVOT_CLAIM_PATH, PIVOT_CLAIM_CLOUD_ORIGIN).
- crm-metrics/ and CRM_METRICS_SECRET.
- The managed Gemini Free provider: routing/managed-free-provider/,
  common/constants/managed-free-providers.ts, the CREDITS_* variables, and
  its entries in provider-endpoints, provider-client, provider.service,
  provider-model-fetcher and routing.module. No other provider's endpoint,
  prefix handling, fallback order or tier resolution changes.
- free-models/ (sync from mnfst/awesome-free-llm-apis), github/ (stars from
  api.github.com) and error-pages/ (public and Peacock push routes, seeders,
  discovery), MANIFEST_PUBLIC_STATS and ERROR_PAGE_PUSH_SECRET.
- gemini-free and the managedFree flag from manifest-shared, and the
  regenerated CLI provider catalog.
- The plan_request_limit_exceeded error class left over from #14.

DropSaasFeatureSchema1803300000000 sets a transaction-local lock_timeout,
drops waitlist_claims, public_error_pages and the cloud-only
IDX_requests_autofix_healed index, and deletes every gemini-free connection
with its agent grants. Request history keeps its provider label, and routes
that still name gemini-free resolve as unavailable and fall through to their
fallbacks, as after a disconnect. down() restores the two tables as the
self-hosted path created them.

Closes #15

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@patrickbrandao

Copy link
Copy Markdown
Owner Author

Revisão do PR #60 (#15): remoção de waitlist, CRM, Gemini Free, free-models, GitHub stars e páginas de erro

Escopo revisado: PR empilhado. Revisei git diff origin/feat/14-remove-billing-backend...64f31b4499d7e6c848278e0fe15dfb3bfc25689f, ou seja, só o commit 64f31b4 (84 arquivos, +419/−7671). Li o código ao redor nesse SHA.

Nenhum bloqueador. O PR entra em risco alto por um critério: ele adiciona uma migration que destrói dados. O up() de DropSaasFeatureSchema1803300000000 faz DROP TABLE de waitlist_claims e public_error_pages e apaga linhas de tenant_providers e de agent_enabled_providers, e o revert não restaura nada disso.

O que verifiquei

1. Migration 1803300000000-DropSaasFeatureSchema

  • SET LOCAL lock_timeout = '10s' é a primeira instrução. Isso tem efeito no caminho real: run-migrations-with-lock.ts:62 e database.module.ts:52 usam transaction: 'each'. O padrão já existia em 1801720000000 e 1801900000000.

  • Todo DROP usa IF EXISTS. Rodar duas vezes não causa problema: o e2e faz down, up e up de novo.

  • O down() recria as duas tabelas com a forma que tinham no caminho self-hosted. Conferi:

    • waitlist_claims bate com 1799000000000 mais o rename da 1800000000000: colunas, PK_waitlist_claims e UQ_waitlist_claims_email;
    • public_error_pages reproduz a 1797000000000 coluna por coluna.

    Com isso, os down() antigos continuam funcionando quando o revert passa da 1803300000000. O down() não usa SET LOCAL, e por isso funciona com migration:revert --transaction none.

  • FKs que apontam para tenant_providers:

    • agent_enabled_providers.tenant_provider_id tem ON DELETE CASCADE (1791000000000). O DELETE explícito das grants é redundante, mas não faz mal.
    • agent_messages.tenant_provider_id tem ON DELETE SET NULL (1792000000000), e o índice que começa por tenant_provider_id atende essa ação.

    Não há outra FK, nem coluna tenant_provider_id em outra entidade. O e2e confirma que o histórico mantém provider = 'gemini-free' e fica com o link nulo.

  • Multi-tenant: o único DELETE entre tenants é LOWER("provider") = 'gemini-free'. Provedores custom usam custom:<uuid>, então não podem colidir com esse id. O e2e cobre dois tenants, um deles em caixa mista, e mantém gemini e openai do mesmo tenant.

2. Hot path

  • Só saíram as entradas de gemini-free:
    • de PROVIDER_ENDPOINTS (o spread MANAGED_FREE_ENDPOINTS);
    • de PROVIDER_CONFIGS e PROVIDER_NON_CHAT;
    • a exceção em stripModelPrefix, que valia só para esse id. openrouter continua com o prefixo, e commandcode não mudou.
  • Em provider.service.ts, o limite agora é sempre MAX_KEYS_PER_PROVIDER, com a mesma mensagem que os outros provedores já recebiam.
  • O tratamento de erro do proxy não mudou. Se gemini-free chegasse ao forward, cairia no caminho que já existe (No endpoint configured), e há teste novo para isso em provider-client.spec.ts. Isso só aconteceria com uma chave, e a migration remove todas.
  • Uma rota que ainda cite gemini-free cai em ProviderKeyService.isRouteAvailable (provider-key.service.ts:417). Sem modelo descoberto e sem registro ativo, a função devolve false. A partir daí valem os caminhos de órfão que já existem e têm teste:
    • o header tier promove o primeiro fallback disponível;
    • specificity e tier passam adiante.
  • POST /routing/:agent/providers com gemini-free responde 400, porque KNOWN_PROVIDER_IDS vem do shared.

3. CORS/CSP

  • A remoção de applyPivotClaimCors tira o único middleware com Access-Control-Allow-Origin: *. Isso aperta o CORS em vez de afrouxar.
  • applyPrivateNetworkAllow e app.enableCors(buildCorsOptions(...)) não mudaram.
  • O connectSrc da CSP já era 'self' na base.
  • cors-csp-config.spec está verde.

4. Rotas removidas

  • Os diretórios dos módulos foram apagados, então as rotas não têm como existir.
  • O ServeStatic exclui /api/{*path}, então essas rotas respondem 404 e não o HTML da SPA.
  • git grep em packages/frontend/src, packages/cli/src e packages/shared/src não acha chamadas às rotas removidas, nem managedFree ou gemini-free.

5. Shared e CLI

  • Em error-taxonomy.ts, plan_request_limit_exceeded saiu da lista e do mapa, e o caso especial 402 também. Fora das migrations antigas e das asserções negativas, não sobra nenhum consumidor:

    • nem mapa de labels no frontend (o tsc passa);
    • nem SQL que espelhe a classificação.

    O IsIn(ERROR_CLASSES) do filtro de mensagens só fica mais estreito.

  • provider-catalog.gen.ts: rebuildei o manifest-shared neste SHA e rodei generate-provider-catalog.cjs e generate-skill-content.cjs. Nenhum diff, então o arquivo gerado está em sincronia.

Projeto e segurança

  • Nenhum destino de saída novo. O PR remove três: credits.manifest.build, api.github.com e raw.githubusercontent.com/mnfst/awesome-free-llm-apis.
  • Nenhum identificador manifest/mnfst novo. O body_manifest no down() só reproduz o schema histórico.
  • O título segue Conventional Commits.
  • Changeset não se aplica: manifest-backend, manifest-shared e mnfst-gateway-cli estão em ignore, como no refactor(backend): remove billing, Stripe and plan limits #52.
  • O PR não mexe em arquivos de governança nem em workflows.

Execuções que fiz neste head (64f31b4)

Verificação Resultado
npm run build em packages/shared limpo
tsc --noEmit em backend, frontend e cli (npm run typecheck) limpo
Regeneração do catálogo do CLI sem diff
Unit do backend afetado (migration, provider-endpoints, provider-client, model-discovery, cors-csp-config, routing-core, resolve, responses-*) 47 suítes, 1793 testes, verdes
Unit do shared 21 suítes, 462 testes, verdes
e2e com Postgres 16 descartável, TZ=UTC: drop-saas-feature-schema-migration, removed-saas-routes, routing-flow, proxy-fallback-auth, multi-key-providers, drop-billing-schema-migration, proxy, proxy-no-plan-gate 8 suítes, 61 testes, verdes

O relatório local do autor cobre lint, tsc e build do backend, typecheck do CLI, unit dos quatro pacotes e e2e completo. Ele não lista o build do shared nem o typecheck e o build do frontend. Rodei os dois primeiros acima e estão limpos.

Achados

Bloqueadores: nenhum.

Should-fix: nenhum.

Nits:

  1. packages/backend/test/removed-saas-routes.e2e-spec.ts:22-43: parte das asserções de 404 não testa nada. O createTestApp (test/helpers.ts) nunca registrou GithubModule, FreeModelsModule nem ErrorPagesModule, nem antes deste PR. Por isso /github/stars, /free-models, /public/error-pages* e /internal/error-pages* já respondiam 404 nesse app antes da remoção. Só waitlist, crm-metrics e /providers/gemini-free/ensure tinham módulo registrado e provam algo.
    • Impacto hoje: baixo, porque o código foi apagado.
    • Correção: dizer isso no comentário do spec e deixar a prova contra o AppModule para o qa-docker na imagem real, que o próprio PR já sugere.
  2. Falta um teste de regressão específico para rotas que citam gemini-free. O comportamento "rota que cita gemini-free vira indisponível e cai no fallback" aparece no PR e na doc, mas nenhum teste cita esse id. A cobertura é genérica: provider-key.service.spec.ts:550 e os casos de override órfão em resolve.service*.spec.ts. Como o tratamento de erro não mudou, isso não é bloqueador.
    • Correção opcional: um caso em resolve.service.spec com um override {provider: 'gemini-free'} e um fallback openai conectado. O resultado esperado é o fallback promovido.
  3. 1803300000000-DropSaasFeatureSchema.ts:46: DROP INDEX sem CONCURRENTLY. Quando o índice existe (só em banco que rodou em modo cloud), o comando pega ACCESS EXCLUSIVE em requests dentro da transação. O lock_timeout de 10s limita a espera, e o PR documenta que self-hosted não tem esse índice. Sem instalação Thinker, o impacto é zero. Registro só para quem portar um banco cloud, o que não é suportado.
  4. .github/workflows/docker-smoke.yml:204-210: nome e mensagem do passo estão desatualizados. O passo "Verify public error pages are gated off by default" e a mensagem citam MANIFEST_PUBLIC_STATS. O passo continua passando (o 404 agora é incondicional) e serve como guarda de regressão. Editar o workflow aqui seria mudança de workflow fora do escopo do CI mínima provisória #38, então é melhor fazer em outro PR com issue, ou na limpeza do Remover Autofix/Phoenix do backend #16 ou Modo único self-hosted: remover MANIFEST_MODE #18. O mesmo vale para o badge de GitHub stars em docker/DOCKER_README.md:11 e README.md:24, que já está listado como follow-up de rebrand.
  5. Notas da 0.1.0 (Release, Changesets 0.1.0 e publicação no Docker Hub #31): devem registrar:
    • a remoção do provedor "Gemini Free", com a exclusão automática das conexões;
    • as rotas removidas;
    • as variáveis CREDITS_*, MANIFEST_PUBLIC_STATS, ERROR_PAGE_PUSH_SECRET e CRM_METRICS_SECRET.

Critério de risco alto

  • Migration que destrói dados: DROP TABLE em waitlist_claims e public_error_pages, e DELETE das conexões gemini-free (credencial e grants), sem caminho de volta para os dados. A decisão está justificada no PR: a credencial só funcionava contra credits.manifest.build, não existe instalação Thinker, e importar um banco Manifest não é suportado (docs/decisoes-iniciais.md §1). O critério continua valendo, e quem decide é o mantenedor (ADR 0002).

Gates restantes (não fazem parte deste veredito)

Head revisado: 64f31b4
Veredito: risco alto (aguardar mantenedor)

patrickbrandao and others added 4 commits October 11, 2026 13:42
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main now holds the squash of #51, whose tree equals ce9fd33, which this
branch already merged, so the result keeps this tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #67

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@patrickbrandao
patrickbrandao changed the base branch from feat/14-remove-billing-backend to main October 11, 2026 17:09
patrickbrandao and others added 3 commits October 11, 2026 14:23
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #67

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@patrickbrandao

Copy link
Copy Markdown
Owner Author

Nova revisão do PR #60 (#15) depois do retarget para main

Escopo revisado: git diff origin/main...498de2300c4475461ba810df30009b1cdeff3475. O merge-base é df6ca96 (o main atual), então o diff de três pontos é igual ao de dois pontos. São 84 arquivos, +420/−7671. Li o código ao redor nesse SHA, num worktree destacado.

Nenhum bloqueador. O PR continua em risco alto pelo mesmo critério da revisão anterior: ele adiciona uma migration que destrói dados. DropSaasFeatureSchema1803300000000 faz DROP TABLE em waitlist_claims e public_error_pages e apaga as conexões gemini-free (credencial e grants), sem caminho de volta para os dados.

1. A atualização reproduz o que foi revisado antes

2. O código mais novo do main não depende do que este PR remove

Rodei git grep na árvore de 498de23, fora das migrations antigas, procurando gemini-free, managedFree, managed-free, waitlist, crm-metrics, free-models, FREE_MODELS, error-pages, PublicErrorPage, github/stars, GithubModule, credits.manifest, awesome-free-llm, CREDITS_, MANIFEST_PUBLIC_STATS, ERROR_PAGE_PUSH_SECRET, CRM_METRICS_SECRET e plan_request_limit_exceeded. O código que entrou no #63, #69, #100/#103, #104/#106/#107, #5, #52 e #57 não cita nenhum deles. As ocorrências que sobram são:

  • asserções negativas nos testes, que é o esperado;
  • itens que a revisão anterior já listou: docker-smoke.yml, README.md, doctor-release.tsx (Remover Autofix/Phoenix do backend #16) e o comentário na migration 1802200000000;
  • README.md:100, a linha "Gemini Free", e dashboard-redesign-brief.md:189, ambos documentação herdada (nit 5);
  • FREE_MODELS_CACHE_TTL_MS, que é novo (nit 1).

Os arquivos de src que o PR altera foram relidos em cima do main atual. Em provider-client.ts, a mudança em stripModelPrefix convive com o código de headers do #107 sem conflito. Em provider-endpoints.ts e provider-model-fetcher.service.ts, só saíram os spreads de managed-free. Em provider.service.ts, o limite agora é sempre MAX_KEYS_PER_PROVIDER. O main.ts só perdeu applyPivotClaimCors, o que aperta o CORS.

3. Todo spec que apaga o banco passa pelo guard

  • Todo arquivo em packages/backend/test que usa dropSchema chama resolveE2eDatabaseUrl(). São os 13 specs de migration/DataSource, mais helpers.ts e e2e-global-setup.ts.
  • request-recordings.e2e-spec.ts só menciona dropSchema como opção de createTestApp(), que já passa pelo guard.
  • Nenhum spec lê process.env['DATABASE_URL'] direto, nem tem fallback para postgresql://...localhost.
  • O commit 498de23 troca o fallback de drop-saas-feature-schema-migration.e2e-spec.ts:72 por resolveE2eDatabaseUrl(), igual ao que o refactor(backend): remove billing, Stripe and plan limits #52 fez.

4. Execuções neste head (498de23)

CI, consultado na API de check-runs com --paginate: tudo success.

Check Resultado
Lint success
Unit (shared, backend, frontend, cli) success
Backend e2e (PostgreSQL 16) success: 64 suítes, 526 testes. drop-saas-feature-schema-migration, removed-saas-routes e drop-billing-schema-migration aparecem como PASS no log
Compose smoke test (read-only), install.sh end-to-end success
CodeQL, dependency-review success

Nenhum teste instável apareceu nesta execução, nem o de local-providers.e2e-spec.ts (#101).

No meu worktree. O CI não tem job de typecheck, então rodei:

Verificação Resultado
npm ci e build de packages/shared ok
tsc --noEmit no backend e no frontend, npm run typecheck no cli limpos
npm run gen no cli, que regenera provider-catalog.gen.ts e o conteúdo da skill sem diff

A tabela de gates no corpo do PR é do head antigo. Agora vale o CI acima, que já cobre lint, unit e e2e.

Achados

Bloqueadores: nenhum.

Should-fix: nenhum.

Nits

O nit 1 é novo. Os nits 2 a 6 vêm da revisão anterior, com o nit 5 ampliado.

  1. packages/backend/src/common/constants/cache.constants.ts:13: export morto. FREE_MODELS_CACHE_TTL_MS era usado só por free-models.controller.ts, que este PR apaga. Hoje nada o importa. Não quebra nada, mas é resto da feature removida.

  2. packages/backend/test/removed-saas-routes.e2e-spec.ts:22-43: parte das asserções de 404 não prova nada. O createTestApp nunca registrou GithubModule, FreeModelsModule nem ErrorPagesModule. A prova contra o AppModule real fica com o qa-docker.

  3. Não existe teste de regressão de uma rota que cite gemini-free. O caminho de órfão é coberto por testes genéricos. Opcionalmente, dá para adicionar um caso em resolve.service.spec com um override {provider: 'gemini-free'} e fallback openai.

  4. 1803300000000-DropSaasFeatureSchema.ts:46: DROP INDEX sem CONCURRENTLY. O índice só existe em bancos que rodaram em modo cloud, e o lock_timeout limita a espera.

  5. Documentação herdada ainda cita o que foi removido.

    • .github/workflows/docker-smoke.yml:204-210: o nome do passo e a mensagem citam MANIFEST_PUBLIC_STATS. O passo continua passando, e mudar workflow está fora do escopo deste PR.
    • README.md:24 e docker/DOCKER_README.md:11: o badge de GitHub stars.
    • README.md:100: a linha "Gemini Free", com "Managed key".
    • dashboard-redesign-brief.md:189.

    Tudo isso cabe na limpeza de rebrand e README, ou no Remover Autofix/Phoenix do backend #16/Modo único self-hosted: remover MANIFEST_MODE #18.

  6. Notas da 0.1.0 (Release, Changesets 0.1.0 e publicação no Docker Hub #31): devem registrar:

    • a remoção do provedor Gemini Free, com a exclusão automática das conexões;
    • as rotas removidas;
    • as variáveis CREDITS_*, MANIFEST_PUBLIC_STATS, ERROR_PAGE_PUSH_SECRET e CRM_METRICS_SECRET.

Critério de risco alto

Migration que destrói dados. DROP TABLE em waitlist_claims e public_error_pages, DROP INDEX IF EXISTS do índice de CRM e DELETE das conexões gemini-free e das grants delas. O down() recria só o schema. O critério da ADR 0002 se aplica: quem decide o merge é o mantenedor, não o Principal.

O PR não mexe em auth, cripto, isolamento entre tenants, governança, workflows nem release. Não há destino de saída novo. O PR remove três: credits.manifest.build, api.github.com (stars) e raw.githubusercontent.com/mnfst/awesome-free-llm-apis.

Gates restantes (não fazem parte deste veredito)

Qualquer commit novo, inclusive um que corrija os nits, invalida este veredito e exige nova revisão.

Head revisado: 498de23
Veredito: risco alto (aguardar mantenedor)

@patrickbrandao

Copy link
Copy Markdown
Owner Author

qa-docker: PASS

Tested SHA: 498de2300c4475461ba810df30009b1cdeff3475 (head of feat/15-remove-saas-backend, confirmed with gh pr view; it includes main with #51, #52 and #57). Image thinker:qa-26b822 built from docker/Dockerfile on arm64 in 18 s (warm cache), 457 MB. For the egress and upgrade comparisons I also built origin/main df6ca96 as thinker:qa-26b822-main. Isolated Compose projects (thinker-qa-26b822, -3d62a5, -e4514a, -0f03d7), volumes renamed through an override (name: !reset null), resolved names carried the run id, labels checked against config --volumes before every down -v, no volume, container or network left behind, host 5432/5433 untouched. Postgres 16.15 (postgres:16-alpine@sha256:721873c3...). Base images: node:24-alpine (build), node:24-slim (prod deps), gcr.io/distroless/nodejs24-debian13:nonroot (runtime), all by digest.

Step Result
Build pass (18 s, 457 MB)
Fresh volume boot, /api/v1/health pass (healthy in about 4 s). Migrations run clean up to DropSaasFeatureSchema1803300000000 (147 rows in migrations). waitlist_claims, public_error_pages and IDX_requests_autofix_healed do not exist afterwards
First-admin setup (second attempt refused with 400), wrong password 401, sign-in, agent + key, custom provider pass
Gateway through a host mock: /v1/models, chat completions (JSON, SSE), /v1/messages (Bearer, x-api-key, SSE), /v1/responses (JSON, SSE), no key gives 401, plus 60 more mixed requests pass, all 200
Removed endpoints, with session cookie and without, with a fake x-internal-secret: POST /waitlist/pivot/claim, POST /waitlist/autofix/claim, OPTIONS /waitlist/pivot/claim, GET /internal/crm-metrics (+/signups, /conversions), GET /github/stars, GET /free-models, GET /public/error-pages (+/:slug), GET/POST/DELETE /internal/error-pages..., POST /providers/gemini-free/ensure pass, all 404 (health 200 and agents 200/401 as controls). Connecting gemini-free through POST /routing/:agent/providers is rejected with 400 provider must be one of: ...
Upgrade main -> PR image on the same volume, seeded with a gemini-free connection + grant + a history message pointing at it, a waitlist claim and a public error page pass: migration 147 DropSaasFeatureSchema ran at boot, both tables gone, gemini-free connection and grant deleted, the custom provider connection kept, the history row kept provider = gemini-free with tenant_provider_id NULL. Gateway smoke on the upgraded DB: all 200, 60 loop requests included. Dashboard routes load with 120 API calls, 0 non-2xx
Signed-in dashboard in headless Chromium: 31 routes (overview, requests, harnesses, playground, providers, integrations, account, all harness tabs, legacy redirects, /free-models, /discovery, /upgrade, /cli/auth) plus the request drawer pass: 273 API calls, 0 non-2xx, 0 external requests, 0 failed requests, 0 page errors, 0 console warnings or errors. The frontend never calls a removed endpoint
Contracts contracts/gateway against the container pass, 6/6
Backend e2e: TZ=UTC npm run test:e2e:docker --workspace=packages/backend (shared built first) pass: 64 suites, 526 tests, 210 s (221 s wall). Main had 554 tests; the difference is the deleted waitlist, CRM, error-pages and free-models specs plus the new removed-saas-routes and drop-saas-feature-schema-migration e2e specs. No flaky test, nothing needed a retry (the run overlapped with two egress stacks and still passed first time)
Teardown (down -v with -p, label check first), worktrees and images removed pass

Egress (PR vs main df6ca96, same procedure)

Default env (telemetry and update check not disabled), tcpdump sidecar in the app netns: boot, a Chromium session across 13 dashboard routes, about 75 s idle. Outbound TCP SYNs to public addresses, by destination:

Destination main PR
openrouter.ai (104.18.2.115) 1 1
models.dev (104.26.9.108) 1 1
raw.githubusercontent.com (185.199.111.133) 2 1
modelparams.dev (216.150.1.1) 1 2 (the same 403 retry, a second fetch 17 s later; same destination)
autofix.manifest.build (69.46.46.119) 1 1

The second raw.githubusercontent.com connection on main is the awesome-free-llm-apis list fetch by FreeModelsSyncService ("Refreshing free models cache from GitHub..." and "Free models cache loaded: 16 providers" appear in the main log and are gone from the PR log). The remaining raw.githubusercontent.com connection is the OpenCode Go doc (ADR 0003). Nothing new appears in the PR and nothing was contacted during the idle period in either run.

The on-demand destinations only fire when something calls the removed endpoints, so I checked them separately on the upgrade stack. On the main image, GET /api/v1/github/stars returned {"stars":7567} and connected to api.github.com (4.228.31.149), and POST /api/v1/providers/gemini-free/ensure connected to credits.manifest.build (69.46.46.46). On the PR image the same calls return 404 and tcpdump shows only the five boot destinations above. (My ensure call on main hit the real credits endpoint once, with no credentials of ours. I should have skipped it; I won't repeat it.)

Observations (not blockers)

  • /api/v1/agents creates keys with the legacy mnfst_ prefix, owned_by: manifest in /v1/models, error messages with the Manifest brand; unchanged by this PR, still on the v0.1 rebrand list.
  • Still on by default and outside this PR: anonymous telemetry (TELEMETRY_ENDPOINT default telemetry.manifest.build), the AutofixHealthProbe call to autofix.manifest.build (the only non-catalog destination the PR still contacts at boot), and the modelparams.dev catalog fetch (403 from here). These contradict the no-phone-home rule and need their own tickets.
  • First boot on an empty database logs ERROR [Better Auth]: Database schema mismatch before the tables exist. Same on main.

@patrickbrandao
patrickbrandao merged commit 44f1689 into main Oct 11, 2026
11 checks passed
@patrickbrandao
patrickbrandao deleted the feat/15-remove-saas-backend branch October 11, 2026 17:41
patrickbrandao added a commit that referenced this pull request Oct 11, 2026
Brings in 498de23 (main plus #60) so #16 can merge into main after #60.
Conflicts came from #57's interim dashboard handling of the legacy Autofix
backend overlapping with this branch's backend removal:

- frontend analytics client: RELIABILITY_PATHS points at the neutral
  /overview/reliability/* routes. foldLegacyRecoveredKey is dropped: the
  backend computes the request disposition from success/fallback only and
  DropAutofixSchema removes requests.autofix_status, so no row can come back
  as "healed", and the dashboard ships with its own backend.
- frontend agents client: the auto-merged autofix_enabled opt-out on create
  and the PATCH /routing/:agent/autofix after duplicate are dropped (the field
  and the endpoint no longer exist; create rejects the field).
- shared: keep the playground-headers exports, drop the autofix-status ones.
- tests: keep this branch's versions (neutral paths, no opt-out, no fold).
- e2e specs from main (local providers, reserved local runner aliases): stop
  sending autofix_enabled on agent create.
- manifest guide: keep the "Autofix removed" section and neutral-path wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
patrickbrandao added a commit that referenced this pull request Oct 11, 2026
origin/main (44f1689, the squash of #60) has the same tree as 498de23,
which this branch already merged, so this merge only records main as an
ancestor and changes no file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
patrickbrandao added a commit that referenced this pull request Oct 12, 2026
…base guard (#111)

* test(backend): fail the unit suite when an e2e spec bypasses the database guard

Add test/e2e-db-guard-scan.ts and its spec, which run in the unit suite
and parse every .ts file under packages/backend/test with the TypeScript
AST. They fail when a file:

- wipes the database (dropSchema not literally false, DROP SCHEMA or
  DROP DATABASE SQL, .dropDatabase(), .synchronize(true)) without
  calling resolveE2eDatabaseUrl() or createTestApp();
- falls back from process.env.DATABASE_URL with ?? or ||;
- has a string naming localhost:5432 or mydatabase (e2e-db-guard.spec.ts
  is exempt, since it proves the guard refuses those URLs).

Fixtures under test/fixtures/e2e-db-guard (*.fixture, outside the scan
and outside Jest) reproduce the legacy migration-spec pattern from #52,
#60 and #66, a raw DROP SCHEMA reset, and the accepted guarded pattern.
A temp-dir tree scan proves that adding the legacy spec fails the check.

Closes #108

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(backend): treat a queryRunner.dropSchema() call as a wipe in the guard scan

Refs #108

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remover waitlist, CRM, Gemini Free, free-models, GitHub stars e páginas de erro públicas

1 participant