Skip to content

feat(backend)!: remove phone-home from the backend and the CLI - #70

Merged
patrickbrandao merged 24 commits into
mainfrom
feat/17-remove-phonehome-backend
Oct 11, 2026
Merged

patrickbrandao merged 24 commits into
mainfrom
feat/17-remove-phonehome-backend

Conversation

@patrickbrandao

@patrickbrandao patrickbrandao commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Closes #17

Update: no longer stacked. #66 is squash-merged, the base is main, and CI runs against main. The branch merged #66's final head 5eef655 (conflicts in package.json/lockfile: Sentry stays removed, better-auth 1.7.7 kept; cors.e2e-spec.ts: #70's Wingman-free builders kept; drop-autofix-schema-migration.e2e-spec.ts: both the DropInstallMetadata revert and the e2e database guard kept), then origin/main (b1ba799, same tree). Guard fix: drop-install-metadata-migration.e2e-spec.ts and no-phonehome-egress.e2e-spec.ts now take their dropSchema URL from resolveE2eDatabaseUrl() with no localhost fallback (d536bce).

Summary

Thinker makes no outbound call of its own. This removes every upstream phone-home from the backend and the CLI.

Upstream phone-home Destination Removed
Usage telemetry (backend) telemetry.manifest.build/v1/report telemetry/ (incl. InstallIdService, PayloadBuilderService), entities/install-metadata.entity.ts, MANIFEST_TELEMETRY_DISABLED, TELEMETRY_ENDPOINT
Usage telemetry (CLI) telemetry.manifest.build/v1/cli-report packages/cli/src/telemetry.ts (+ spec), the calls and USAGE notice in index.ts, the README section, MANIFEST_CLI_TELEMETRY_ENDPOINT
Update check api.github.com/repos/mnfst/llm-gateway/releases version/ (module, controller, version-check.config.ts), GET /api/v1/version, MANIFEST_UPDATE_CHECK_*
Discovery blue.manifest.build/v1/self-hosted/discovery discovery/, POST /api/v1/discovery/complete, DISCOVERY_ENDPOINT
Sentry *.sentry.io (when SENTRY_DSN set) instrument.ts, sentry/, SentryModule/SentryGlobalFilter, @sentry/nestjs (and its 17 transitive packages in the lockfile), the !instrument.ts coverage exclusion, SENTRY_*
Hosted Wingman wingman.manifest.build always allowed by CORS (dev + prod) and CSP frame-src (dev) HOSTED_WINGMAN_ORIGIN, wingmanPort/WINGMAN_PORT, the Wingman dev origins, buildFrameSrc (frame-src is now 'self' in every mode)
modelparams.dev modelparams.dev/api/v1/models.json on boot + @Cron('34 * * * *') refreshCatalog, fetchAndSwapCatalog, scheduledRefresh, onModuleInit, ETag, MODELPARAMS_API_*; ProviderParamSpecService serves only the bundled modelparams snapshot (decided in ADR 0003 / #11)
CLI default host (commit d168f73) app.manifest.build whenever no --url, MANIFEST_URL or stored login: a MANIFEST_API_KEY set without a host went to Manifest's cloud DEFAULT_URL is now http://localhost:2099 (the default Thinker port); the invalid-URL hint and the README follow. The config dir and the CLI rename stay in #23
Backend /chat/completions 404 hint told callers to set baseURL to https://app.manifest.build/v1 now points at "this gateway URL followed by /v1"

WINGMAN_CORS_ORIGINS is renamed THINKER_CORS_ORIGINS, same meaning (extra browser origins allowed to call the gateway, exact match, trailing slash stripped). It now also applies in dev, on top of CORS_ORIGIN. With it unset, production allows no cross-origin browser caller at all (security: no third-party origin by default).

Schema: new migration DropInstallMetadata1803500000000 drops install_metadata (SET LOCAL lock_timeout = '10s', DROP TABLE IF EXISTS). down() recreates the table as 1775700000000 created it, empty, so reverting DropAutofixSchema1803400000000 next can restore its column. No SET LOCAL in down(), matching #16, because migration:revert runs with --transaction none.

Not touched: the OpenRouter / models.dev / OpenCode Go catalog behaviour (#39), the frontend ModelParamsAffordance.tsx report link (#25).

manifest.build hosts in the CLI: besides DEFAULT_URL, the only ones left are docs links: the README "CLI documentation" link and homepage in package.json. Neither is an API endpoint; both are left to #23. skill/SKILL.md and the generated skill content have none. Spec fixtures that used app.manifest.build now use gateway.example.com.

Backend leftovers, out of scope here: app.manifest.build / gateway.manifest.build remain in three backend production files. None of them is a request destination:

The guard pins this list so it can only shrink.

Docs and config

  • docs/dev/manifest-guide.md: overrides banner, intro, file tree, endpoint table, env vars (MANIFEST_TELEMETRY_*, MANIFEST_UPDATE_CHECK_*, DISCOVERY_ENDPOINT, WINGMAN_*, SENTRY_*, MODELPARAMS_API_* removed, THINKER_CORS_ORIGINS added). The Telemetry and Sentry sections are replaced by a "No phone-home" section.
  • docker/docker-compose.yml, docker/.env.example, packages/backend/.env.example: removed variables gone, THINKER_CORS_ORIGINS added. docker/install.sh had none.
  • docker/Dockerfile: drops the runtime COPY packages/manifest/package.json, which only the telemetry sender read. The frontend reads that file at build time, in the build stage, so the build is unaffected.
  • docker/DOCKER_README.md, CONTRIBUTING.md, deploy/{easypanel,fly,apple-containers}: telemetry, update-check, Sentry and Wingman mentions removed. Only lines were deleted; the rest of these templates is left to Docker, Compose e instalador do Thinker #26 and Templates de deploy da comunidade #30.

Guard against regressions

  • test/no-phonehome-egress.e2e-spec.ts:

    • Source scan. Fails if packages/{backend,cli,shared}/src (old migrations aside) mention telemetry.manifest.build, blue.manifest.build, api.github.com/repos/mnfst, modelparams.dev, wingman.manifest.build, sentry.io or any of the removed services and variables, or if @sentry/* is a dependency again.
    • Boot check. Boots the real server (src/main.ts, full AppModule) in a child process with NODE_ENV=production and no opt-out variable. A preload (test/support/egress-recorder.cjs) records every fetch URL and every DNS lookup, so http/net egress that bypasses fetch is caught too, and keeps the process offline. The test asserts:
      • no phone-home destination is contacted;
      • no fetch goes outside the ADR 0003 catalogs;
      • no remote hostname is resolved;
      • /api/v1/version and /api/v1/discovery/complete answer 404.
    • Mutation check. I temporarily restored the old provider-param-spec.service.ts: the boot check failed with the recorded https://modelparams.dev/api/v1/models.json fetch.
    • I used a child process instead of importing AppModule into jest because ts-jest reports a keyv duplicate-type error on app.module.ts that tsc/nest build do not. No e2e had imported AppModule before.
  • test/cors.e2e-spec.ts covers:

    • dev allows the Vite origin only;
    • production allows nothing by default and rejects the former hosted origin;
    • a THINKER_CORS_ORIGINS opt-in gets the preflight, the POST, the PNA header and maxAge, with no credentials.
  • test/csp.e2e-spec.ts asserts frame-src 'self' and no remote origin in the CSP.

  • test/drop-install-metadata-migration.e2e-spec.ts covers:

    • the table is absent after a migration from empty;
    • a populated table is dropped, and the drop is idempotent;
    • revert works in runner order through the Remover Autofix/Phoenix do backend #16 drop, then reapplies.
  • test/drop-autofix-schema-migration.e2e-spec.ts now reverts this PR's drop first, in runner order, so the table its revert touches exists.

  • provider-param-spec.service.spec.ts:

    • the remote-refresh tests are replaced by "no boot hook / cron / refresh / fetch";
    • snapshot validation (parseModelParametersCatalog, now exported) is tested directly.
  • Hosted gateway guard (in no-phonehome-egress.e2e-spec.ts):

    • fails on app.manifest.build or gateway.manifest.build anywhere in the CLI or shared source, tests included;
    • fails on any backend production file naming them beyond the three pinned leftovers;
    • fails if the CLI DEFAULT_URL is not http://localhost:2099;
    • fails on a boot request to either host.

    Mutation check: with the old DEFAULT_URL restored, both CLI checks fail.

  • CLI config.spec.ts asserts the local default, including that an env key with nothing configured resolves to http://localhost:2099. index.spec.ts asserts that agent list with only MANIFEST_API_KEY sends every request, carrying the key, to http://localhost:2099.

  • CLI index.spec.ts checks that a command sends only its own request, writes no telemetry files, and that USAGE has no telemetry notice. The test helper no longer sets an opt-out variable.

Gates (local; CI only runs on PRs to main)

Gate Result
npm run lint (root: shared, backend, frontend, cli) 0 errors (372 pre-existing warnings)
Prettier on touched code files clean
Typecheck/build: shared build, backend tsc --noEmit + nest build, cli typecheck + build pass
Unit backend 464 suites, 8786 tests pass
Unit cli 13 suites, 307 tests pass
Unit shared 20 suites, 451 tests pass
Unit frontend (not touched) 200 files, 4135 tests pass
Backend e2e, in band, TZ=UTC, disposable postgres:16 (removed after) 64 suites, 526 tests pass
Migration via CLI on a clean schema migration:run → table absent. migration:revert → table recreated (5 columns). Second revert (#16) → autofix_consented_at restored. migration:run → table absent, no autofix* columns
Issue acceptance grep on packages/backend/src packages/cli/src only migrations match (the old ones and the new drop)

qa-docker

Required: runtime behaviour changed (no outbound calls, CORS/CSP, migration, Dockerfile). The issue's acceptance criterion is network-level: a fresh container with no opt-out variable makes no request to modelparams.dev, telemetry.manifest.build, blue.manifest.build, api.github.com or Sentry (DNS/egress log, as in #34).

Review follow-ups (bfe9f1d)

🤖 Generated with Claude Code

patrickbrandao and others added 9 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>
Thinker makes no outbound calls of its own, and Autofix depended on
Manifest's hosted Phoenix service. The dashboard drops both before the
backend modules go in #16 and #17.

- Update check: VersionIndicator now only shows the build-time
  __MANIFEST_VERSION__ badge. It makes no request, shows no "new version"
  link to the manifest.build changelog and has no dismiss state.
  services/api/version.ts and its test are gone.
- Discovery: delete the post-signup questionnaire (page, service, styles,
  route, tests) and the pending-discovery redirects in AuthGuard and
  GuestGuard. First-admin setup now lands on / (Overview), signup on
  /welcome, and social signup also goes to /welcome. A leftover
  manifest_discovery_pending_* key from an older build no longer
  redirects anyone.
- Wingman: delete the dev drawer, wingman-dev-proxy.ts, the
  VITE_WINGMAN_* / __WINGMAN_URL__ / __DEV_MODE__ build constants, the
  App and Welcome mounts, the drawer CSS and the CONTRIBUTING section.
- Autofix: delete AutofixKpiCards, SettingsAutofixSection,
  NotificationBell (it only nagged about inactive Autofix), the Autofix
  switch and Manifest Terms/Privacy line on harness creation (modal and
  Welcome), the autofix badges, trigger filter, Phoenix panels, sibling
  links and status labels in the Requests views, the "Autofixed attempts"
  connection card, the Doctor tooltip variant, the healed chart series,
  the autofix fields in services/api/*, the two public PNGs, and the
  dead CSS.
- The generic reliability metrics that the backend still serves under
  the legacy /overview/autofix-* paths (success rate, fallback
  recoveries, request status over time, per harness/provider/model
  reliability) stay. They are now read through neutral clients
  (getReliabilityStats, getRequestStatusTimeseries, RELIABILITY_PATHS)
  and shown by ReliabilityKpiCards. "Self-healed" becomes "Recovered" and
  counts fallback recoveries only.
- Update the tests, the design-system registry and the inherited dev
  guide.

Closes #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up for #57.

- A harness stored without autofix_enabled inherits the backend default.
  The backend turns Autofix ON when it detects no container (bare-metal
  npm start, dev runs), and the dashboard no longer has a switch to turn
  it off. Until #16 removes Autofix from the backend:
  - createAgent always sends autofix_enabled: false, which covers the
    Connect Harness modal, Welcome and ConnectProvider;
  - duplicateAgent follows the copy with PATCH
    /routing/:agent/autofix {enabled:false}, because the duplicate
    endpoint has no field for it.
  Both live in one commented block in services/api/agents.ts, with
  tests on the request payloads.
- The request-status timeseries client folds a legacy `healed` key into
  `success`. The backend's success rate already counts those requests
  as successes, so the bars still sum to the request total, Success +
  Recovered + Error matches the Success rate card, and Recovered stays
  fallback-only, like its KPI.
- The Requests page rewrites a stale ?trigger= value (for example
  ?trigger=autofix) on load to the filter it actually applies.
- Add AuthGuard tests for /cli/auth, with and without a session.
- Correct the dev guide: the self-hosted default is OFF only in a
  container or with MANIFEST_MODE=selfhosted. Document
  AUTOFIX_GLOBAL_ENABLED=false as the operator workaround for existing
  harnesses.

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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Autofix sent failing requests to Manifest's hosted healer
(autofix.manifest.build) and pinged it at every production boot. Thinker
makes no such call (#16). The local self-heal of v0.6 will build on the
fallback chain on its own terms.

- Delete routing/autofix (AutofixService, healing clients, health probe,
  observation reporter, Phoenix types and vendored contract) and the
  contract:refresh script with its ajv/js-yaml devDependencies.
- Proxy: drop the heal-once branch from the primary and fallback hops and
  the wire-body retry hook from ForwardResult. A failed request now goes
  straight to shouldTriggerFallback, exactly as for an agent that never
  opted in. The primary is always attempt 1, the x-manifest-provider /
  x-manifest-auth-type replay rewrite is gone, and the recorder writes no
  repair columns or auto_fixed rows.
- Analytics: move the generic reliability metrics to
  /api/v1/overview/reliability/{stats,timeseries,per-agent,per-provider,
  per-model} without the Autofix fields; drop the healed disposition, the
  auto_fixed counter, the autofix trigger filter and the connection
  breakdown's Autofix attempts.
- Remove /autofix/status, /autofix/enable-all, the per-harness
  /routing/:agent/autofix endpoints, the autofix_enabled create field, the
  MCP Autofix tools and the CLI `routing autofix` command and `--autofix`.
- Schema: DropAutofixSchema1803400000000 drops the autofix columns, check
  constraint and indexes (lock_timeout, IF EXISTS, full revert).
  1802200000000 now runs its self-hosted no-op path everywhere.
- Shared: drop deriveAutofixStatus, AUTOFIX_STATUSES and auto_fixed.
- Delete the unused doctor-release email.
- Frontend: call the neutral reliability paths and drop the temporary
  autofix_enabled opt-out, the duplicate PATCH and the healed fold.
- Docs: move the PoC spec to docs/referencia/, remove AUTOFIX_* from the
  guide, .env.example and the Docker README.

BREAKING CHANGE: the Autofix API, analytics paths, MCP tools and CLI
command are removed; the reliability analytics moved to
/api/v1/overview/reliability/*.

Closes #16

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thinker makes no outbound call of its own. Remove every upstream
phone-home from the backend and the CLI:

- usage telemetry (telemetry/, InstallIdService, PayloadBuilderService,
  the InstallMetadata entity) and the CLI usage spool
  (packages/cli/src/telemetry.ts);
- the daily update check (version/, GET /api/v1/version, the GitHub
  releases URL);
- the discovery forwarding to blue.manifest.build (discovery/,
  POST /api/v1/discovery/complete);
- Sentry (instrument.ts, sentry/, SentryModule/SentryGlobalFilter,
  the @sentry/nestjs dependency);
- the hosted Wingman origin in CORS and CSP frame-src; frame-src is
  'self' in every mode and WINGMAN_CORS_ORIGINS becomes
  THINKER_CORS_ORIGINS with the same meaning;
- the modelparams.dev fetch on boot and hourly cron; parameter specs
  come only from the bundled modelparams package snapshot.

DropInstallMetadata1803500000000 drops the install_metadata table
(lock_timeout, IF EXISTS, revert recreates it empty).

The removed variables leave docker-compose, the .env examples, the
Docker README, the deploy templates and the developer guide.

A new e2e guard fails if the backend, CLI or shared source names a
removed destination again, or if a production-mode boot of the real
server fetches or resolves anything outside the ADR 0003 catalogs.

BREAKING CHANGE: GET /api/v1/version and POST /api/v1/discovery/complete
are gone, the hosted Wingman origin is no longer allowed by CORS, and
WINGMAN_CORS_ORIGINS is renamed THINKER_CORS_ORIGINS.

Closes #17

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With no --url, MANIFEST_URL or stored login, the CLI targeted
https://app.manifest.build, so a MANIFEST_API_KEY set without a host
went to Manifest's cloud. DEFAULT_URL is now http://localhost:2099, the
default Thinker port, and the invalid-URL hint and README follow.

The backend's 404 hint for /chat/completions told callers to point
their baseURL at https://app.manifest.build/v1; it now refers to this
gateway.

The egress guard now fails on app.manifest.build or
gateway.manifest.build anywhere in the CLI or shared source (tests
included), on a new backend production file naming them, on a CLI
default that is not local, and on a boot request to either host. The
three remaining backend files (Cloud trusted origins, the dashboard
link fallback and the OG-tag placeholder) are pinned for #18, #22 and
#25 and may only shrink.

Refs #17

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

Copy link
Copy Markdown
Owner Author

Revisão do PR #70 (#17: remover phone-home do backend e da CLI)

Diff revisado: git diff origin/feat/16-remove-autofix-backend...d168f73df1b36bdf1450a7dae26840ba456fec6f (stack sobre #66). O código ao redor foi lido no próprio SHA.

Resumo: nada bloqueia. A remoção está completa. O guard de egresso é útil: confirmei por mutação que ele falha no código antigo. CORS e CSP ficaram mais restritos, não mais frouxos. A migration está correta. O PR é risco alto porque a migration apaga uma tabela e porque a CLI muda o destino padrão da chave de API.

Bloqueios

Nenhum.

Should-fix

S1. dashboard-url.ts é, sim, destino de requisição: as imagens dos e-mails. Ver packages/backend/src/common/utils/dashboard-url.ts:2,8 e packages/backend/test/no-phonehome-egress.e2e-spec.ts:31-41.

  • Cenário: um deploy sem BETTER_AUTH_URL (por exemplo docker run direto, ou Fly/Easypanel sem a variável; o compose sempre define) usa getEmailAssetUrl('manifest-logo.png'), que vira https://app.manifest.build/manifest-logo.png. Isso acontece em todo e-mail (verify-email.tsx:24, reset-password.tsx:24, threshold-alert.tsx:59, test-email.tsx:21). O cliente de e-mail de quem recebe busca a imagem no host da Manifest, que fica sabendo da abertura e do IP. O servidor não faz a chamada, mas a instância fica induzindo um beacon de terceiro.
  • Contradição: o corpo do PR e o comentário do guard dizem que "none of them is a request destination".
  • Correção: não é regressão nem destino novo, e o arquivo já está no escopo de Rebranding do backend: textos, e-mails, MCP e atribuição de provedores #22. Faltam duas coisas: (a) corrigir a frase no corpo do PR e no comentário de BACKEND_HOSTED_GATEWAY_LEFTOVERS; (b) registrar em Rebranding do backend: textos, e-mails, MCP e atribuição de provedores #22 que o fallback gera esse beacon. Assim a correção não se perde: derivar de BETTER_AUTH_URL sem fallback remoto, ou embutir o logo (CID/data URI).

Nits

  • N1. O preload não registra "every DNS lookup". Ver packages/backend/test/support/egress-recorder.cjs:38-52. Rodei o preload numa sonda própria:

    • https.get, net.connect(host) e fetch são registrados;
    • dns.resolve4('x.invalid') (c-ares) fez uma consulta DNS real e não deixou registro;
    • net.connect(9, '192.0.2.1') (IP literal) tentou conectar sem registro.

    Hoje nada no backend usa dns.resolve* (só há dns/promises.lookup no CIMD, que o preload cobre). Mesmo assim, ajuste uma de duas coisas: o texto (fica "fetch e dns.lookup"), ou o código, envolvendo dns.resolve*/dns.promises.resolve* e net.Socket.prototype.connect para IPs fora do loopback. A verificação na rede (qa-docker/Baseline do qa-docker: smoke e e2e da imagem v0.1 #34) continua sendo a garantia final.

  • N2. O preload bloqueia o host do banco quando ele não é loopback. Ver egress-recorder.cjs:17,41. Com DATABASE_URL=postgresql://…@postgres:5432/…, como numa rede do compose, o lookup de postgres recebe ENOTFOUND. O servidor filho não sobe e o teste falha com um erro que confunde. Na CI (localhost) isso não acontece. Correção: passar o hostname de DATABASE_URL como permitido, por exemplo EGRESS_ALLOW_HOSTS, e excluí-lo da asserção de DNS.

  • N3. packages/cli/src/config.ts:95: o doc de resolveTarget ainda diz "then the active config host, then Cloud". Deveria ser "then http://localhost:2099".

  • N4. A troca de WINGMAN_CORS_ORIGINS para THINKER_CORS_ORIGINS é silenciosa. Ver packages/backend/src/main.ts:78. Um operador com a variável antiga perde a liberação sem nenhum aviso. Não há obrigação de compatibilidade. Um warn no boot quando WINGMAN_CORS_ORIGINS estiver definida ajudaria, ou pelo menos uma menção nas notas de release.

  • N5. docker/DOCKER_README.md (seção "No telemetry") diz que raw.githubusercontent.com só é usado "for OpenCode Go". No boot de uma instalação limpa, sem provedor OpenCode Go, o documento é buscado mesmo assim (ver a verificação 2 abaixo). Isso é escopo de Catálogos públicos: chave THINKER_CATALOG_SYNC e OpenCode Go só com provedor cadastrado #39 e o aperto do guard já está registrado lá. É só para não esquecer de alinhar o texto quando Catálogos públicos: chave THINKER_CATALOG_SYNC e OpenCode Go só com provedor cadastrado #39 entrar.

  • N6. A CLI deixa ~/.config/manifest/telemetry-id e o spool antigo no disco de quem atualiza. É inofensivo (UUID aleatório). CLI thinker #23 pode apagar esses arquivos ao migrar o diretório de config.

Verificações por foco

  1. Completude. Busquei telemetry|sentry|discovery|update_check|modelparams.dev|MODELPARAMS_API|install_metadata|InstallId|blue.manifest|api.github.com|wingman no repositório, fora de node_modules, dist, CHANGELOG, ADRs e migrations antigas. Só aparecem a seção "No phone-home" do guia e o próprio guard. Nenhum refreshCatalog/scheduledRefresh ficou pendurado, e a CLI não tem mais reportUsage/urlFlagOf. tsc --noEmit passa no backend e na CLI. Os três arquivos fixados no guard:

    • auth.instance.ts só monta a allow-list de hosts e os recursos MCP quando authOrigin é um domínio Cloud. Não faz requisição.
    • og-rewrite.ts só substitui texto.
    • dashboard-url.ts: o servidor não faz requisição, mas o fallback vira destino de imagem em e-mails (S1).
  2. O guard de egresso.

    • Testado em Postgres 16 descartável na porta 55470. As 5 suítes alvo passaram (47 testes, cerca de 22 s no total). O boot do servidor real com o preload ficou saudável em cerca de 9 s. Registros no HEAD: openrouter.ai, models.dev, raw.githubusercontent.com (go.mdx), nenhum DNS remoto, e /api/v1/version respondendo 404.
    • Mutação confirmada. Restaurei packages/backend/src, packages/cli/src e packages/backend/package.json da base feat(backend)!: remove Autofix and the Phoenix healer from the backend #66 e rodei o spec do HEAD: 14 de 16 testes falharam. O boot registrou https://modelparams.dev/api/v1/models.json, e /api/v1/version respondeu 401 em vez de 404. Telemetria e update check são cron e não disparam em boot + 3 s. Quem os pega é a varredura de fonte, então as duas camadas se complementam.
    • Robustez na CI. A suíte é rápida, roda offline (o filho não sai da máquina e o pai só fala com 127.0.0.1), tem prazo de 150 s para ficar saudável, roda com --runInBand (o dropSchema não colide com outras suítes) e mata o filho no afterAll.
    • Mascaramento. O preload só existe no processo filho dessa suíte, e o 503 sintético só afeta os catálogos da ADR 0003 dentro desse boot. Ele não mascara falha de rede em outras suítes nem em produção. As lacunas estão em N1 e N2.
  3. CORS e CSP.

    • Produção: a lista padrão agora é vazia; antes sempre incluía wingman.manifest.build.
    • Dev: saíram as portas do Wingman, a :3002 e a origem hospedada. THINKER_CORS_ORIGINS passa a valer também em dev, mas é só opt-in.
    • Mantidos: credentials: false, maxAge e o PNA restrito à lista.
    • CSP: frame-src 'self' em todo modo, e o frontend não usa iframe.
    • Fluxo do dashboard: o fluxo same-origin continua igual. O handler aceita requisição sem Origin. Em POST same-origin, quando o Origin não está na lista, o pacote cors só omite os cabeçalhos e não bloqueia, como já acontecia antes. Nada ficou mais frouxo.
  4. CLI e mensagens.

    • CLI: DEFAULT_URL agora é http://localhost:2099, e o teste cobre MANIFEST_API_KEY sem host indo só para localhost. Na CLI, manifest.build só sobra em homepage e no link do README (docs, CLI thinker #23).
    • Backend: a dica 404 de /chat/completions ficou correta. Nenhuma mensagem manda o usuário enviar chave para manifest.build. O que resta são links de documentação de erro (MANIFEST_ERRORS_DOCS_BASE), HTTP-Referer: https://manifest.build para o OpenRouter e os e-mails (S1), tudo coberto por Rebranding do backend: textos, e-mails, MCP e atribuição de provedores #22.
  5. Migration 1803500000000-DropInstallMetadata. O down() recria a tabela exatamente como 1775700000000 (colunas, tipos e PK). O e2e passa com tabela populada, up idempotente e revert na ordem do runner, passando pelo Remover Autofix/Phoenix do backend #16 e voltando. O ajuste no e2e do Remover Autofix/Phoenix do backend #16, que reverte o drop desta migration antes, está correto. SET LOCAL só aparece no up, e o runtime usa migrationsTransactionMode: 'each', consistente com Remover Autofix/Phoenix do backend #16.

  6. Dockerfile. Nada em packages/backend/src ou packages/shared/src lê packages/manifest/package.json em runtime. O health não expõe versão (o spec garante isso), e o serverInfo do MCP é o literal 1.0.0 (Rebranding do backend: textos, e-mails, MCP e atribuição de provedores #22). O build stage continua copiando packages/manifest, então o __MANIFEST_VERSION__ do frontend não é afetado.

  7. Lockfile. Saíram só @sentry/* (7 pacotes), @opentelemetry/* (7), import-in-the-middle (com seu es-module-lexer aninhado), require-in-the-middle e module-details-from-path. A única outra mudança é cjs-module-lexer passar a "dev": true, efeito esperado. @opentelemetry/api continua citado só como peer opcional, e nenhum código importa @opentelemetry.

Outras observações

Risco alto

Critérios que se aplicam:

  • Migration que apaga dados: DROP TABLE install_metadata. O down() recria a tabela vazia, e o install id não volta.
  • Manuseio de chave: a CLI muda o destino padrão para onde MANIFEST_API_KEY é enviada, de app.manifest.build para localhost:2099. A mudança deixa tudo mais seguro, mas é mudança de key handling.

CORS e CSP ficaram mais restritos e não entram como critério. Não há arquivo de governança no diff.

O merge fica com o mantenedor. Antes dele, o S1 precisa estar corrigido no PR ou registrado em #22, com o link anotado no PR.

Head revisado: d168f73
Veredito: risco alto (aguardar mantenedor)

@patrickbrandao

Copy link
Copy Markdown
Owner Author

Destino dos achados da revisão (ADR 0002): should-fix (logo dos e-mails servido por app.manifest.build) na #22, com prioridade; N1/N2 na #34; N3/N6 na #23; N4 na #31; N5 já está na #39.

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 and others added 5 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>
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>
The DropAutofixSchema e2e spec opens its own DataSource with dropSchema,
so it now takes its URL from resolveE2eDatabaseUrl() like the other
migration specs instead of falling back to a default localhost database.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@patrickbrandao
patrickbrandao changed the base branch from feat/16-remove-autofix-backend to main October 11, 2026 17:41
patrickbrandao and others added 4 commits October 11, 2026 14:43
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>
…-phonehome-backend

Brings in main (44f1689) through #66's final head so #70 merges cleanly
after #66.

Conflict resolutions:
- packages/backend/package.json, package-lock.json: keep #70's removal of
  @sentry/nestjs and take better-auth 1.7.7 from #5. The lockfile was
  re-checked with npm install --package-lock-only and needed no change.
- test/cors.e2e-spec.ts: keep #70's shared buildApp/buildDevApp/buildProdApp
  without the hosted Wingman origin; buildApp already serves on loopback (#77).
- test/drop-autofix-schema-migration.e2e-spec.ts: keep both the
  DropInstallMetadata revert from #70 and resolveE2eDatabaseUrl from #66.

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

The DropInstallMetadata migration spec and the no-phone-home egress spec
both open a dropSchema connection. Resolve their URL with
resolveE2eDatabaseUrl() from test/e2e-db-guard instead of falling back to
localhost:5432, so neither can wipe a database that is not an explicit,
disposable test database (#67, #69).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main's tree equals 5eef655, already merged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
patrickbrandao added a commit that referenced this pull request Oct 11, 2026
…le-selfhosted-mode

Brings in main (#5, #63, #69, #100, #103, #104, #106, #107 and the SaaS,
billing, Autofix and phone-home removals) through #66 and #70.

Resolutions:
- package-lock.json: keep undici 6.29.0 as a runtime dependency (no
  "dev": true), since ssrf-safe-fetch imports it; the version bump from
  #65 is kept.
- src/mcp/mcp-discovery.spec.ts: keep the single-mode test (canonical
  resource and issuer whatever the Host header) and serve it on loopback
  like the rest of the spec (#77).
- src/common/constants/local-servers.ts and its spec: import
  getContainerHostAlias from common/utils/container-host, since
  detect-self-hosted no longer exists.
- design-system/registry: regenerate (npm run design:registry). The
  Playground header classes from #106 were missing from it, which the
  pre-commit check refuses.

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

Copy link
Copy Markdown
Owner Author

Re-revisão do PR #70 (#17: remover phone-home do backend e da CLI)

Diff revisado: git diff origin/main...bfe9f1da1d83777370fda95893abbae73d5f726c (base b1ba799, que é o squash de #66). O código ao redor foi lido no próprio SHA, num worktree destacado. Head anterior revisado: d168f73.

Resumo: nada bloqueia. O conteúdo é o mesmo que revisei em d168f73, mais a resolução de conflitos e os dois specs que agora passam pelo guard do banco. A CI de testes está verde. O check CodeQL falhou com 3 alertas novos no spec de egresso. É código de teste e não há vulnerabilidade real, mas sem resolver isso a CI não fica verde (S1). A sonda ao Ollama que o QA viu já existia antes e não vem deste PR. Fica como follow-up (F1). O PR continua risco alto, pelos mesmos critérios de antes.

Bloqueios

Nenhum.

Should-fix

S1. CodeQL js/incomplete-sanitization falha em packages/backend/test/no-phonehome-egress.e2e-spec.ts:114,119,124.

  • Cenário: o check "CodeQL" do head termina em failure com "3 new alerts including 3 high severity". O motivo é que host.replace(/[.]/g, '\\.') escapa só o ponto antes de montar a RegExp. Os hosts são constantes do próprio arquivo, então não há exploração possível. Mesmo assim, o gate "CI verde" da ADR 0002 não passa, e se o alerta for dispensado na interface ele some sem deixar rastro no código.
  • Correção: trocar a regex montada por text.includes(host) (filesMatching aceita um predicado), ou usar um escape completo, s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), num helper escapeRegExp. A alternativa é o mantenedor dispensar os 3 alertas como falso positivo em código de teste e registrar isso no PR. A correção no código é melhor.

Follow-up (fora do diff, pré-existente)

F1. Sonda ao Ollama sem provedor configurado: GET /api/v1/setup/status em packages/backend/src/setup/setup.controller.ts:22 e setup.service.ts:79-89.

  • Origem: não é hook de boot. A rota é @Public() e, quando isSelfHosted() é verdadeiro (em Docker sempre é, por causa do /.dockerenv), faz fetch(${OLLAMA_HOST}/api/tags) a cada chamada, com timeout de 3 s. O dashboard chama essa rota no primeiro carregamento (GuestGuard.tsx:16 e Setup.tsx:18, via services/setup-status.ts). Por isso o QA viu a requisição a host.docker.internal:11434 logo depois do boot, ao abrir o login ou o setup. O padrão de OLLAMA_HOST no compose é http://host.docker.internal:11434. O código veio do snapshot upstream (f59ae23), e este PR não mexe nele.
  • Avaliação: não é phone-home no sentido da ADR 0003. O destino é o host ou a LAN do operador, nenhum dado sai e não há terceiro envolvido. Mas a requisição contradiz a letra da regra ("outbound only to providers the operator configured"), porque acontece sem nenhum provedor Ollama conectado. Ela também pode ser disparada sem autenticação, e cada chamada gera uma sonda de até 3 s. Por fim, expõe a quem não está autenticado se há Ollama no host. O campo nem é consumido: checkIsOllamaAvailable() (setup-status.ts:80) não tem chamador no frontend, só mocks em testes.
  • Classificação: follow-up, não bloqueia este PR. Não é regressão nem destino novo na internet, e Remover phone-home do backend e da CLI #17 trata de telemetria, update check, discovery, Sentry e modelparams.dev.
  • Correção sugerida (nova issue, ou anexar à Controlar quem pode conectar runners locais (Ollama, LM Studio, llama.cpp) #102): tirar ollamaAvailable de /setup/status junto com isOllamaAvailable(), já que nada no frontend usa o campo. A sonda fica só nas ações explícitas do operador, que já existem: conectar Ollama (provider.controller.ts:197) e POST ollama/sync (model.controller.ts:119). Valeria também mudar o ramo "sem provedor ativo" de ollama-sync.service.ts:24-41, que só sonda quando chamado por essas ações. Na mesma issue, estender o boot check do spec de egresso para chamar /api/v1/setup/status em modo self-hosted e exigir que nenhum host fora de loopback e da ADR 0003 seja contatado.

Verificações pedidas

  1. SHA e merges.

  2. Equivalência com a revisão anterior.

    • git diff --stat origin/main...bfe9f1d e git diff --stat 7dfb718 d168f73 são idênticos: 73 arquivos, +919/−4760.
    • O interdiff (ignorando index e cabeçalhos de hunk) mostra só quatro diferenças:
    • Nenhuma mudança funcional nova.
  3. Resolução de conflitos.

    • packages/backend/package.json remove só @sentry/nestjs e !instrument.ts, e mantém better-auth: 1.7.7.
    • drop-autofix-schema-migration.e2e-spec.ts importa DropInstallMetadata1803500000000 e resolveE2eDatabaseUrl, e reverte o drop desta migration antes, na ordem do runner.
    • cors.e2e-spec.ts usa os builders deste PR, sem origem do Wingman, e mantém listenOnLoopback.
    • DropInstallMetadata1803500000000 continua sendo a última migration (depois de 1803400000000), também na lista de data-source-definitions.ts.
  4. Phone-home restante no head. Busquei modelparams.dev|MODELPARAMS|api.github.com|sentry|telemetry|update check|version-check|discovery|autofix|phoenix|blue.manifest|wingman em packages/, docker/ e deploy/.

    • Só restam o telemetry: { enabled: false } do Better Auth (que desliga a telemetria), comentários sobre "telemetry rows" (o domínio de uso), "model discovery" (a listagem de modelos dos provedores configurados) e migrations antigas.
    • Não há rota /api/v1/version, discovery/complete nem debug-sentry, e nada no frontend ou na CLI as chama.
    • Também não há referência a install_metadata fora das migrations.
  5. modelparams.dev. O PR remove o acesso: main ainda tem MODELPARAMS_API_DEFAULT_URL = 'https://modelparams.dev/api/v1/models.json' com refresh no boot e por cron. No head, ProviderParamSpecService só lê node_modules/modelparams/dist/generated/data.js como texto e faz JSON.parse do literal CATALOG, sem executar código do pacote. Também conferi que o dist de modelparams@0.0.57 não tem fetch nem https. O host está em FORBIDDEN_DESTINATIONS, e o spec de egresso passou na CI.

  6. Guard de egresso.

    • A allow-list remota é exatamente openrouter.ai, models.dev e raw.githubusercontent.com (no-phonehome-egress.e2e-spec.ts:43-47), além do loopback.
    • DNS remoto precisa ser vazio.
    • Na CI, PASS test/no-phonehome-egress.e2e-spec.ts (8.276 s).
    • Limitação já registrada (N1/N2 na Baseline do qa-docker: smoke e e2e da imagem v0.1 #34): o boot check não chama /setup/status, e em CI o processo não é self-hosted, então ele não pegaria F1.
  7. dropSchema e o guard. Todos os 18 arquivos de test/ que usam dropSchema resolvem a URL por resolveE2eDatabaseUrl(). request-recordings.e2e-spec.ts faz isso indiretamente, por createTestApp de helpers.ts. Nenhum lê process.env.DATABASE_URL diretamente fora de e2e-db-guard.ts. O processo filho do spec de egresso só migra, não faz drop, e recebe a URL já validada.

  8. CI em bfe9f1d (check-runs com --paginate):

    Check Resultado
    Lint success
    Unit (backend): 469 suítes, 8982 testes success
    Unit (cli): 13 suítes, 307 testes success
    Unit (shared): 21 suítes, 525 testes success
    Unit (frontend) success
    Backend e2e (PostgreSQL 16): 68 suítes, 568 testes success
    Compose smoke test, install.sh end-to-end, dependency-review success
    CodeQL (análise) success
    CodeQL (code scanning, alertas do PR) failure (S1)

    A CI não roda typecheck nem build de backend e CLI, então rodei no worktree, no head: npm ci, build do shared, backend tsc --noEmit e nest build, CLI typecheck e build. Tudo passou.

Gates pendentes

Risco alto

Continuam valendo os critérios da revisão anterior:

  • Migration que apaga dados: DROP TABLE install_metadata (1803500000000-DropInstallMetadata.ts). O down() recria a tabela vazia.
  • Manuseio de chave: o destino padrão da CLI para MANIFEST_API_KEY muda de app.manifest.build para http://localhost:2099 (packages/cli/src/config.ts). A mudança deixa tudo mais seguro, mas é mudança de key handling.

O diff não toca arquivo de governança, workflow nem release. O merge fica com o mantenedor.

Head revisado: bfe9f1d
Veredito: risco alto (aguardar mantenedor)

CodeQL flagged js/incomplete-sanitization: only dots were escaped before
building a RegExp from the host constants.

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

Copy link
Copy Markdown
Owner Author

Re-revisão do PR #70: delta bfe9f1d..8aa729a

Diff revisado: git diff bfe9f1da1d83777370fda95893abbae73d5f726c 8aa729a84debf71c71937f1600401aa37e426646, num worktree destacado no novo head (confirmado com gh pr view). O commit 8aa729a tem um único pai, bfe9f1d, e muda só packages/backend/test/no-phonehome-egress.e2e-spec.ts (+8/−3). Fora esse arquivo, o código é o mesmo da revisão anterior: #70 (comment)

Resumo: S1 está corrigido, e o check de code scanning do CodeQL passou. F1 virou a #109, com o link registrado no corpo do PR. Nada bloqueia. O PR continua risco alto, pelos mesmos critérios.

O delta

  • escapeRegExp(text) escapa todos os metacaracteres de RegExp (/[.*+?^${}()|[\]\\]/g → '\\$&'). Os três pontos que o CodeQL apontou (linhas 119, 124 e 129) agora usam esse helper.
  • O comportamento não muda. Para os hosts constantes do arquivo, o padrão gerado é o mesmo de antes. api.github.com/repos/mnfst continua funcionando, porque / não precisa de escape no construtor RegExp. Não sobrou nenhum replace(/[.]/g, ...).
  • tsc --noEmit do backend passa no head. As asserções do guard (allow-list da ADR 0003, DNS remoto vazio, rotas 404) não mudaram.

CI em 8aa729a (check-runs --paginate)

Check Resultado
CodeQL (code scanning): "No new alerts in code changed by this pull request" success
CodeQL (análise) success
Lint success
Unit backend (469 suítes), cli (13 suítes / 307 testes), shared (21 suítes / 525 testes), frontend success
Backend e2e (PostgreSQL 16): 68 suítes / 568 testes, no-phonehome-egress PASS success
Compose smoke test, install.sh end-to-end, dependency-review success

Achados

Gates pendentes

  • qa-docker: falta o resultado registrado para este head. O critério é de rede: um container limpo, sem variável de opt-out, não contata modelparams.dev, telemetry.manifest.build, blue.manifest.build, api.github.com nem Sentry. A sonda ao Ollama é esperada até a Setup: não sondar o Ollama local sem provedor configurado #109 entrar e não reprova este PR.

Risco alto

  • Migration que apaga dados: DROP TABLE install_metadata (1803500000000-DropInstallMetadata.ts). O down() recria a tabela vazia.
  • Manuseio de chave: o destino padrão da CLI para MANIFEST_API_KEY muda para http://localhost:2099 (packages/cli/src/config.ts).

O diff não toca arquivo de governança, workflow nem release. O merge fica com o mantenedor.

Head revisado: 8aa729a
Veredito: risco alto (aguardar mantenedor)

@patrickbrandao

Copy link
Copy Markdown
Owner Author

QA (qa-docker) for #70

Image/stack runs: head bfe9f1da1d83777370fda95893abbae73d5f726c (includes main b1ba799). Backend e2e: re-run on the new head 8aa729a84debf71c71937f1600401aa37e426646 (the only change from bfe9f1d is packages/backend/test/no-phonehome-egress.e2e-spec.ts). Docker Desktop arm64, images thinker:qa-70d-pr (427 MB) and thinker:qa-70d-main (457 MB), Postgres 16.15-alpine (compose digest), distroless nodejs24-debian13 runtime. Every stack used a unique Compose project with name: !reset null volume overrides and label-checked teardown; nothing was left behind.

Step Result
(a) Fresh volume: migrations (incl. DropInstallMetadata1803500000000), health in ~8 s, setup, sign-in, agent + key, /v1/models, chat completions, messages (Bearer and x-api-key), responses, stream and non-stream, via host mock; 60-request loop all 200 pass
(b) Upgrade main -> PR on the same volume: DropInstallMetadata ran, install_metadata gone (a seeded row was dropped with it), 67 messages / agents / users / keys / custom provider identical (md5 of ids), gateway and dashboard work after upgrade pass
(c) Egress, default env, boot + setup + dashboard + 120 s idle (tcpdump in the app netns, DNS names decoded from the pcap) pass, with 2 findings below
(d) Removed endpoints (/api/v1/version, /api/v1/github/*, /api/v1/telemetry*, /api/v1/discovery/complete, /api/v1/debug-sentry, /api/v1/update-check) return 404 (anon and signed in); headless Chromium over 14 dashboard routes: 120-131 API calls all 2xx, 0 external requests, 0 page/console errors pass
(e) CLI: default base URL http://localhost:2099, no manifest.build in src/dist/bin; mnfst --help, config path, auth status, doctor work with the network denied (sandbox-exec); no telemetry files written. Only bin is mnfst (no thinker bin yet) pass
(f) contracts/gateway against the PR container: 6/6. Backend e2e (TZ=UTC npm run test:e2e:docker, 8aa729a): 68 suites, 568 tests, 0 failures pass

Egress per side (DNS names from the pcap, default env)

Side Destinations contacted
main b1ba799 openrouter.ai, models.dev, raw.githubusercontent.com, modelparams.dev (all at boot); no telemetry/update-check call seen within the 120 s window
PR openrouter.ai, models.dev, raw.githubusercontent.com (all at boot); nothing else

Not contacted on either side: telemetry.manifest.build, *.up.railway.app, autofix.manifest.build, api.github.com, sentry, app.manifest.build. The PR no longer resolves modelparams.dev. After boot, the PR's only DNS lookups are postgres and the only non-DB, non-provider traffic is the Ollama probe below. The browser saw no external request on either side.

Findings

  1. raw.githubusercontent.com is fetched at every boot with no OpenCode Go provider configured (OpencodeGoCatalogService.onModuleInit -> list(), packages/backend/src/model-discovery/opencode-go-catalog.service.ts; log line OpenCode Go catalog loaded: 32 models). ADR 0003 allows it only when an OpenCode Go provider exists. Same on main, so it is not a regression of this PR, and it is tracked in Catálogos públicos: chave THINKER_CATALOG_SYNC e OpenCode Go só com provedor cadastrado #39. The destination is within the allowed set, so I do not count it against feat(backend)!: remove phone-home from the backend and the CLI #70.
  2. The app probes host.docker.internal:11434 (resolved to 192.168.65.254:11434, repeated SYNs, never answered here) with no provider configured. Path: GET /api/v1/setup/status -> SetupService.isOllamaAvailable() (packages/backend/src/setup/setup.service.ts), a silent fetch(${OLLAMA_HOST}/api/tags) with a 3 s timeout, hit on each dashboard page load. There is no log line. Not at boot and not while idle. Identical on main and PR; tracked in Setup: não sondar o Ollama local sem provedor configurado #109, not counted against this PR.

Minor: on the first boot after the upgrade, Better Auth logged Could not validate the database schema once (non-fatal; it did not repeat on the next restart, and fresh boots log the usual Database schema mismatch on both main and PR).

@patrickbrandao
patrickbrandao merged commit 90598c0 into main Oct 11, 2026
11 checks passed
@patrickbrandao
patrickbrandao deleted the feat/17-remove-phonehome-backend branch October 11, 2026 18:55
patrickbrandao added a commit that referenced this pull request Oct 11, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
patrickbrandao added a commit that referenced this pull request Oct 11, 2026
main's tree equals 8aa729a (#70), already merged.

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 phone-home do backend e da CLI

2 participants