# Findings & Decisions ## Requirements - 2026-07-14 follow-up: with exactly one usable account for a platform, the first human-verification challenge must prevent every later local task for that platform from reaching the adapter. - 2026-07-14: When any account hits a human-verification challenge, retry the current collection with another usable account for the same AI model/platform. - The client/platform error must always produce a desktop system notification, regardless of whether account failover succeeds. - If the challenged model/platform has no other usable account, automatically cancel all later tasks for that model/platform. - Verify the repository against the 9-step implementation plan provided by the user. - Use `/Users/liangxu/Documents/test/geo-rankly/docs/superpowers/specs/2026-03-31-admin-web-backend-core-design.md` as the source of truth for the next development slice. - Continue development immediately after identifying the earliest unfinished step. - For desktop AI monitoring, put scheduling authority on the client, keep scraping silent/background-only, allow stale cross-day tasks to be dropped, and avoid fanning one question to all models at once. ## Research Findings - Screenshot evidence: on client `e83077a0...`, `monitoring_collect:7995` for Doubao failed with `doubao_challenge_required`, while later Doubao task `monitoring_collect:7989` also appeared as failed with the same message. - Screenshot refinement: the first challenged desktop task `#7224` shows attempt 1, while later `monitoring_collect:7989` shows attempt 0 and the same copied challenge message. This indicates the later task was bulk-terminalized without adapter execution, but recorded as `failed`, making cancellation look like another failed attempt. - The product-correct state model should be: the triggering task is `failed`; later same-model tasks are `cancelled` in `desktop_tasks` and `skipped` (with a human-verification skip reason) in `monitoring_collect_tasks`, and cancellation events remove them from the local scheduler. - The missing invariant is client-local: after the only usable account becomes challenged, same-platform tasks already leased or queued locally must be canceled/removed before adapter activation. Server result propagation is asynchronous and cannot be the sole guard. - `monitor-scheduler` already honors `blockedPlatforms` during selection, and `pumpExecutionLoop` supplies `blockedMonitorPlatforms()`. This supports the screenshot's attempt-0 evidence: the main observed bug is cancellation being represented as failure, not a confirmed second adapter call. - The scheduler has only single-task removal (`noteMonitorTaskCanceled`); it lacks an atomic "remove all non-active tasks for platform" operation, so local stale signals/legacy leases remain cached until later invalidation. - Server bulk authorization handling currently sets queued `desktop_tasks` to `failed`, publishes `task_completed`, and sets linked/remaining `monitoring_collect_tasks` to `failed` with `skip_reason = NULL`. Those exact choices produce the misleading repeated-failure rows. - The queued desktop candidate query also filters `target_account_id`; for monitor tasks this is narrower than model-level cancellation and should be relaxed to client+platform once all local accounts are exhausted, while publish cancellation must remain account-scoped. - Desktop runtime already removes monitor scheduler entries for both `task_completed` and `task_canceled`; changing the event type improves semantics, while an immediate local platform purge closes the interval before the server event arrives. - A monitor circuit breaker must be cleared only after an explicit successful account probe/rebind or a later account sync that projects at least one usable account. It should not clear merely because a queued task arrives. - Server/UI contracts already support `desktop_tasks.status = cancelled`, `monitoring_collect_tasks.status = skipped`, and translated/admin display for skipped/cancelled states, so no schema migration is required. - Desktop shared event types expose terminal cancellation as runtime `aborted` rather than raw DB `cancelled`; server lifecycle publication must normalize the canceled DB status before sending, or the client event contract becomes inconsistent. - `publishDesktopTaskLifecycleEvent` currently forwards raw `task.Status`; before choosing a new DB status, confirm the existing desktop cancellation query's canonical status. The client already treats `task_canceled` as removal regardless of status. - Existing desktop cancellation uses canonical DB/runtime status `aborted`, not `cancelled`. Follow-up monitor desktop tasks should therefore transition to `aborted` and emit `task_canceled`, keeping the shared event contract valid. - The failed combined server patch made no changes; exact code inspection confirmed the remaining edits are isolated to the authorization bulk helper, its two lifecycle-event loops, and the monitoring bulk SQL. - Review confirms the triggering task remains failed; only follow-up monitor tasks change to aborted/skipped. Publish authorization behavior remains failed and account-scoped. - The local circuit breaker participates in both scheduler selection and server lease `platform_ids`, while platform cleanup removes queued/leasing records only and preserves the currently active task. - Follow-up queued desktop tasks should also set `completed_at = NOW()` when moved to terminal `aborted`, preventing misleading open durations in ops views. - Full desktop and server regression suites pass after the state transition changes. Existing monitoring outbox/projection code already treats `skipped` as terminal, and ops job projection maps skipped/superseded work to canceled. - Final diff review found no debug instrumentation or whitespace errors. One remaining audit point is whether the new skip reasons must join projection exclusion lists so canceled human-verification tasks do not affect monitoring coverage metrics. - Projection audit: only tasks deleted because brand/keyword/question configuration disappeared are excluded from planned samples. Existing `platform_not_accessible` skips remain planned misses, so human-verification cancellations should follow that policy and remain in the denominator. No exclusion-list change is needed. - Runtime reset and successful account probe/rebind provide explicit circuit-breaker reset points. The breaker should also reconcile after account sync only when health projection reports a usable account. - 2026-07-14 initial trace: `monitor-account-pool.ts` already filters `challenge_required` accounts and classifies challenge errors as same-platform failover candidates. - Runtime admission also computes platform-level eligibility and sends it as `platform_ids` during monitor leases; challenge handling therefore needs to distinguish "one account blocked" from "no usable account remains" before excluding/canceling a model. - Current desktop/server cancellation APIs cancel one current task/lease. No explicit batch operation for all later tasks of one AI platform/model was found in the first search. - Electron main bootstrap already owns tray-balloon behavior, while current monitor challenge handling primarily writes runtime activities and renderer issue rows; a reusable main-process system notification seam is needed for all runtime client errors. - The account-pool classifier already returns `challenge_required`/`risk_control` failover reasons and the execution loop references candidate selection around `runtime-controller.ts:3273`; focused inspection must verify notification timing and whether health projection removes only the failed account. - Server final-result handling already has two bulk authorization-failure paths: queued `desktop_tasks` for the same target account/platform, and same-day `monitoring_collect_tasks` for the same client/platform. Both are activated only when the final submitted result is non-retryable AI authorization failure. - The monitoring bulk path sets later model tasks to `failed` with an explicit `blocked_by_authorization_failure` payload; product wording says "cancel", so final verification must decide whether the existing terminal failure state is the intended schema-compatible cancellation representation or needs a distinct state transition. - Hypothesis 1 was falsified by code-path inspection: `executeMonitorTaskWithAccountPool` snapshots all usable same-platform accounts before attempts, then continues to the next account after the first account is marked challenged/cooling. The health change does not remove the already-selected alternate from the current loop. - Hypothesis 2 is confirmed: challenge/account access handling only calls `recordActivity`; there is no runtime-to-Electron system-notification callback, so an intermediate account failure remains invisible outside the activity/issue UI even when failover succeeds. - Hypothesis 3 is confirmed at implementation level: a final non-retryable AI authorization result invokes server bulk failure for remaining same-day platform tasks. The desktop task schema supports `cancelled`, but monitoring collection workflow uses `failed` as the terminal authorization-block state; preserving that state avoids introducing an incompatible cross-database status without product benefit. - Required regression seams: desktop pure policy/helper coverage for "notify on challenged account even when an alternate remains", plus server SQL-contract coverage proving platform-wide remaining task failure is scoped to the same client/platform/business date and triggered only by non-retryable AI authorization. - Implementation boundary: add a runtime-to-Electron notification sink, route every `danger` activity through it, and explicitly notify account challenge/risk-control transitions because those remain `warn` while same-platform failover continues. - Use native Electron notifications on macOS/Linux and the existing tray balloon path on Windows; add short-window duplicate suppression in the runtime notification sink to avoid repeated loop errors flooding the user. - Desktop tests run under Vitest's default discovery (no desktop-local Vitest config), so the notification sink should remain Electron-free and unit-testable; only `bootstrap.ts`/`tray.ts` should depend on Electron notification APIs. - Existing server tests do not directly mention the authorization bulk-failure helpers, confirming the cancellation/terminalization scope currently has no focused regression lock. - The new runtime notification test was red before implementation (`Cannot find module './runtime-system-notification'`) and now passes together with the existing same-platform account-pool tests. - Native notification delivery is isolated in `tray.ts`: Windows prefers the initialized tray balloon and falls back to Electron `Notification`; macOS/Linux use Electron `Notification` when supported. - Existing callback tests already prove authorization failures are non-retryable and that explicit target client wins over leased executor. The untested part is the bulk SQL scope (`business_date + platform + client`, excluding the current task), not the failure disposition itself. - The existing `captureDailyMonitorTaskTx` test double implements `pgx.Tx` and records SQL/arguments, so `failRemainingMonitoringTasksForAuthorizationFailure` can be exercised directly without a database or production refactor. - Final result ingest calls the platform-wide bulk failure only after a failed result is not requeued. Intermediate account errors never reach this server method because the desktop account loop submits only its final result; this is the key invariant that allows alternate-account success without canceling later tasks. - The bulk-failure method accepts a `monitoringCollectTask` containing tenant, business date, platform, execution owner, and target/leased client fields, so the SQL-scope regression can assert both query predicates and exact bound arguments. - `doubao_challenge_required` is recognized as an authorization failure by the server's `_challenge_required` suffix rule, and an explicit `retryable=false`/`failure_category=ai_platform_authorization` final error activates the bulk path. - Final behavior: one challenged account is immediately notified and excluded while the current task continues on another usable same-model account; if the account pool is exhausted, the final non-retryable result terminalizes later same-day tasks for that client/model through the existing server bulk path. - Final automated verification passed. Native notification center display itself remains a host-OS integration caveat, but the Electron API path compiled in the production bundle. - Current 2026-05-07 publish-management request targets `apps/admin-web`: the user wants a new content-management page showing publish title, account/platform, status, task info, time, and actions for external link, opening the desktop client workbench, and retrying. - Desktop client already has `PublishManagementView.vue` with the desired table shape and action semantics, but it calls Electron IPC (`window.desktopBridge`). Admin-web needs tenant-authenticated HTTP endpoints instead. - Existing backend desktop endpoints are `/api/desktop/publish-tasks` and `/api/desktop/publish-tasks/:id/retry`, but they require desktop client token auth. Tenant API currently exposes publish-job creation only, not publish task list/retry. - `DesktopTaskService.ListPublishTasks` already has the correct pagination/title-search/status ordering behavior, but accepts a desktop client context. A tenant variant should scope by request actor/current workspace and created-by user. - `PublishJobService.RetryByDesktopTask` already contains retry reconstruction logic, but accepts desktop client context. A tenant variant can reuse the same core flow with actor workspace/user ownership checks. - Admin-web content-management nav currently includes media, knowledge, and images under `AppShell.vue`; publish management should join that group as `/publish-management`. - Web "open workbench" cannot call Electron IPC directly. A desktop deep-link URL can be used as a link-style entry; if the desktop app does not register that scheme yet, the link is still a front-end contract for the desktop client to adopt. - Current review scope: determine whether the monitoring data detail path and the daily scheduled generation of question collection tasks are working. - `cmd/scheduler` starts `ScheduleDispatchWorker`, monitoring result recovery, lease recovery, received inspection, and knowledge cleanup; it does not start a worker that generates daily `monitoring_collect_tasks`. - The only production code path found so far that inserts `monitoring_collect_tasks` is `MonitoringService.CollectNow` via `ensureCollectNowTasks`; this writes `trigger_source = 'manual'`. - Current local Monitoring PG data confirms recent task creation is manual: on 2026-04-24 there are 3 desktop/manual/plugin_standard tasks; on 2026-04-23 there are 6 desktop/manual/plugin_standard tasks. The latest `automatic` monitoring collect tasks in this local DB are from 2026-04-17, not today. - Current local business PG has `tenant_monitoring_quotas.collect_frequency = 'daily'` and `task_daily_hard_cap = 36`, but those fields are configuration only in the code paths inspected so far; no scheduler reads them to generate today's monitoring tasks. - The repository already contains core backend scaffolding under `server/`, including `cmd/tenant-api`, `internal/bootstrap`, `shared`, `tenant`, `migrations`, and SQL query files. - `server/Makefile` already defines `dev-init`, `dev-api`, `migrate-up`, `sqlc-generate`, `seed`, `lint`, and `test`. - The bootstrap layer already exposes `/api/health/live` and `/api/health/ready`. - The design doc defines a sequential 9-step delivery plan and expects `sqlc` generated code under `internal/tenant/repository/generated/`, plus repository wrappers around it. - The repository has 19 migration `.up.sql` files, matching the plan's schema count, and `cmd/dev-seed` seeds `admin@geo.local` plus plan/quota/template data. - `internal/tenant/repository/` currently contains only query SQL, `sqlc.yaml`, `tx.go`, and placeholder integration tests; there is no `generated/` directory and no repository wrapper implementations. - `auth_service.go`, `workspace_service.go`, `template_service.go`, `article_service.go`, and `brand_service.go` still issue SQL directly against `pgxpool.Pool`, which contradicts the design doc's Step 4 target. - Running `make sqlc-generate` currently fails because `server/internal/tenant/repository/sqlc.yaml` sets `schema: "../../migrations/"`, which resolves to a non-existent path. The correct repository-relative path should point to `server/migrations`. - After fixing `sqlc.yaml`, `make sqlc-generate` succeeds and produces typed code in `server/internal/tenant/repository/generated/`. - Repository wrappers now exist for auth, workspace, template, article, quota, and audit queries, and the auth/workspace/template services consume them on the main code path. - Refresh token rotation is now atomic in Redis via a Lua script in `session_store.go`, with tests covering successful rotation and hash mismatch rejection. - `make test` and `make lint` both pass locally after cleaning up existing lint violations. - A GitHub Actions workflow now exists at `.github/workflows/backend-ci.yml`; it runs `sqlc` generation, verifies generated code is committed, executes the tenant SQL scope guard, runs tests, and runs `golangci-lint`. - The `server/Makefile` now exposes `tenant-scope-guard`, so the tenant filter check is available both locally and in CI. - The `server/Makefile` no longer depends on a preinstalled `migrate` binary; it now runs `golang-migrate` via `go run -tags postgres`, which allowed `make dev-init` to run successfully on this machine. - `make dev-init` now completes end-to-end: Docker services start, all 19 migrations apply, and seed data creates tenant/user/plan/template records. - Runtime verification succeeded for `/api/health/live`, `/api/health/ready`, `POST /api/auth/login`, `GET /api/auth/me`, `POST /api/auth/refresh`, and `POST /api/auth/logout`. - Negative auth checks also passed at runtime: the old access token returns `40103 token_revoked` after logout, and the old refresh token returns `40121 refresh_session_expired` after rotation. - Workspace runtime verification passed for all four first-screen endpoints: `overview`, `recent-articles`, `quota-summary`, and `template-cards`. - Template generation runtime verification passed end-to-end: creating a generation task produced an article, article detail returned `generate_status=completed`, article versions returned one version, and quota balance decreased from 100 to 99. - Brand runtime verification passed for create keyword/question/competitor flows, question version history returned two versions after update, and soft-deleted brands can be recreated with the same name. - The repository still has no `apps/` or `packages/` directories, so neither `admin-web` nor `ops-admin-web` has been started. - The frontend architecture doc recommends `pnpm workspace + Vue 3 + TypeScript + ant-design-vue + Vite`, with shared packages for UI, shared DTOs, HTTP client, and TS config. - The current backend already exposes enough stable endpoints for a first `admin-web` slice: `POST /api/auth/login`, `GET /api/auth/me`, and the four workspace endpoints under `/api/tenant/workspace/*`. - Backend responses use a consistent envelope: `code`, `message`, `data`, and `request_id`, which makes a shared frontend API client straightforward. - Local toolchain support for the frontend is present on this machine: `node v22.20.0`, `npm 10.9.3`, and `pnpm 10.28.2`. - A new pnpm workspace now exists at the repo root, with `apps/admin-web` plus shared packages under `packages/shared-types`, `packages/http-client`, and `packages/tsconfig`. - `apps/admin-web` is now a working Vue 3 + TypeScript + Vite 8 app with Ant Design Vue, Pinia, Vue Router, and Vue Query wired in. - The frontend implements a persistent auth session store, login flow, route guard, auto-refresh-capable API client, and a tenant-facing application shell. - The workspace page is wired to live backend data through the four dashboard endpoints and renders template cards, stats, quota, and recent articles. - Placeholder routes now exist for template creation, custom generation, optimization, media, brands, tracking, and knowledge so the navigation shell is ready for incremental page delivery. - `pnpm build:admin` passes successfully, producing a production build under `apps/admin-web/dist/`. - Static preview verification passed via `vite preview` and HTTP checks against `http://127.0.0.1:4173/login`, which returned `HTTP/1.1 200 OK` plus the built asset references. - Gemini CLI was tested but then abandoned for this turn: `gemini-3.1-pro-preview` consistently returned `429 MODEL_CAPACITY_EXHAUSTED`, and the user explicitly requested not to use Gemini afterward. - After the user indicated Playwright was fixed, browser-level verification succeeded through the Playwright CLI path rather than the broken MCP path. - Browser testing exposed one real frontend bug: the login CTA rendered correctly but did not submit the form on click until it was explicitly bound to `handleSubmit`. - After the login-button fix, a Playwright end-to-end flow successfully loaded `/login`, signed in with the seeded account, reached `/workspace`, and verified the presence of the workspace heading plus the template and recent-content sections. - The production bundle size improved substantially after switching Ant Design Vue registration from full-plugin mode to per-component registration; the main JS chunk dropped from roughly `1.4 MB` to about `747 kB` before gzip. - The currently exposed tenant-facing backend interfaces fall into four frontend-covered groups: auth, workspace, templates/articles, and brands. - The reference screenshots were re-read using position-aware OCR extraction, which made the layout intent clear beyond the raw text labels. - `docs/refer/工作台.png` places the main content in a left-heavy column with template cards above recent articles, while the right side holds the compact stats cluster; the quota/plan card sits in the left sidebar footer rather than the main canvas. - `docs/refer/模板创作.png` uses a top page hero, a horizontal filter/action strip, an article table below it, and a separate template-selection layer before entering a 3-step generation flow. - `docs/refer/文擎GEO/模版创作-新建模版-创建topx文章-步骤一.png` and `...步骤二.png` show the generation flow as a wizard: step 1 basic info plus brand/keyword/competitor context, step 2 title and structure selection, step 3 generation. - `docs/refer/品牌词库.png` uses a top brand card rail, then a split content area with keyword navigation on the left and question management on the right; the competitor library lives under a sibling tab. - `docs/refer/媒体管理.png` confirms the overall visual system: page hero under the content header, three-step guide card near the top, and white cards on a cool gray background with the same left navigation shell. - The user explicitly asked for i18n and styling discipline during implementation: text should move behind translation keys, and component styles should live in `