diff --git a/.hermes/plans/2026-08-15_092534-chatwoot-private-rebrand.md b/.hermes/plans/2026-08-15_092534-chatwoot-private-rebrand.md new file mode 100644 index 000000000..710e3c241 --- /dev/null +++ b/.hermes/plans/2026-08-15_092534-chatwoot-private-rebrand.md @@ -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 +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. diff --git a/.hermes/plans/chatwoot-private/06-remove-amplitude.md b/.hermes/plans/chatwoot-private/06-remove-amplitude.md new file mode 100644 index 000000000..580cfac28 --- /dev/null +++ b/.hermes/plans/chatwoot-private/06-remove-amplitude.md @@ -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` diff --git a/.hermes/plans/chatwoot-private/README.md b/.hermes/plans/chatwoot-private/README.md new file mode 100644 index 000000000..5cee72d09 --- /dev/null +++ b/.hermes/plans/chatwoot-private/README.md @@ -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. diff --git a/HANDOFF.md b/HANDOFF.md new file mode 100644 index 000000000..b626874bc --- /dev/null +++ b/HANDOFF.md @@ -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. diff --git a/engineering-log.md b/engineering-log.md new file mode 100644 index 000000000..a01fbc886 --- /dev/null +++ b/engineering-log.md @@ -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.