[docs] close SM-06 checkpoint handoff
This commit is contained in:
502
.hermes/plans/2026-08-15_092534-chatwoot-private-rebrand.md
Normal file
502
.hermes/plans/2026-08-15_092534-chatwoot-private-rebrand.md
Normal file
@@ -0,0 +1,502 @@
|
||||
# Chatwoot Private Rebrand & Zero-Telemetry Implementation Plan
|
||||
|
||||
> **For Hermes:** Execute task-by-task with todo progress tracking and per-task commits. Use TDD for behavior changes. Run `requesting-code-review` before delivery. Do not push, publish, or deploy without explicit user approval.
|
||||
|
||||
**Goal:** สร้าง community-edition fork ที่ไม่มี Chatwoot branding ใน runtime/public product, ไม่ส่ง telemetry หรือ installation/application statistics ไปยัง Chatwoot/third parties และยัง rebase/merge จาก upstream `chatwoot/chatwoot` ได้โดยมี conflict ต่ำ
|
||||
|
||||
**Architecture:** คง core Rails/Vue architecture และ upstream internals ไว้ แต่สร้าง privacy boundary และ brand configuration layer ที่จุดรวม แทนการเปลี่ยนชื่อ internal namespace หลายพันจุดทันที. Network behavior ต้องเป็น allow-by-purpose: outbound ที่ผู้ดูแลตั้งค่าเพื่อใช้งาน channel/integration ยังคงอยู่ ส่วน telemetry, hub sync, analytics, remote changelog, hosted push relay และ script injection ถูกตัดออกจาก build/runtime.
|
||||
|
||||
**Tech Stack:** Ruby on Rails, Sidekiq, PostgreSQL, Redis, Vue 3, Vite, pnpm, RSpec, Vitest, Playwright.
|
||||
|
||||
## Small-model execution package
|
||||
|
||||
This document remains the architecture and acceptance source of truth. Implementation is split into bounded runbooks at [`chatwoot-private/README.md`](chatwoot-private/README.md). The package contains an execution contract plus `SM-*` tasks with dependencies, exact file scope, RED/GREEN commands, stop conditions, commit boundaries, release gates, and upstream-maintenance workflow. Give a small model only one `SM-*` task at a time; the orchestrator must review the exact diff and test evidence before advancing.
|
||||
|
||||
---
|
||||
|
||||
## 0. Audit baseline
|
||||
|
||||
- Repository: `https://github.com/chatwoot/chatwoot.git`
|
||||
- Local path: `/Users/kunthawat/Gitea/Chatwoot`
|
||||
- Audited branch: `develop`
|
||||
- Audited commit: `9a73c1473ffa0ae6a9c7725046b8ca17922dcc83`
|
||||
- Clone state: shallow — fetch full upstream history before the first merge/rebase rehearsal
|
||||
- Application/toolchain baseline: Chatwoot 4.16.2; Ruby 3.4.4; Rails 7.2.3.1; Node 24; pnpm 10.2.0; PostgreSQL 16 + pgvector; Redis
|
||||
- Working tree before report: clean
|
||||
- Audit time: `2026-08-15 09:25:34 +0700`
|
||||
- Root license: MIT (`LICENSE`)
|
||||
- Enterprise subtree: proprietary (`enterprise/LICENSE`) — ห้ามถือว่าใช้งาน/ดัดแปลง/ขายได้ตาม MIT
|
||||
|
||||
### Scope decision
|
||||
|
||||
ฐานผลิตภัณฑ์ควรเป็น **Community Edition เท่านั้น**. หากต้องการ enterprise features ต้องทำ commercial agreement แยก; อย่าแก้ `enterprise/` แล้วนำไปใช้เพียงเพราะ source อยู่ใน repository.
|
||||
|
||||
---
|
||||
|
||||
# Audit findings
|
||||
|
||||
## 1. Architecture map
|
||||
|
||||
| Layer | Source of truth | Runtime role |
|
||||
|---|---|---|
|
||||
| Rails web/API | `app/controllers/`, `app/models/`, `app/services/`, `config/routes.rb` | Dashboard API, widget API, integrations, webhooks |
|
||||
| Background workers | `app/jobs/`, `config/schedule.yml`, Sidekiq | scheduled sync, message processing, notifications |
|
||||
| Dashboard frontend | `app/javascript/dashboard/`, `app/javascript/entrypoints/v3app.js` | Vue dashboard/auth |
|
||||
| Widget/SDK | `app/javascript/widget/`, `app/javascript/sdk/` | customer chat widget and browser SDK |
|
||||
| Shared frontend | `app/javascript/shared/` | branding config, links, common components |
|
||||
| Runtime config | `config/installation_config.yml`, `GlobalConfig`, `DashboardController::GLOBAL_CONFIG_KEYS` | brand URLs/logos, analytics token, behavior flags |
|
||||
| Hub integration | `lib/chatwoot_hub.rb`, `enterprise/lib/enterprise/chatwoot_hub.rb` | installation sync, registration, cloud/plan info |
|
||||
| Build/deploy | `package.json`, `Gemfile`, `Dockerfile`, `docker/`, `Procfile` | Rails/Vite builds and services |
|
||||
| Community tests | `spec/`, frontend tests under JS tree | RSpec/Vitest/Playwright gates |
|
||||
|
||||
## 2. Verdict: มี outbound telemetry จริง
|
||||
|
||||
**คำตอบสั้น:** มี. Production job ส่ง installation configuration และ usage metrics ไป Chatwoot Hub. การตั้ง `DISABLE_TELEMETRY=true` ในโค้ดปัจจุบัน **ไม่ได้หยุด request**; มันตัดเฉพาะ metrics บางส่วน แต่ยัง POST `instance_config` ไป Hub อยู่.
|
||||
|
||||
### 2.1 Chatwoot-controlled outbound inventory
|
||||
|
||||
| Severity | Flow | Destination | Trigger / call path | Payload / data | Current opt-out | Decision |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Critical | Hub installation sync | `CHATWOOT_HUB_URL` default `https://hub.2.chatwoot.com` + `/ping` | `config/schedule.yml` → `Internal::TriggerDailyScheduledItemsJob` → `Internal::CheckNewVersionsJob` → `ChatwootHub.sync_with_hub` | installation identifier, version, installation URL, locale, active accounts/users/inboxes/conversations/messages, account/user/inbox/channel breakdowns | `DISABLE_TELEMETRY` removes `instance_metrics` only; request and `instance_config` remain | **Remove runtime flow entirely** |
|
||||
| Critical | Chatwoot push relay | `https://hub.2.chatwoot.com/send_push` | notification job → `PushNotificationService` → `ChatwootHub.send_push` | installation config, FCM device token, title/body, conversation/navigation payload, platform options and analytics label | `ENABLE_PUSH_RELAY_SERVER` defaults true; bypassed only when own Firebase credentials exist | **Remove relay fallback; require own FCM/VAPID** |
|
||||
| High | cwctl command telemetry | hardcoded `https://hub.2.chatwoot.com/events` | most `deployment/setup_20.04.sh` command branches → `report_event` | installation identifier + invoked cwctl action | no gate or opt-out | **Remove reporter and every callsite** |
|
||||
| High | Signup-domain enrichment | signup email domain via HTTPS + DNS MX lookup | account create → `BrandingEnrichmentJob` → `WebsiteBrandingService` | source IP/request metadata leaks to inferred company domain/DNS; locally stores full email, site branding/social data and inferred mail provider in `brand_info` | always enqueued when email exists | **Remove from privacy baseline or make explicit disabled-by-default admin feature** |
|
||||
| Low | Remote changelog | `https://hub.2.chatwoot.com/changelogs` | cloud-only sidebar mount → `dashboard/api/changelog.js#fetchFromHub` | browser request metadata/IP; no app metrics shown in call | normal self-hosted UI excludes it, but no privacy-wide master switch | **Remove Cloud promotional UI/fetch from private fork** |
|
||||
| Medium | Optional onboarding registration | Hub registration endpoint | `Installation::OnboardingController` when `subscribe_to_updates` checked → `ChatwootHub.register_instance` | company, name, email, installation identifier | user checkbox | **Remove registration/subscription step** |
|
||||
| Medium | Enterprise plan/license sync | same Hub path via enterprise prepend | daily Hub sync | community payload plus plan/license/support config response | coupled to Hub | **Exclude enterprise path unless separately licensed** |
|
||||
|
||||
**Evidence:**
|
||||
- `lib/chatwoot_hub.rb:21-93`
|
||||
- `app/jobs/internal/check_new_versions_job.rb:4-8`
|
||||
- `app/jobs/internal/trigger_daily_scheduled_items_job.rb:4-19`
|
||||
- `config/schedule.yml`
|
||||
- `spec/lib/chatwoot_hub_spec.rb:17-33` explicitly proves `DISABLE_TELEMETRY` still POSTs `instance_config`
|
||||
- `app/services/notification/push_notification_service.rb`
|
||||
- `deployment/setup_20.04.sh:1133-1180,1281-1331`
|
||||
- `app/controllers/api/v1/accounts_controller.rb:24-34,77-86`
|
||||
- `app/jobs/account/branding_enrichment_job.rb`
|
||||
- `app/services/website_branding_service.rb:10-54,117-145`
|
||||
- `app/javascript/shared/constants/links.js:11`
|
||||
- `app/javascript/dashboard/api/changelog.js:11-12`
|
||||
- `app/controllers/installation/onboarding_controller.rb:30-36`
|
||||
|
||||
### 2.2 Third-party analytics/error/observability
|
||||
|
||||
| Surface | Initialization | Default behavior | Data risk | Decision |
|
||||
|---|---|---|---|---|
|
||||
| Amplitude browser analytics | `@amplitude/analytics-browser`, `app/javascript/dashboard/helper/AnalyticsHelper/index.js`, initialized from `v3app.js` | only when `CLOUD_ANALYTICS_TOKEN` is present | event names/properties, user/account identifiers depending helper context | replace helper with local no-op; remove SDK/token exposure |
|
||||
| Frontend Sentry | `v3app.js` dynamic Sentry init from `window.errorLoggingConfig` | depends on configured DSN | browser errors, URL, user context/breadcrumbs | remove remote init; preserve local console/log handling |
|
||||
| Backend Sentry | initializer + `ChatwootExceptionTracker` Sentry branch | depends on DSN | exceptions, request/job context | keep the existing tracker API as a local-logging facade; remove its Sentry branch and Sentry dependency/config |
|
||||
| New Relic / Datadog / Scout / Elastic APM | optional gems/initializers/env configs | configuration-dependent | traces, request metadata, DB/job context | exclude from privacy build; document unsupported remote APM |
|
||||
| Arbitrary dashboard scripts | `DASHBOARD_SCRIPTS` loaded by `DashboardController#set_dashboard_scripts` and injected in layout | blank unless configured | any script can exfiltrate dashboard data | remove runtime injection capability |
|
||||
| Marketing tracking config | `MARKETING_CONVERSION_TRACKING_CONFIG` | blank unless configured | conversion/browser tracking | remove from config and layouts |
|
||||
|
||||
### 2.3 Outbound that is not telemetry
|
||||
|
||||
อย่าลบ network ทั้งหมดแบบเหมารวม. Chatwoot เป็น omnichannel app; ฟีเจอร์หลักต้องติดต่อภายนอกเมื่อ admin เปิดใช้:
|
||||
|
||||
- Meta/Facebook/Instagram/WhatsApp APIs
|
||||
- Twilio/Telegram/Line/TikTok and channel providers
|
||||
- SMTP/IMAP/email delivery
|
||||
- Object storage (S3-compatible)
|
||||
- user-configured webhooks
|
||||
- OAuth/OIDC/SAML providers
|
||||
- AI/LLM providers when explicitly configured
|
||||
- CAPTCHA, maps, translation and other optional integrations
|
||||
|
||||
สิ่งเหล่านี้ควรเป็น **administrator-initiated, documented, credential-gated** ไม่ใช่ startup/daily telemetry. Verification ต้องแยก “unexpected egress” ออกจาก “feature-requested egress”.
|
||||
|
||||
### 2.4 Additional privacy-sensitive hooks
|
||||
|
||||
- `ACCOUNT_SECURITY_NOTIFICATION_WEBHOOK_URL`: optional webhook; can send security events externally.
|
||||
- `INSTALLATION_EVENTS_WEBHOOK_URL`: installation lifecycle webhook, especially cloud/enterprise paths.
|
||||
- `DASHBOARD_SCRIPTS`: remote/arbitrary JS injection.
|
||||
- `MARKETING_CONVERSION_TRACKING_CONFIG`: browser conversion tracking.
|
||||
- `SuperAdmin::SettingsController#refresh`: manual synchronous path into the same Hub sync, separate from the daily scheduler.
|
||||
- Installer/cwctl updater: fetches `raw.githubusercontent.com/chatwoot/chatwoot/master/VERSION_CWCTL` and `get.chatwoot.app/linux/install.sh`; this is update traffic rather than telemetry, but a fully offline fork must replace or remove it and pin/verify any downloaded installer.
|
||||
- Onboarding newsletter consent is rendered **checked by default** in `app/views/installation/onboarding/index.html.erb:62-66`.
|
||||
|
||||
Privacy edition should remove these from ordinary community runtime unless there is an explicit, audited first-party destination and admin consent.
|
||||
|
||||
---
|
||||
|
||||
## 3. Branding inventory
|
||||
|
||||
Branding is not one folder. It is spread across configuration, templates, runtime links, copy, assets, manifests, package names, technical APIs and tests.
|
||||
|
||||
### 3.1 Central runtime brand configuration
|
||||
|
||||
Primary file: `config/installation_config.yml`
|
||||
|
||||
High-value keys:
|
||||
- `INSTALLATION_NAME`
|
||||
- `BRAND_NAME`
|
||||
- `BRAND_URL`
|
||||
- `WIDGET_BRAND_URL`
|
||||
- `LOGO`
|
||||
- `LOGO_DARK`
|
||||
- `LOGO_THUMBNAIL`
|
||||
- `API_CHANNEL_NAME`
|
||||
- `API_CHANNEL_THUMBNAIL`
|
||||
- `TERMS_URL`
|
||||
- `PRIVACY_URL`
|
||||
- `DISPLAY_MANIFEST`
|
||||
- `CLOUD_ANALYTICS_TOKEN`
|
||||
- `CHATWOOT_INBOX_TOKEN`
|
||||
|
||||
Exposure path:
|
||||
`config/installation_config.yml` → `GlobalConfig` → `DashboardController::GLOBAL_CONFIG_KEYS` → `app/views/layouts/vueapp.html.erb` → `window.globalConfig` → Vue store/components.
|
||||
|
||||
Change approach:
|
||||
1. replace defaults with new product values/assets;
|
||||
2. remove telemetry/script keys from public global config;
|
||||
3. make missing brand assets fail build/test instead of falling back to Chatwoot URLs.
|
||||
|
||||
### 3.2 Visible frontend sources
|
||||
|
||||
| Category | Important sources | Change approach |
|
||||
|---|---|---|
|
||||
| Dashboard layout/title/favicon | `app/views/layouts/vueapp.html.erb`, `app/views/layouts/application.html.erb` | drive title/meta/icons from first-party config and local assets |
|
||||
| Shared branding component | `app/javascript/shared/components/Branding.vue` | render own mark; remove Chatwoot fallback/powered-by URL |
|
||||
| Hardcoded links | `app/javascript/shared/constants/links.js` | replace docs/help/changelog links; delete Hub changelog URL |
|
||||
| Dashboard copy | `app/javascript/dashboard/i18n/locale/**` | convert product proper noun to interpolation/brand token; do not manually fork every translation where avoidable |
|
||||
| Widget copy/powered-by | `app/javascript/widget/**`, widget locale files | remove powered-by row and Chatwoot URLs; replace bot/default image |
|
||||
| Email/views | `app/views/**`, `app/mailers/**` | use brand config helpers and local URLs; remove Chatwoot support/docs links |
|
||||
| Installation/onboarding | installation controller/views/frontend | remove update subscription and Chatwoot-specific onboarding text |
|
||||
| Help center/portal | portal views/components/locales | replace visible name, favicon, metadata and link targets |
|
||||
|
||||
### 3.3 Assets and public metadata
|
||||
|
||||
Audit/replace at source:
|
||||
- `public/manifest.json`
|
||||
- `public/favicon*`
|
||||
- `public/apple-touch-icon*`
|
||||
- `public/assets/images/chatwoot_bot.png`
|
||||
- logo files under `public/` and `app/javascript/**/assets/`
|
||||
- mail/logo images and social/Open Graph images
|
||||
- PWA icons and browser metadata
|
||||
- screenshots/seed/demo content that ships into production
|
||||
|
||||
Do not edit generated Vite bundles. Replace the source asset then verify `public/` and built output.
|
||||
|
||||
### 3.4 Brand strings and URLs
|
||||
|
||||
Search gates must include at least:
|
||||
|
||||
```text
|
||||
Chatwoot
|
||||
chatwoot
|
||||
CHATWOOT
|
||||
chatwoot.com
|
||||
chwt.app
|
||||
hub.2.chatwoot.com
|
||||
@chatwoot/
|
||||
window.$chatwoot
|
||||
window.chatwootSettings
|
||||
window.chatwootSDK
|
||||
```
|
||||
|
||||
The English dashboard locale alone has hundreds of Chatwoot references; other locales multiply this surface. Use an automated inventory with allowlist categories, not a one-time manual grep.
|
||||
|
||||
### 3.5 Important pushback: do not rename every internal identifier in Phase 1
|
||||
|
||||
“ไม่มีแบรนด์ในผลิตภัณฑ์” และ “ไม่มีคำว่า Chatwoot ใน source” เป็นคนละงาน.
|
||||
|
||||
Phase 1 acceptance should require:
|
||||
- no visible Chatwoot text/logo/link in runtime;
|
||||
- no Chatwoot-controlled telemetry/relay/changelog calls;
|
||||
- no Chatwoot branding in public metadata, email, widget and built artifacts.
|
||||
|
||||
Keep these internal compatibility surfaces temporarily:
|
||||
- Ruby modules/classes such as `ChatwootHub`/`ChatwootApp` until their behavior is neutralized;
|
||||
- npm workspace/package imports such as `@chatwoot/utils`;
|
||||
- public widget JS APIs such as `window.$chatwoot` and `window.chatwootSettings`, with deprecated aliases if a new API is introduced.
|
||||
|
||||
Renaming all internals immediately creates a permanent merge-conflict factory and breaks existing customer embed code. Do internal namespace migration only as a separate, measured phase after upstream sync is stable.
|
||||
|
||||
---
|
||||
|
||||
# Implementation plan
|
||||
|
||||
## Phase 1 — Fork topology and legal boundary
|
||||
|
||||
### Task 1: Establish remotes and branch policy
|
||||
|
||||
**Objective:** Make upstream syncing explicit without publishing anything yet.
|
||||
|
||||
**Git topology:**
|
||||
- `upstream` → `https://github.com/chatwoot/chatwoot.git`
|
||||
- `origin` → future private Gitea repository (user approval required before creation/push)
|
||||
- immutable base tag: `upstream-audit-2026-08-15`
|
||||
- working branches: `privacy/*`, `brand/*`, `ux/*`
|
||||
|
||||
**Steps:**
|
||||
1. Convert the shallow checkout into a full-history clone (`git fetch --unshallow` or an equivalent full fetch) and verify the audited commit remains reachable.
|
||||
2. Add/rename remotes only after private Gitea repository name is approved.
|
||||
3. Create `privacy/zero-egress-baseline` from audited commit.
|
||||
4. Commit by concern; never mix telemetry, branding and UX in one commit.
|
||||
5. Keep a machine-readable upstream patch ledger in `docs/upstream-patches.yml`.
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
git remote -v
|
||||
git rev-parse --is-shallow-repository
|
||||
git merge-base --is-ancestor upstream/develop HEAD
|
||||
git status --short
|
||||
```
|
||||
|
||||
### Task 2: Enforce Community Edition boundary
|
||||
|
||||
**Objective:** Prevent accidental reliance on proprietary enterprise code.
|
||||
|
||||
**Files likely affected:** build/deploy docs, CI configuration, feature flags; do not alter `enterprise/LICENSE`.
|
||||
|
||||
**Tests:** CI boot/build with community-only configuration; assert enterprise-only routes/features are absent or disabled.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Create privacy egress boundary
|
||||
|
||||
### Task 3: Add a centralized outbound-purpose policy
|
||||
|
||||
**Objective:** Every application-originated outbound path declares a purpose.
|
||||
|
||||
**Create:**
|
||||
- `app/services/privacy/outbound_policy.rb`
|
||||
- `spec/services/privacy/outbound_policy_spec.rb`
|
||||
|
||||
**Policy classes:**
|
||||
- allowed only after admin configuration: channel, email, storage, webhook, OAuth, AI provider;
|
||||
- denied unconditionally: telemetry, vendor hub sync, remote changelog, hosted push relay, marketing tracking, remote APM.
|
||||
|
||||
Do not monkey-patch `Net::HTTP` globally in production. Use the policy at first-party service boundaries and an egress test proxy/firewall in CI.
|
||||
|
||||
### Task 4: Remove Chatwoot Hub sync and registration
|
||||
|
||||
**Modify:**
|
||||
- `app/jobs/internal/check_new_versions_job.rb`
|
||||
- `app/jobs/internal/trigger_daily_scheduled_items_job.rb`
|
||||
- `lib/chatwoot_hub.rb`
|
||||
- `app/controllers/super_admin/settings_controller.rb`
|
||||
- `app/controllers/installation/onboarding_controller.rb`
|
||||
- related routes/views/specs
|
||||
|
||||
**Behavior:**
|
||||
- no `/ping`, registration or Hub version request;
|
||||
- version remains local from `Chatwoot.config[:version]`/build metadata;
|
||||
- onboarding has no update-subscription payload;
|
||||
- no environment flag can silently re-enable Hub communication.
|
||||
|
||||
**Tests first:**
|
||||
- production daily job does not invoke any HTTP client;
|
||||
- onboarding completion never calls Hub;
|
||||
- repository scan rejects `hub.2.chatwoot.com` in runtime source.
|
||||
|
||||
### Task 5: Remove hosted push relay
|
||||
|
||||
**Modify:**
|
||||
- `app/services/notification/push_notification_service.rb`
|
||||
- `app/services/notification/push_test_service.rb`
|
||||
- installation config/env docs/specs
|
||||
|
||||
**Behavior:** push works only with explicitly configured first-party FCM/VAPID credentials. Missing credentials produce clear local log/admin warning, not relay fallback.
|
||||
|
||||
**Tests first:** assert the relay path never calls `ChatwootHub.send_push` or `/send_push`; direct administrator-configured Firebase/Web Push paths still pass.
|
||||
|
||||
### Task 6: Remove remote changelog
|
||||
|
||||
**Modify:**
|
||||
- `app/javascript/dashboard/api/changelog.js`
|
||||
- changelog store/components/routes
|
||||
- `app/javascript/shared/constants/links.js`
|
||||
|
||||
Either delete the feature or serve release notes from local static content controlled by this fork. No browser request to Chatwoot Hub.
|
||||
|
||||
### Task 6A: Remove cwctl telemetry and vendor updater dependencies
|
||||
|
||||
**Modify:** `deployment/setup_20.04.sh` and deployment tests/docs.
|
||||
|
||||
Delete `report_event`, its hardcoded Hub URL, installation-identifier lookup used by reporting, and every `report_event` call. For a fully private installer, replace the Chatwoot-hosted self-updater with fork-owned, checksum/signature-verified release artifacts or disable self-update entirely.
|
||||
|
||||
**Tests:** shell static check rejects Hub/get.chatwoot.app telemetry paths; invoke each cwctl command under a recording proxy and assert no reporting request.
|
||||
|
||||
### Task 6B: Remove automatic signup-domain enrichment
|
||||
|
||||
**Modify:**
|
||||
- `app/controllers/api/v1/accounts_controller.rb`
|
||||
- `app/jobs/account/branding_enrichment_job.rb`
|
||||
- `app/services/website_branding_service.rb`
|
||||
- `app/models/account.rb`
|
||||
- onboarding enrichment frontend/state and related specs
|
||||
|
||||
For the strict privacy baseline, do not infer a company domain from signup email, fetch its website, issue an MX lookup, store the full email in `brand_info`, or block onboarding on enrichment. If retained later, it must be a disabled-by-default administrator feature with disclosure, SSRF/DNS-rebinding tests, data minimization and retention/deletion controls.
|
||||
|
||||
**Tests first:** creating an account performs no HTTP/DNS enrichment and stores no derived `brand_info`; onboarding proceeds without an enrichment state.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Remove third-party telemetry and script injection
|
||||
|
||||
### Task 7: Convert frontend analytics adapter to no-op
|
||||
|
||||
**Modify:**
|
||||
- `app/javascript/dashboard/helper/AnalyticsHelper/index.js`
|
||||
- `app/javascript/entrypoints/v3app.js`
|
||||
- `package.json` and lockfile
|
||||
- `DashboardController::GLOBAL_CONFIG_KEYS`
|
||||
- `config/installation_config.yml`
|
||||
|
||||
Keep existing `AnalyticsHelper.track(...)` callsites initially to reduce upstream conflicts, but make the adapter side-effect-free and remove Amplitude SDK/token initialization.
|
||||
|
||||
**Tests:** spy on `fetch`, XHR and `sendBeacon`; dashboard actions produce zero analytics requests.
|
||||
|
||||
**Checkpoint status (2026-08-16):** Verified at `ced77af`. The exact committed range `a96b977..ced77af` adds the missing `.slice(*GLOBAL_CONFIG_KEYS)` before `app_config` merge, preventing stale/unrequested configuration values from reaching `window.globalConfig`. Fresh pre-commit and post-commit reviews passed with empty security and logic blocking arrays. Rails RSpec/RuboCop remain blocked by the local Ruby/Bundler mismatch.
|
||||
|
||||
### Task 8: Replace Sentry with local error tracking facade
|
||||
|
||||
**Create:** local `ErrorTracker` interfaces for Rails and frontend.
|
||||
|
||||
**Migrate in bounded commits:** initializer/config → service/job callsites → JS callsites → dependencies.
|
||||
|
||||
**Behavior:** structured local logs with secret/PII redaction; no remote DSN, envelope or SDK transport.
|
||||
|
||||
**Tests:** captured exception becomes local log event; network transport remains zero.
|
||||
|
||||
### Task 9: Remove remote APM and injected tracking scripts
|
||||
|
||||
**Modify:** Gemfile/package/config/initializers/layouts and runtime config for New Relic, Datadog, Scout, Elastic APM, `DASHBOARD_SCRIPTS`, and `MARKETING_CONVERSION_TRACKING_CONFIG`.
|
||||
|
||||
**Tests:** production boot with typical environment cannot initialize those agents; CSP/build scan sees no injected tracking script.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Centralized rebrand
|
||||
|
||||
### Task 10: Add a single brand manifest
|
||||
|
||||
**Create:** `config/brand.yml` (or equivalent typed config) with placeholders pending product name/assets:
|
||||
- product name/short name
|
||||
- canonical URL/support URL/docs URL
|
||||
- logos light/dark/thumbnail
|
||||
- favicon/PWA icons
|
||||
- email sender display name
|
||||
- widget powered-by policy
|
||||
|
||||
Runtime must fail validation if a required asset still points to Chatwoot-controlled domains.
|
||||
|
||||
### Task 11: Replace visible shell and metadata
|
||||
|
||||
**Modify:** layouts, manifest, favicons, logos, OG assets, shared `Branding.vue`, emails and widget assets.
|
||||
|
||||
**Verification viewports:** desktop plus mobile `320×568` and `500×768`; inspect dashboard login, onboarding, inbox, widget, help center and emails.
|
||||
|
||||
### Task 12: Migrate visible copy to brand interpolation
|
||||
|
||||
**Modify:** locale source files and view strings.
|
||||
|
||||
Do not blind replace technical API names. Replace customer-visible proper nouns and URLs using a locale-safe interpolation variable. Add a script that categorizes remaining hits as:
|
||||
- allowed legal/upstream attribution;
|
||||
- internal compatibility identifier;
|
||||
- test fixture;
|
||||
- **failure: customer-visible or runtime outbound**.
|
||||
|
||||
### Task 13: Preserve widget API compatibility
|
||||
|
||||
If a new public SDK namespace is required, add it while keeping deprecated aliases for `window.$chatwoot`, `window.chatwootSettings`, and related embed APIs for at least one migration window. Document that these are compatibility identifiers, not visible branding.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — Verification gates
|
||||
|
||||
### Task 14: Static zero-brand/zero-vendor scan
|
||||
|
||||
**Create:** `script/privacy_audit` and CI job.
|
||||
|
||||
Scan source and built artifacts for forbidden domains/strings. Allowlist only license, upstream attribution, changelog documentation and internal compatibility identifiers. Fail on visible HTML/JS/CSS/email/manifest matches.
|
||||
|
||||
### Task 15: Dynamic egress test
|
||||
|
||||
Run Rails, Sidekiq, dashboard and widget behind a recording proxy or container firewall with DNS/network logs.
|
||||
|
||||
Exercise:
|
||||
- fresh install/onboarding;
|
||||
- account signup while recording both HTTP and DNS (must not probe the signup email domain);
|
||||
- login/dashboard navigation;
|
||||
- manual super-admin instance-status refresh;
|
||||
- daily scheduled jobs;
|
||||
- representative cwctl commands and installer/update checks;
|
||||
- create account/inbox/contact/conversation/message;
|
||||
- widget session;
|
||||
- exception paths;
|
||||
- push test without own credentials.
|
||||
|
||||
Expected unsolicited destinations: **none**. Then separately enable one integration at a time and confirm only its documented destinations.
|
||||
|
||||
### Task 16: Regression and visual QA
|
||||
|
||||
Run existing baseline before code changes, then after each phase:
|
||||
|
||||
```bash
|
||||
bundle exec rspec <target specs>
|
||||
pnpm test
|
||||
pnpm lint
|
||||
pnpm build
|
||||
```
|
||||
|
||||
Final:
|
||||
- targeted RSpec + full feasible suite;
|
||||
- frontend unit/integration tests;
|
||||
- Playwright E2E;
|
||||
- production asset build;
|
||||
- browser network capture;
|
||||
- screenshot comparison at desktop, `320×568`, `500×768`.
|
||||
|
||||
### Task 17: Independent review and release gate
|
||||
|
||||
Run `requesting-code-review` pipeline:
|
||||
1. static security/privacy scan;
|
||||
2. tests/lint compared with baseline;
|
||||
3. self-review for validation/error handling/debug leftovers;
|
||||
4. independent reviewer subagent;
|
||||
5. max two auto-fix cycles and re-verification.
|
||||
|
||||
No push/deploy/publication without explicit user approval.
|
||||
|
||||
---
|
||||
|
||||
# Upstream maintenance strategy
|
||||
|
||||
1. Keep `upstream/develop` untouched.
|
||||
2. Rebase/sync monthly or per upstream release, not continuously.
|
||||
3. Maintain small commits in this order: policy → Hub removal → relay removal → analytics → error tracking → brand config → visible assets/copy → UX.
|
||||
4. Prefer no-op adapters and central configuration over deleting hundreds of callsites; this reduces recurring conflicts.
|
||||
5. Add privacy tests that fail immediately when upstream reintroduces a vendor domain, analytics SDK or scheduled ping.
|
||||
6. For every upstream sync, review diffs to:
|
||||
- `lib/chatwoot_hub.rb`
|
||||
- `config/schedule.yml`
|
||||
- `config/installation_config.yml`
|
||||
- `app/javascript/entrypoints/`
|
||||
- analytics/error helpers
|
||||
- push notification services
|
||||
- `deployment/setup_20.04.sh`
|
||||
- account signup branding-enrichment job/service
|
||||
- layouts/manifest/assets
|
||||
- package/Gem dependencies.
|
||||
|
||||
# Risks and decisions still needed
|
||||
|
||||
1. **Product identity:** final name, domains, logos, support/docs URLs and email sender name are not supplied; implementation can build the layer but cannot finish assets/copy truthfully.
|
||||
2. **Widget compatibility:** recommended to keep old JS namespace aliases temporarily. Renaming immediately breaks customer embeds.
|
||||
3. **Error observability:** strict local-only logs reduce central incident visibility. If later needed, support a user-operated self-hosted collector behind explicit admin configuration—not a vendor default.
|
||||
4. **Push notifications:** removing Chatwoot relay means deployment must supply own FCM/VAPID setup or accept no mobile push.
|
||||
5. **Enterprise features:** require separate legal/commercial decision; excluded from this plan.
|
||||
6. **Trademark/support wording:** public site may truthfully state “compatible with / based on Chatwoot community edition” in legal attribution, but must not imply official Chatwoot affiliation. Product UI should remain independently branded.
|
||||
|
||||
# Definition of done
|
||||
|
||||
- No unsolicited HTTP/DNS connection to Chatwoot-controlled or telemetry/observability domains during the dynamic egress suite.
|
||||
- `DISABLE_TELEMETRY` is unnecessary because telemetry code path is absent, not merely disabled.
|
||||
- No customer-visible Chatwoot name, logo, URL, powered-by text, favicon, email branding or manifest metadata.
|
||||
- Required feature integrations work only after explicit admin configuration and are documented by purpose/destination/data class.
|
||||
- Community build, tests and E2E pass; baseline regressions are documented honestly.
|
||||
- Upstream merge rehearsal succeeds and privacy scan catches a deliberately injected forbidden domain.
|
||||
80
.hermes/plans/chatwoot-private/06-remove-amplitude.md
Normal file
80
.hermes/plans/chatwoot-private/06-remove-amplitude.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Runbook 06 — Remove Amplitude and Product Analytics
|
||||
|
||||
**Goal:** dashboard emits no user/account/page/event data to Amplitude or another analytics provider. Existing callers may keep a stable no-op adapter to reduce upstream conflicts.
|
||||
|
||||
## SM-06.01 — Convert analytics specs to a no-egress contract
|
||||
|
||||
**Modify:** `app/javascript/dashboard/helper/AnalyticsHelper/specs/helper.spec.js`
|
||||
|
||||
Replace provider-behavior assertions with:
|
||||
- importing helper does not import/initialize Amplitude;
|
||||
- `init`, `identify`, `track`, and `page` return without network/provider calls;
|
||||
- methods accept existing signatures so callsites need no bulk rewrite;
|
||||
- passing a token cannot enable analytics.
|
||||
|
||||
Do not delete all tests. The no-op contract is the regression guard.
|
||||
|
||||
RED:
|
||||
```bash
|
||||
pnpm test app/javascript/dashboard/helper/AnalyticsHelper/specs/helper.spec.js
|
||||
```
|
||||
|
||||
## SM-06.02 — Make AnalyticsHelper provider-free no-op
|
||||
|
||||
**Modify:** `app/javascript/dashboard/helper/AnalyticsHelper/index.js`
|
||||
|
||||
Rules:
|
||||
- remove `@amplitude/analytics-browser` import;
|
||||
- retain exported class and default object for compatibility;
|
||||
- no dynamic import, fetch, beacon, image pixel, local queue, user/email/account serialization;
|
||||
- each public method is an explicit no-op.
|
||||
|
||||
GREEN:
|
||||
```bash
|
||||
pnpm test app/javascript/dashboard/helper/AnalyticsHelper/specs/helper.spec.js
|
||||
pnpm eslint app/javascript/dashboard/helper/AnalyticsHelper/index.js app/javascript/dashboard/helper/AnalyticsHelper/specs/helper.spec.js
|
||||
```
|
||||
|
||||
## SM-06.03 — Remove dashboard token delivery
|
||||
|
||||
**Modify:**
|
||||
- `app/controllers/dashboard_controller.rb`
|
||||
- `app/views/layouts/vueapp.html.erb`
|
||||
- `spec/controllers/dashboard_controller_spec.rb`
|
||||
|
||||
Remove `CLOUD_ANALYTICS_TOKEN` from `GLOBAL_CONFIG_KEYS` and remove `window.analyticsConfig` serialization. Add response assertion that the rendered dashboard contains neither `analyticsConfig` nor configured test token.
|
||||
|
||||
Do not alter other global config keys.
|
||||
|
||||
Run:
|
||||
```bash
|
||||
bundle exec rspec spec/controllers/dashboard_controller_spec.rb
|
||||
bundle exec rubocop app/controllers/dashboard_controller.rb spec/controllers/dashboard_controller_spec.rb
|
||||
```
|
||||
|
||||
## SM-06.04 — Remove SDK and installation config
|
||||
|
||||
**Modify:**
|
||||
- `package.json`
|
||||
- `pnpm-lock.yaml` via `pnpm remove @amplitude/analytics-browser`
|
||||
- `config/installation_config.yml`
|
||||
|
||||
Remove the `CLOUD_ANALYTICS_TOKEN` entry. Do not hand-edit unrelated lockfile entries.
|
||||
|
||||
GREEN:
|
||||
```bash
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm test app/javascript/dashboard/helper/AnalyticsHelper/specs/helper.spec.js
|
||||
rg -n '@amplitude/analytics-browser|CLOUD_ANALYTICS_TOKEN|analyticsConfig' app config package.json pnpm-lock.yaml --glob '!enterprise/**'
|
||||
git diff --check
|
||||
```
|
||||
Expected runtime/config matches: zero; compatibility helper filenames may remain.
|
||||
|
||||
## Verified checkpoint status — 2026-08-16
|
||||
|
||||
- `a96b977` removed the Amplitude SDK, analytics token delivery, and `window.analyticsConfig`, but its controller regression exposed that an unrelated `CLOUD_ANALYTICS_TOKEN` returned by a config stub/source could still be serialized through `window.globalConfig`.
|
||||
- `ced77af` fixes the root cause by applying `.slice(*GLOBAL_CONFIG_KEYS)` before merging explicit `app_config` values.
|
||||
- Fresh pre-commit review `deleg_f9644984` and exact post-commit review `deleg_9b0a2f63` both passed with `security_concerns=[]` and `logic_errors=[]`.
|
||||
- Ruby syntax, `git diff --check`, and the added-line security scan passed. Rails RSpec/RuboCop are blocked until the pinned Ruby 3.4.4/Bundler 2.5.16 toolchain is available.
|
||||
|
||||
Checkpoint commit: `privacy: disable product analytics by construction`
|
||||
66
.hermes/plans/chatwoot-private/README.md
Normal file
66
.hermes/plans/chatwoot-private/README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Chatwoot Private Fork — Small-Model Runbook Index
|
||||
|
||||
> ใช้ไฟล์นี้เป็นสารบัญเท่านั้น ห้ามส่ง runbooks ทั้งหมดให้โมเดลเล็กในครั้งเดียว
|
||||
|
||||
## Baseline
|
||||
|
||||
- Repository: `/Users/kunthawat/Gitea/Chatwoot`
|
||||
- Branch/commit ที่ audit: `develop` / `9a73c1473ffa0ae6a9c7725046b8ca17922dcc83`
|
||||
- Clone เป็น shallow; การ fetch history เป็นงานแยกที่ต้องมี network และต้องไม่ปนกับ code changes
|
||||
- Master plan: `../2026-08-15_092534-chatwoot-private-rebrand.md`
|
||||
|
||||
## วิธีใช้
|
||||
|
||||
1. Orchestrator อ่าน `00-execution-contract.md` และ runbook ที่จะทำ
|
||||
2. ส่งให้โมเดลเล็ก **ครั้งละหนึ่ง `SM-*` task** พร้อม current `git status --short`
|
||||
3. โมเดลเล็กแก้เฉพาะ `allowed_files`
|
||||
4. รันคำสั่ง RED/GREEN ตาม task
|
||||
5. Orchestrator ตรวจ diff และผล test ก่อนส่ง task ถัดไป
|
||||
6. Commit เฉพาะ checkpoint ที่ runbook ระบุ; ห้าม push/deploy
|
||||
7. หาก command/path ไม่ตรงกับ tree ปัจจุบัน ให้หยุดและคืน `BLOCKED` ห้ามเดา
|
||||
|
||||
## Dependency order
|
||||
|
||||
| Order | Runbook | Gate |
|
||||
|---:|---|---|
|
||||
| 0 | `00-execution-contract.md` | ต้องแนบทุกครั้ง |
|
||||
| 1 | `01-baseline-and-privacy-tests.md` | baseline evidence |
|
||||
| 2 | `02-remove-hub-sync.md` | Hub `/ping`, registration, events = 0 |
|
||||
| 3 | `03-remove-push-relay.md` | direct push ผ่าน; Hub relay ไม่มี |
|
||||
| 4 | `04-remove-cwctl-telemetry.md` | cwctl ไม่ส่ง Hub event |
|
||||
| 5 | `05-remove-signup-enrichment.md` | signup ไม่มี HTTP/DNS enrichment |
|
||||
| 6 | `06-remove-amplitude.md` | analytics adapter no-op; SDK removed |
|
||||
| 7 | `07-remove-sentry-and-apm.md` | local errors only; no remote SDK/agent |
|
||||
| 8 | `08-remove-script-injection.md` | dashboard cannot inject tracking script |
|
||||
| 9 | `09-brand-config-foundation.md` | ต้องมี brand inputs ก่อน GREEN |
|
||||
| 10 | `10-visible-rebrand-and-compatibility.md` | no visible Chatwoot brand; aliases retained |
|
||||
| 11 | `11-egress-e2e-and-release-gate.md` | dynamic egress + full review |
|
||||
| 12 | `12-upstream-maintenance.md` | full history + merge rehearsal + anti-regression |
|
||||
|
||||
## Checkpoint status (2026-08-16)
|
||||
|
||||
- SM-06 `06-remove-amplitude.md`: verified checkpoint `ced77af`; exact post-commit review `deleg_9b0a2f63` passed. The dashboard config path now allowlists `GLOBAL_CONFIG_KEYS` before serializing `window.globalConfig`.
|
||||
- SM-07 and the remaining SM-08 dashboard/config work: in progress in the unstaged worktree; keep them out of the SM-06 checkpoint.
|
||||
- SM-09/10: blocked pending approved product identity, domains, assets and legal values.
|
||||
- SM-11/12: pending full egress/release and upstream-history gates.
|
||||
|
||||
## Parallelism
|
||||
|
||||
Safe only after baseline:
|
||||
- `03`, `04`, `05` can run in separate worktrees.
|
||||
- `06`, `07`, `08` can run in separate worktrees.
|
||||
- `09` must finish before `10`.
|
||||
- `11` runs only after all merged into one exact tree.
|
||||
- `12` establishes the recurring upstream workflow after the first verified release tree; its anti-regression gates apply to every later sync.
|
||||
|
||||
Do not parallel-edit `package.json`, `pnpm-lock.yaml`, `config/installation_config.yml`, `app/views/layouts/vueapp.html.erb`, or `DashboardController`; serialize tasks touching these files.
|
||||
|
||||
## Stop conditions
|
||||
|
||||
Stop immediately when:
|
||||
- existing baseline test fails for reasons unrelated to the task;
|
||||
- a required file differs materially from the audited commit;
|
||||
- enterprise code becomes necessary;
|
||||
- migration/schema change appears necessary;
|
||||
- task needs credentials, external service, push, deploy, or public action;
|
||||
- more than three unlisted files appear in the diff.
|
||||
77
HANDOFF.md
Normal file
77
HANDOFF.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# HANDOFF
|
||||
|
||||
## Current state
|
||||
Implementation is in progress in `/Users/kunthawat/Gitea/Chatwoot`; source changes are present and remain unpushed.
|
||||
|
||||
### Verified checkpoints
|
||||
- `8ebb320` — privacy/branding audit harness.
|
||||
- `832a7fd` — Hub sync/registration/telemetry removal.
|
||||
- `1a3697f` — restored only the compatibility APIs required by direct push/billing callers.
|
||||
- `8b1a033` — removed the Chatwoot Hub push relay; exact post-commit independent review passed.
|
||||
- `3458272` — removed cwctl telemetry from the installer; exact post-commit independent review passed.
|
||||
- `d9bf4c4` — removed community signup/onboarding website-branding enrichment; split exact post-commit reviews `deleg_5e2578c5` (backend) and `deleg_ee3e217f` (frontend) passed with empty blocking arrays.
|
||||
- `ced77af` — SM-06 Product Analytics removal and dashboard config allowlist remediation; pre-commit review `deleg_f9644984` and exact post-commit review `deleg_9b0a2f63` passed with empty security/logic arrays.
|
||||
|
||||
### Current work / uncheckpointed work
|
||||
- SM-05 community signup enrichment removal is checkpointed at `d9bf4c4`; pre-commit review, commit hooks, and split exact post-commit reviews passed. The enterprise service/spec remain unchanged and fail with `NameError: uninitialized constant WebsiteBrandingService`. This remains an explicit architecture/legal blocker; do not add a dummy service or restore remote enrichment.
|
||||
- SM-06 is verified at `ced77af`. Its follow-up fix applies `.slice(*GLOBAL_CONFIG_KEYS)` before merging `app_config`, preventing stale/unrequested values such as `CLOUD_ANALYTICS_TOKEN` from reaching serialized `window.globalConfig`. SM-07 local-only observability and the remaining SM-08 dashboard/config script-injection work are still uncheckpointed. The community Help Center SM-08 correction is committed as `5619cc3`: remove GTM remote-container execution, reject/remove legacy GTM config, and retain only fixed allow-listed provider snippets for explicit admin configuration. Exact post-commit review `deleg_dd3db5b7` passed with empty blocking arrays. Enterprise marketing conversion tracking remains outside this community scope pending legal/compatibility review.
|
||||
|
||||
### Evidence and blockers
|
||||
- SM-03 exact post-commit review passed for `1a3697f..8b1a033`; reviewer suggestions are to add explicit missing/blank/partial Firebase negative coverage and clarify legacy FCM configuration names in `.env.example`.
|
||||
- SM-04 exact post-commit review passed for `1a3697f..3458272`; Bash 3.2 compatibility and fail-closed privacy-test behavior were verified.
|
||||
- SM-05 exact committed range `3458272..d9bf4c4` passed split independent reviews: backend `deleg_5e2578c5` and frontend `deleg_ee3e217f`; both returned complete five-key verdicts with empty security and logic arrays. Frontend targeted run passed 9 suites/8 tests. Backend RSpec rerun is environment-blocked because Ruby 3.4.4 is required but only 3.4.10 is installed and the bundle has no usable `rspec` executable.
|
||||
- SM-06 exact committed range `a96b977..ced77af` changed one controller line and passed fresh pre-commit/post-commit independent review. Raw merge leaked a stubbed analytics token; the allowlist slice now removes unrequested keys before layout serialization. Committed Ruby syntax, diff check, and security scan passed. Rails RSpec/RuboCop remain blocked because the active Ruby is 2.6.10 and Bundler 2.5.16 is unavailable.
|
||||
- SM-08 exact post-commit review `deleg_dd3db5b7` passed for `d9bf4c4..5619cc3`; 57 portal model/controller examples, GTM regression 3/3, Ruby/ERB syntax, RuboCop, ESLint/Prettier, Vite build and privacy-audit harness passed. Full Vitest remains 407 passed/15 failed in six unrelated date/time/report-snapshot tests.
|
||||
- No push or deploy has occurred.
|
||||
- Approved product name, domains, logos/icons and sender/legal values are still missing; stop before SM-09/10.
|
||||
- Full Rails/frontend suites, production-like DB checks, HTTP/DNS egress capture, `git fetch --unshallow`, and upstream merge rehearsal remain outstanding. Current full Vitest baseline has 407 passed/15 failed in six unrelated date/time and report-snapshot tests; do not report it as a clean suite until those baseline failures are resolved or formally waived.
|
||||
- Temporary test services/configuration stay under `/tmp`; never commit them or secrets.
|
||||
|
||||
### Repository baseline
|
||||
- Path: `/Users/kunthawat/Gitea/Chatwoot`
|
||||
- Origin: `https://github.com/chatwoot/chatwoot.git`
|
||||
- Branch: `develop`
|
||||
- Initial baseline commit: `9a73c1473ffa0ae6a9c7725046b8ca17922dcc83`
|
||||
- Latest checkpoint: `ced77af` (SM-06 Product Analytics plus dashboard config allowlist remediation; exact post-commit review `deleg_9b0a2f63` passed). SM-08 Help Center remains separately verified at `5619cc3`.
|
||||
- Clone is shallow; fetch full upstream history before merge/rebase rehearsal.
|
||||
|
||||
### Primary deliverable
|
||||
- `.hermes/plans/2026-08-15_092534-chatwoot-private-rebrand.md`
|
||||
- Small-model execution index: `.hermes/plans/chatwoot-private/README.md`
|
||||
- The execution package contains `00-execution-contract.md` plus runbooks `01`–`12` with 58 unique `SM-*` tasks. Send only one task at a time and review its exact diff/test evidence before advancing.
|
||||
|
||||
### Most important finding
|
||||
`DISABLE_TELEMETRY=true` is not a complete opt-out. `lib/chatwoot_hub.rb` still posts `instance_config` to the Hub; it only omits `instance_metrics`. The corresponding behavior is asserted in `spec/lib/chatwoot_hub_spec.rb`.
|
||||
|
||||
### Other unsolicited/vendor-controlled egress to remove
|
||||
- Chatwoot Hub daily sync and onboarding registration
|
||||
- Chatwoot Hub `/send_push` relay fallback
|
||||
- Hub-hosted changelog fetch
|
||||
- silent cwctl command reporting to the Hub
|
||||
- automatic signup email-domain website/DNS branding enrichment
|
||||
- Amplitude browser analytics
|
||||
- Sentry/frontend/backend remote error reporting
|
||||
- optional remote APM agents
|
||||
- arbitrary dashboard scripts and remote GTM container execution
|
||||
- enterprise marketing conversion tracking (proprietary/cloud-only; pending separate legal/compatibility decision)
|
||||
|
||||
### Implementation order
|
||||
1. Follow dependency order in `.hermes/plans/chatwoot-private/README.md`.
|
||||
2. Establish the baseline and privacy audit harness before behavior changes.
|
||||
3. Remove Hub sync/registration/manual refresh, push relay, cwctl telemetry, signup-domain enrichment and remote changelog.
|
||||
4. Neutralize/remove analytics, remote error reporting/APM and script injection.
|
||||
5. Stop at the Runbook 09 input gate until approved brand values/assets are supplied.
|
||||
6. Add central brand defaults and replace visible assets/copy without renaming compatibility APIs.
|
||||
7. Run static and isolated dynamic egress gates, positive feature-traffic tests, full review and visual QA.
|
||||
8. Complete upstream-history and merge-rehearsal workflow; never mix upstream merge with customization commits.
|
||||
|
||||
### User decisions required before implementation
|
||||
- Product name, canonical/support/docs domains, logos/icons and email sender name
|
||||
- Private Gitea repository name
|
||||
- Whether to keep deprecated widget API aliases (recommended: yes)
|
||||
- Whether local-only logs are sufficient or a self-hosted observability endpoint is required
|
||||
- Push notification plan and own FCM/VAPID credentials
|
||||
|
||||
### Safety/legal notes
|
||||
- Root is MIT, but `enterprise/` is proprietary; do not ship enterprise features without a separate license.
|
||||
- No remote repository was created, no files were pushed and no deployment was performed.
|
||||
86
engineering-log.md
Normal file
86
engineering-log.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Engineering Log
|
||||
|
||||
## 2026-08-15 — Chatwoot privacy and rebrand audit
|
||||
|
||||
### Objective
|
||||
Clone/inspect the official Chatwoot source and produce an evidence-based plan for a private-label community fork with no Chatwoot telemetry or visible product branding while preserving upstream syncability.
|
||||
|
||||
### Baseline
|
||||
- Repository: `https://github.com/chatwoot/chatwoot.git`
|
||||
- Branch: `develop`
|
||||
- Commit: `9a73c1473ffa0ae6a9c7725046b8ca17922dcc83`
|
||||
- Root license: MIT
|
||||
- `enterprise/` license: proprietary and excluded from the recommended baseline
|
||||
|
||||
### Actions performed
|
||||
1. Confirmed clone origin, branch, commit and clean working tree.
|
||||
2. Mapped Rails, Sidekiq, Vue/Vite, widget, runtime configuration, manifests and deployment entry points.
|
||||
3. Traced scheduled installation sync to `ChatwootHub.sync_with_hub`.
|
||||
4. Confirmed from implementation and specs that `DISABLE_TELEMETRY` removes metrics but still POSTs installation configuration to Chatwoot Hub.
|
||||
5. Traced Chatwoot-hosted push relay, remote changelog and optional onboarding registration.
|
||||
6. Audited Amplitude, frontend/backend Sentry, optional APM integrations, arbitrary dashboard script injection and marketing tracking configuration.
|
||||
7. Classified feature-required outbound integrations separately from unsolicited telemetry.
|
||||
8. Mapped branding configuration, views, frontend components, locale copy, assets, manifest, mail/widget surfaces and technical compatibility identifiers.
|
||||
9. Wrote implementation plan at `.hermes/plans/2026-08-15_092534-chatwoot-private-rebrand.md`.
|
||||
10. Incorporated independent outbound-audit findings: silent cwctl command reporting, prechecked onboarding subscription, manual super-admin Hub refresh, and unconditional signup-domain website/DNS enrichment.
|
||||
11. Split the master plan into a small-model execution package with an execution contract, dependency index, 12 runbooks and 58 unique bounded tasks covering baseline, privacy removals, observability, branding, E2E egress, release gates and upstream maintenance.
|
||||
|
||||
### Key decisions
|
||||
- Use Community Edition as the legal/technical baseline.
|
||||
- Remove telemetry code paths rather than rely on environment opt-out.
|
||||
- Preserve internal namespaces and widget API aliases initially to avoid upstream conflict and customer embed breakage.
|
||||
- Use no-op adapters/central policy for high-churn analytics callsites.
|
||||
- Remove hosted push relay; require own push credentials.
|
||||
- Remove cwctl event reporting and automatic signup-domain enrichment from the strict privacy baseline.
|
||||
- Validate zero unexpected egress dynamically, not by grep alone.
|
||||
|
||||
### Source changes
|
||||
Application changes are tracked in the verified checkpoints and the uncheckpointed working tree described below.
|
||||
|
||||
### Implementation state (updated 2026-08-16)
|
||||
- SM-01 audit harness checkpoint: `8ebb320`.
|
||||
- SM-02 Hub sync/registration/telemetry removal checkpoint: `832a7fd`; compatibility correction checkpoint: `1a3697f`.
|
||||
- SM-03 push-relay removal checkpoint: `8b1a033`; exact post-commit independent review passed with empty security and logic blocking arrays.
|
||||
- SM-04 cwctl privacy checkpoint: `3458272`; exact post-commit independent review `1a3697f..3458272` passed with empty security and logic blocking arrays.
|
||||
- SM-05 community enrichment removal checkpoint: `d9bf4c4`; split exact post-commit reviews `deleg_5e2578c5` (backend) and `deleg_ee3e217f` (frontend) both passed with empty security and logic blocking arrays. Enterprise compatibility fails because proprietary code still depends on the deleted community base class.
|
||||
- SM-05 boundary decision: do not modify `enterprise/` or restore a compatibility stub in the community surface. The community-only checkpoint is independently verified; the enterprise `WebsiteBrandingService` load failure remains an explicit architecture/legal blocker.
|
||||
- SM-06 Product Analytics removal is verified at `ced77af`: `a96b977` removed the Amplitude/token delivery surface, and `ced77af` added the missing `GLOBAL_CONFIG_KEYS` allowlist before `window.globalConfig` serialization. Fresh pre-commit review `deleg_f9644984` and exact post-commit review `deleg_9b0a2f63` both passed with empty security and logic blocking arrays. SM-07 local-only observability remains uncheckpointed. SM-08 community Help Center GTM removal is committed as `5619cc3` and exact post-commit independent review `deleg_dd3db5b7` passed; fixed, allow-listed provider snippets remain explicit admin-configured traffic, while the GTM container path is removed because its remote container can execute arbitrary tags/scripts.
|
||||
- SM-08 enterprise marketing conversion tracking was classified as fixed-endpoint, cloud-only, limited attribution traffic inside proprietary enterprise code; it remains untouched pending a separate legal/compatibility decision.
|
||||
- SM-09/10 remain blocked until approved product brand inputs are supplied.
|
||||
- No push or deployment has occurred.
|
||||
|
||||
### Current verification notes
|
||||
- SM-06 exact committed range `a96b977..ced77af` changed one controller line. The root cause was raw `GlobalConfig.get(...).merge(app_config)` allowing an unrequested `CLOUD_ANALYTICS_TOKEN` from a stubbed/configured hash to reach serialized `window.globalConfig`; the remediation applies `.slice(*GLOBAL_CONFIG_KEYS)` before the explicit `app_config` merge. Raw merge/sliced-allowlist reproductions were RED/GREEN, committed Ruby syntax and `git diff --check` passed, and the added-line security scan was clean. Rails RSpec/RuboCop remain environment-blocked because the active Ruby is 2.6.10 and Bundler 2.5.16 is unavailable.
|
||||
- SM-03 post-commit exact-range review `1a3697f..8b1a033`: passed; exactly five requested paths changed; 7 targeted RSpec examples, Ruby syntax, RuboCop and git diff checks passed; no secrets or Hub relay fallback remain.
|
||||
- SM-04 post-commit exact-range review `1a3697f..3458272`: passed; exactly two requested deployment paths changed; Bash 3.2 syntax, normal privacy test, fail-closed grep-status checks, secret scan and diff checks passed.
|
||||
- SM-05 pre-commit staged review: passed for exactly 16 community-only paths; commit hook completed ESLint/RuboCop checks.
|
||||
- SM-05 post-commit split review: backend slice `deleg_5e2578c5` and frontend slice `deleg_ee3e217f` both passed with explicit empty blocking arrays; combined committed range is exactly the intended 16 paths.
|
||||
- SM-05 frontend targeted run: 9 suites and 8 tests passed. Backend RSpec rerun was blocked by the local toolchain (installed Ruby 3.4.10 versus Gemfile Ruby 3.4.4; bundle environment has no usable `rspec` executable). Historical controller baseline remains 28 examples/5 failures versus HEAD baseline 27/5; the new no-enrichment regression passed.
|
||||
- SM-07 targeted Rails specs: 7 examples, 0 failures; Ruby/JS lint and syntax checks pass.
|
||||
- SM-08 dashboard controller specs: 7 examples, 0 failures; runtime denylist confirms no `DASHBOARD_SCRIPTS` reader or sink in community files.
|
||||
- SM-08 Help Center correction: 57 portal model/controller examples, targeted GTM regression 3/3, Ruby/ERB syntax, RuboCop, ESLint/Prettier and exact staged security/scope scans pass; Vite production build passes. Full Vitest has 407 passed/15 failed across six unrelated date/time and report-snapshot tests; those failures are not in the changed Help Center path.
|
||||
- Frontend privacy batch: 14 tests, 14 passed.
|
||||
|
||||
### SM-05 gate closure
|
||||
- Exact committed range `3458272..d9bf4c4` is verified by two fresh read-only reviews: backend `deleg_5e2578c5` and frontend `deleg_ee3e217f`; both returned `passed=true`, `security_concerns=[]`, and `logic_errors=[]`.
|
||||
- Community signup/onboarding no longer performs automatic website/branding enrichment or its Redis marker; enterprise compatibility remains intentionally unresolved and must not be repaired by restoring remote enrichment.
|
||||
|
||||
### Remaining gates
|
||||
- Resolve the SM-05 enterprise/legal boundary without restoring remote enrichment.
|
||||
- Complete the SM-07 local-only observability checkpoint and the remaining SM-08 dashboard/config script-injection work; keep provider comments, marketing conversion tracking, and enterprise compatibility as separate scoped decisions.
|
||||
- Run full Rails/frontend suites, production-like copied-database checks, HTTP/DNS egress capture, upstream history fetch and merge rehearsal.
|
||||
|
||||
## 2026-08-16 — SM-06 checkpoint closure
|
||||
|
||||
### Root cause and fix
|
||||
- The first SM-06 commit (`a96b977`) removed `CLOUD_ANALYTICS_TOKEN` from the requested config keys but left `GlobalConfig.get(*GLOBAL_CONFIG_KEYS).merge(app_config)` vulnerable to unrelated keys returned by a stub/config source being serialized through `window.globalConfig`.
|
||||
- The regression was reproduced with `analytics-test-token`; the one-line remediation in `ced77af` applies `.slice(*GLOBAL_CONFIG_KEYS)` before merging the explicit application config.
|
||||
|
||||
### Verification
|
||||
- Pre-commit independent review `deleg_f9644984`: `passed=true`, `security_concerns=[]`, `logic_errors=[]`.
|
||||
- Exact post-commit review `deleg_9b0a2f63` over `a96b977..ced77af`: `passed=true`, `security_concerns=[]`, `logic_errors=[]`.
|
||||
- Committed scope: one path, one line; Ruby syntax, diff check, and added-line security scan passed.
|
||||
- Rails RSpec/RuboCop: blocked by local runtime mismatch (`ruby 2.6.10`, missing Bundler 2.5.16); no runtime pass is claimed.
|
||||
|
||||
### Next action
|
||||
- Start SM-07 as a separate isolated task/checkpoint. Do not mix its unstaged files with the verified SM-06 commits.
|
||||
Reference in New Issue
Block a user