Commit Graph

25 Commits

Author SHA1 Message Date
Semih
fdce3f6bd0 feat(content): Phase 8 content generation (Faz A+B) for Sase.tr
Hybrid content automation pilot: generation + review + drafts live in the
panel (reusing the insight pipeline's DeepSeek client, prompt_templates
versioning, cost_ledger and budget_settings); publishing/distribution will
go through n8n (Faz C, not built). Channels: blog, LinkedIn, X, Instagram.
Topic sourcing is automatic (LLM-generated ideas). Approval model: drafts
sit in the panel for manual review/edit/publish.

Faz A (worker):
- ContentTopic / ContentDraft Prisma models (content_topics, content_drafts)
- content-prompts.ts: 5 seed prompts (topic ideas[pro] + blog[pro] +
  linkedin/x/instagram[flash]), Turkish B2B automotive tone, per-channel
  JSON schemas
- content-budget.ts: separate budget envelope (sums only content_* spend)
- content-topics job (auto idea gen, backlog-capped, title dedupe) +
  content-generate job (queued topic -> one draft per channel)
- content-pipeline scheduler (separate BullMQ queue, topics@*/8h,
  generate@*/10min), wired into index.ts; seeded via seed-runtime
- content budget settings (caps + content_paused kill switch); seed default
  content_paused=true for a safe first deploy

Faz B (web):
- /content (queue + auto/manual triggers + manual topic form),
  /content/t/[id] (per-channel draft cards: preview, JSON edits,
  approve/reject), /content/costs (content-only spend)
- server actions (audit-logged), manual trigger API routes, contentQueue(),
  nav + Cmd+K entries
- content caps surfaced on /insights/settings/budgets + whitelisted

Both packages typecheck. Schema applies on deploy (web start runs
prisma db push).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 00:11:05 +03:00
Semih
16695de6d2 feat(insights): compress.ts noise filter + per-session reprocess
Drop successful (2xx/3xx) asset and telemetry GETs from the semantic
timeline — these were eating MAX_LINES (80) before any user-behavior
signal could surface. In a 9m32s session we observed only the first
1:18 reached the LLM input because PostHog /collect/* pings filled
the budget. Failures (4xx/5xx) are still emitted as signal.

Filtered prefixes: /collect/, /flags, /array/, /static/, /assets/,
/api/surveys, /css2. Also raise MAX_LINES 80→120.

Add optional { sessionId } to POST /api/insights/reprocess so a single
session can be re-run end-to-end without resetting a whole batch.
2026-05-20 17:00:31 +03:00
Semih
39c26bd4d4 feat(sase): VIN list management view
A user-list-style table at /projects/sase/vin-decode/vins for inspecting
and managing decoded VINs. Pairs with sase.tr#34 (cache-clear + delete
endpoints).

Repo (lib/sase/vin-list.ts)
- listVinDecodes(filter, sort, page) — raw SQL with dynamic WHERE for
  search (VIN/email), provider IN, success boolean, brand IDs, date
  range. Joins users + brands, computes EXISTS(vehicles) per row so the
  action menu knows whether DB delete is meaningful.
- KNOWN_PROVIDERS export.

Admin SDK
- clearVinCache({ vin, reason, founderId }) — POST cache-clear
- deleteVehicleByVin({ vin, reason, founderId }) — DELETE vehicle
- Result types: VinCacheClearResult, VinDeleteResult.
- SASE_ADMIN_ENDPOINTS list updated.

Panel routes
- POST /api/sase/vins/[vin]/cache-clear — auth + spoke-wired + reason
  ≥ 5 chars. Audit on both paths.
- DELETE /api/sase/vins/[vin] — same shape.

UI (/projects/sase/vin-decode/vins)
- URL-driven filters (VIN/email search, provider multi-select pills,
  success/fail toggle) — sharable links.
- Sortable columns: Tarih (createdAt) and RT (responseTimeMs).
- Per-row cells: timestamp · VIN (mono) · brand slug+name · user email
  (link to user detail) · provider badge (link to provider drill-down)
  · ok/fail badge (fail title = errorMessage) · responseTimeMs ·
  cacheSource from timings jsonb · action menu.
- Per-row action menu (gated on saseAdminWired):
    [Cache] — opens reason modal, POSTs cache-clear
    [DB]    — only when vehicles row exists; opens reason modal,
              destructive variant, DELETE
- Pager with prefix/postfix range + ←/→ links preserving filters.

Dashboard header gets a "VIN list →" pill next to Business/Trends.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 07:59:22 +03:00
Semih
a10996f6c5 feat(sase): deploy regression Telegram alert
Closes the alert side of the deploy-regression view added in Faz 2b.
The dashboard table already flagged regressed deploys; this commit
pushes a Telegram when one happens, so MTTD doesn't depend on the
founder checking the dashboard.

Detection (panel)
- detectVinRegressions() pulls the last 20 Coolify deploys for the
  Sase.tr app, filters to those whose post-window has elapsed (≥30min
  since finishedAt) and isn't too old (≤180min since finishedAt), and
  reuses analyzeDeployRegressions to compute the 30min before/after
  success-rate slices. A row is flagged when:
    - both before and after have ≥5 samples, and
    - success rate dropped ≥10pp (severity 'high'; ≥15pp → 'critical').
- Returns a RegressionHit per flagged deploy with a 24h dedupe TTL
  keyed on deploymentUuid so each deploy alerts exactly once ever
  (regardless of how often the 5-min cron checks).

Endpoint
- GET /api/internal/vin-anomaly-check now returns
  { ok, current, baseline, anomalies, regressions }.

Worker
- sendTelegram() accepts an optional dedupeTtlSeconds override so
  per-call long-TTL dedupes (like deploy alerts) don't have to go
  through the global env default.
- New alertVinRegression() formats severity icon + before/after %
  + deploy commit/timestamp + dashboard link.
- runVinAnomalyDetect now also walks the regressions array and
  fires Telegram for each. Returns { anomalies, regressions,
  alertsFired, alertsDeduped }; pipeline log prints when either
  count is non-zero.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 13:33:07 +03:00
Semih
4694c1ffdb feat(sase): VIN decode anomaly detection + Telegram alerts
Adds the active half of Phase 8: dashboard was passive, this pushes
when something breaks. Targets MTTD < 10 minutes from the PRD.

Detection (panel-web)
- detectVinAnomalies() compares a 15-minute current window against a
  baseline built from the same 15-minute slot across the previous 7
  days (same-hour-of-day, average across days that had ≥ 1 sample).
- Five hits, two severities:
  * success_rate_drop — high at ≥ 5pp drop, critical at ≥ 15pp
  * p95_latency_spike — high at 2× baseline (and > 1s), critical at 3×
  * volume_drop — high at ≥ 80% below baseline, critical at ≥ 95%
  * volume_spike — high at ≥ 5× baseline
  * timeout_dominance — high when ≥ 50% of failures are timeouts
- Minimum-volume guards on both current and baseline so quiet hours
  don't generate noise (MIN_CURRENT_VOLUME=5, MIN_BASELINE_VOLUME=10).
- Each anomaly carries a 15-min-bucket dedupe key — same anomaly type
  fires at most once per bucket regardless of cron cadence.

Endpoint
- GET /api/internal/vin-anomaly-check, gated by x-internal-worker-token
  header (constant-time compare against INTERNAL_WORKER_TOKEN env).
  Returns { current, baseline, anomalies[] }.

Worker
- New job vin-anomaly-detect, BullMQ scheduler */5 * * * *.
  Fetches the panel endpoint, then for each anomaly calls
  alertVinAnomaly() — sendTelegram with the bucket dedupe key, so
  Redis SETEX NX dedupes across the 1h cooldown window.
- alertVinAnomaly() in worker/lib/telegram.ts formats the message with
  severity icon + baseline/observed/volume context + dashboard link.
- runVinAnomalyDetect returns { ok, checked, anomalies, alertsFired,
  alertsDeduped }; pipeline logs only when something happened or the
  check failed.

Env
- INTERNAL_WORKER_TOKEN set on both panel-web and panel-worker
  (32-byte hex, generated in Coolify).
- PANEL_INTERNAL_URL on panel-worker → coolify-network UUID hostname
  for panel-web, no Cloudflare/Tailscale hop on internal calls.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 12:22:02 +03:00
Semih
096ca9b4a3 feat(sase): brand picker — Phase F
Pairs with sase.tr#31 (POST /internal/admin/subscriptions/:id/brands).

Admin SDK
- setSubscriptionBrands({ subscriptionId, brandIds, reason, founderId }).
- BrandSetResult type; SASE_ADMIN_ENDPOINTS list updated.

Repo
- listBrands() helper (RO).
- getUser() now returns currentPlanBrandCount + brandIds for the picker.

Route
- POST /api/sase/subscriptions/[subId]/brands — auth + spoke-wired,
  brandIds array required, reason ≥ 5 chars. Audit on both paths.

UI (BrandPicker on the Billing tab, below subscription actions)
- Full plan (brandCount=0): shows "all brands automatic" badge list,
  no picker.
- Active/trial with brandCount > 0: checkbox grid of all active brands.
  Clicking past the plan limit shows an inline error. Visual deltas:
  outline "kaldırılacak" badge on rows being removed, default "yeni"
  badge on rows being added.
- Cancelled/expired: read-only message.
- Save button enabled only when selection is exactly plan.brandCount,
  there's a dirty diff, and a reason ≥ 5 chars is entered. Saves +
  router.refresh().

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 10:38:34 +03:00
Semih
0eacc32286 feat(sase): refund + bonus extension — Phase E
Pairs with sase.tr#30.

Refund
- admin-sdk: refundPayment({ paymentId, amount?, reason, founderId }) +
  RefundResult type; SASE_ADMIN_ENDPOINTS lists the new route.
- POST /api/sase/payments/[paymentId]/refund — auth + spoke-wired,
  reason ≥ 5 chars, amount > 0 when provided (full refund if omitted).
  Audit on success and failure.
- _refund-button.tsx: per-row [Refund] button in the Billing tab
  payments table. Modal has [✓] full vs partial input, reason, and a
  destructive submit. Hidden for payments that aren't refundable
  (status !== completed/partially_refunded).
- Payment row now exposes an "İşlem" column (only when spoke is wired).

Bonus extension (goodwill)
- BillingActions: new "Bonus +7g/+14g/+30g + özel" buttons surfaced
  for active subscriptions. Hits the same /trial/extend wire under the
  hood — the spoke generalized that endpoint to active too — but the
  panel uses "Bonus süre ekle (goodwill)" copy instead of "Trial uzat".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 10:06:16 +03:00
Semih
631ca6b4c4 chore(sase): show emails in plain text — drop masking + reveal flow
User decision: emails should display openly on the panel (Tailscale-only,
single founder). The reveal-with-audit pattern was overkill for this
trust model and added a click per inspection.

- List page: u****@x.com → u.email
- Detail header: <EmailReveal/> → plain <span>{user.email}</span>
- Delete _email-reveal.tsx + /api/sase/users/[id]/reveal-email route
- Delete maskEmail() helper (no remaining callers)

Audit log no longer captures per-email reveals; the audit tab now only
shows lifecycle/billing/impersonation actions, which is what we actually
care about.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 09:53:54 +03:00
Semih
3621c0801f feat(sase): plan change + subscription cancel/resume — Phase D
Completes the billing surface; pairs with sase.tr#29.

Admin SDK
- changePlan(input) + cancelSubscriptionById(input) + resumeSubscription(input).
- PlanChangeResult + SubscriptionStateChange types exported.
- SASE_ADMIN_ENDPOINTS lists the three new spoke routes.

Route
- /api/sase/subscriptions/[subId] now accepts change-plan/cancel/resume
  in addition to trial-extend/activate. newPlanId required when
  action=change-plan. Reason ≥ 5 chars enforced. Audit captures the
  action + days + newPlanId + reasonLen on both paths.

Repo
- getUser() also returns currentPlanId for the plan picker.

UI
- BillingActions extended:
    active/trial → [Plan değiştir][İptal et]
    cancelled    → [Devam ettir]
- Change-plan modal lists active plans (current excluded) with brand
  count hint. The spoke flags brand-reassignment via response field;
  v1 surfaces only the confirmation.
- Cancel uses destructive variant + clear messaging.

Phase D ships the user-management mutation surface — A/B/C/D all live.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 09:47:05 +03:00
Semih
01bb820c57 feat(sase): trial extend + manual activate — Phase C
Billing tab on user detail page now has writable actions wired to spoke
endpoints landed in sase.tr#28.

Admin SDK
- extendTrial({ subscriptionId, days, reason, founderId }) + result type
- activateSubscription({ subscriptionId, reason, founderId }) + result type
- SASE_ADMIN_ENDPOINTS list updated

Repo
- getUser() now returns subscriptionId so the billing UI can act on it.

Route
- POST /api/sase/subscriptions/[subId] — single multiplexed endpoint:
  body { action: 'trial-extend' | 'activate', reason, days? }.
  Auth + spoke-wired + reason ≥ 5 chars + days 1..90 (trial-extend).
  Audit on success and failure paths.

UI
- BillingActions client component on the Subscription & Billing tab.
- Trial state: [+7g][+14g][+30g] quick buttons + custom days input +
  reason modal.
- Trial or pending state: [Subscription'ı aktive et] button.
- Other states show "no billing action available" hint.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 09:41:45 +03:00
Semih
b1ec7a5277 feat(sase): user lifecycle controls — Phase B
Suspend / Reactivate / Ban buttons on the user detail page; wired to
spoke endpoints (sase.tr#27).

Admin SDK
- suspendUser/reactivateUser/banUser on SaseAdmin + LifecycleResult.
- SASE_ADMIN_ENDPOINTS updated.

Route
- POST /api/sase/users/[id]/lifecycle (action + reason). Auth + spoke
  wired + min reason length checks. Audit on both success and failure.

RO model
- Sase Prisma schema adds status/statusReason/statusChangedAt/
  statusChangedBy. getUser() returns lifecycleStatus + statusReason.

UI
- LifecycleStatusBadge in header next to email reveal.
- LifecycleButtons renders the right actions for the current state.
- Modals with reason textarea; ban requires a double-confirm checkbox.
- Impersonate hidden when user is suspended/banned (AuthGuard rejects).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 09:38:07 +03:00
Semih
5b26d10485 feat(sase): founder notes — Phase A of user mutations
Panel-side only — first writable feature on the Sase user detail page.
Notes are stored in the panel database (sase_user_notes); the spoke is
never touched (KVKK minimize).

Model
- SaseUserNote { id, saseUserId, authorUserId, body, pinned, createdAt }
- Index on (saseUserId, pinned, createdAt) for the default render order

API
- GET  /api/sase/users/[id]/notes — list (pinned first, then newest)
- POST /api/sase/users/[id]/notes — { body, pinned } create (audit'li)
- PATCH/DELETE /api/sase/notes/[noteId] — toggle pin / hard delete

UI
- New "Notlar" tab on user detail. Textarea + pinned checkbox + submit;
  list shows TR-localized timestamps, per-row pin/unpin and delete.

Schema sync
- package.json `start` now runs `prisma db push --skip-generate` before
  `next start`. Panel uses db-push style (no migrations dir); this lets
  the new table land on next deploy without a separate manual step.
  Future destructive changes will require a smarter migration approach.

teknikborc.md updated: super_panel_reader role still needs to be created
on the new sase-postgres (current panel uses sase superuser).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 09:32:48 +03:00
Semih
d8c2bdd22f feat(sase): read-only impersonation from user detail page
Adım 3 — Süper Panel side of the readonly impersonation flow.

- admin-sdk/sase.ts: impersonateReadonly(userId, founderId, ttlMinutes,
  reason) — POSTs to /internal/admin/users/:id/impersonate-readonly.
  notWiredSdk stub returns same shape so type contract holds when env vars
  are absent.
- POST /api/sase/users/[id]/impersonate-readonly — auth-checked panel
  endpoint. Validates ttl ∈ {15,30,60} + reason ≥ 5 chars. Calls spoke SDK,
  writes audit (double-audit: AdminClient also logs at the wire), returns
  { redirectUrl, expiresAt, sessionIdPrefix }.
- _impersonate-button.tsx (client): shadcn Dialog. TTL pills (15/30/60) +
  reason textarea + submit. On success opens spoke redirectUrl in new
  window/_blank/noopener.
- Detail header gains [Impersonate (read-only)] button — gated on
  saseAdminWired() so it stays hidden until SASE_ADMIN_API_BASE +
  INTERNAL_API_TOKEN_SASE land in Coolify env.

Spoke side (sase.tr@79a2616 → … → next release): InternalAdminModule +
ImpersonationReadonlyGuard already merged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 00:51:38 +03:00
Semih
0b31685e95 feat(sase): user management Phase 7a — listing + detail (RO)
Adım 1 — User listesi sayfası
- Extend Sase RO Prisma schema: Plan, Brand, UserBrand, BankAccount, Payment, QueryLog
- listUsers() + listPlans() repo with filter/sort/page in apps/web/src/lib/sase/users.ts
- /projects/sase/users — TanStack-free server-rendered table with URL-param filters
  (plan multi-select, status, activity preset, search) + pager
- Sase landing "Users →" link

Adım 2 — User detay 360°
- /projects/sase/users/[id] — header (masked email + audit-logged reveal button)
  + 5 tabs: Özet · Subscription & Billing · Kullanım · Aktivite · Audit
- Usage stats: 30d/90d/lifetime queries, success rate, avg response, daily sparkline,
  brand/source distribution, top VINs
- Timeline: merged signup + subscriptions + payments + recent queries
- User-scoped audit trail (AuditLog endpoint LIKE filter)
- POST /api/sase/users/[id]/reveal-email — audit-logged full email reveal

teknikborc.md — Phase 7 PRD vs Sase.tr şeması gap'leri kaydedildi
(EFT yok, B2B yok, API keys/webhooks yok, vb.)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 00:42:59 +03:00
Semih
f6727665a3 fix(reprocess): include 'analyzed' status in 'all' scope + new 'analyzed' scope
Previously 'all' missed status='analyzed', so re-running v2 prompts on existing
analyzed sessions returned 0 reset.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 11:15:37 +03:00
Semih
b38721bb05 feat(api): POST /api/insights/[id]/status (status + optional notes) 2026-05-14 11:58:30 +00:00
Semih
a713505f44 feat(insights): Telegram alerts + daily brief push notifications
apps/worker/src/lib/telegram.ts:
- sendTelegram() with Redis-backed dedupe (NX SETEX, 1h TTL)
- Helpers: alertP0Insight, alertRegression, alertSanitizationAnomaly, alertBudgetCap

Wired into:
- analyze.ts: P0/P1 insight creation → instant alert (dedupe per insight_id);
  budget guard halt → daily cap alert (dedupe per state per day)
- validation.ts: regression detected (≥3 sessions w/ same fingerprint after shippedAt)
  → alert (dedupe per insight_id)
- compress-sessions.ts: sanitization anomaly (>500 tokens, 0 PII matches)
  → alert (dedupe per session_id) — possible PII leak warning

Daily Brief (jobs/daily-brief.ts):
- Cron @05:00 UTC (= 08:00 Europe/Istanbul)
- 24h: sessions/insights/cost/cache-hit + 3 top priorities + 7d shipped/validated/regressed
- POST /api/insights/brief/send for manual trigger / smoke test

Env: TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID, PANEL_PUBLIC_URL (Coolify both apps).
Bot: @Pl24_mitm_bot (AiFactory), chat 7840804807. Source: airflow3 monitoring DAG.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 11:36:10 +00:00
Semih
74f0ff4935 fix(ingest): rolling lookback + ongoing-aware watermark to prevent missed sessions
Bug: previous logic advanced watermark to the latest session start_time including
ongoing sessions. PostHog session_recordings filters by start_time, so once a
session was 'seen' as ongoing the watermark moved past its start time and the
session was never re-fetched after it ended. Today 4 auth sessions on
/dashboard/vehicles/* and /dashboard/search (07:17-07:40 UTC) were lost this way.

Fix:
1. ROLLING_LOOKBACK_MINUTES (default 60): every cycle queries date_from =
   min(watermark, now - 60min). Sessions that just finished get re-fetched
   regardless of watermark drift. Upsert dedupes.
2. Track earliestOngoingStart; cap watermark to (earliestOngoingStart - 1s)
   so subsequent cycles re-read that range.

Also added GET/DELETE /api/insights/watermark for manual reset (used to
trigger 24h backfill after this deploy).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 10:53:38 +00:00
Semih
dfc19c8f13 refactor(integrations): migrate issue tracker GitHub → Gitea
- apps/web/src/lib/gitea.ts: createIssue / getIssue / verifyWebhookSignature / buildIssueBody
  * Endpoint: git.semih.ai/api/v1 (configurable via GITEA_BASE_URL)
  * Auth: 'Authorization: token <PAT>' (Gitea convention)
  * Labels: Gitea expects numeric IDs not strings → ensureLabels() resolves/creates
    with color coding (P0/P1 red, P2 yellow, P3 green, type-* grey, default blue)
  * Webhook signature: X-Gitea-Signature (hex, no sha256= prefix)
- apps/worker/src/lib/gitea.ts: read-only getIssue() for sync polling
- _actions.ts + github-sync.ts now import from /lib/gitea
- Removed old apps/{web,worker}/.../lib/github.ts + /api/webhooks/github route
  (the receiver was already dead — sp.semih.ai is Tailscale-only)
- UI: 'GitHub' label → 'Gitea' on insight detail card
- github-sync job filters by githubIssueUrl.startsWith(GITEA_BASE_URL) so legacy
  GitHub-hosted insights (semihyesilyurt/sase.tr#20) stay frozen rather than
  collide with same-numbered Gitea issues at root/sase.tr.

Env migration (Coolify, panel-web + panel-worker):
- removed: GITHUB_TOKEN, GITHUB_REPO_SASE, GITHUB_WEBHOOK_SECRET
- added:   GITEA_TOKEN, GITEA_REPO_SASE=root/sase.tr, GITEA_BASE_URL=https://git.semih.ai

Provisioned Gitea PAT 'super-panel-insights' (scopes: write:repository + write:issue),
stored in Bitwarden.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 09:36:57 +00:00
Semih
b83d28c119 feat(eval): POST /api/insights/eval-sets thin wrapper (create + optional run)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 06:15:39 +00:00
Semih
0c6350db2d feat(insights): POST /api/insights/[id]/create-issue thin wrapper around server action
Useful for programmatic smoke tests and future automation. Auth-gated via underlying action.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 05:49:20 +00:00
Semih
c5acdfc8ec feat(phase6d): GitHub action loop — issue creation, webhook, validation cron, patterns view
Schema:
- Insight.+githubIssueNumber (user-visible #N, separate from id BigInt)

GitHub integration (apps/web/src/lib/github.ts):
- repoFor(projectKey): env-based GITHUB_REPO_<KEY>=owner/repo mapping
- createIssue / getIssue REST wrappers
- verifyWebhookSignature (HMAC-SHA256 timing-safe)
- buildIssueBody: renders structured markdown from insight + LLM body
  (hypothesis, reproduce steps, affected route/provider, quick/long fixes,
  suggested investigation, evidence links to panel, DoD checklist)

Server action createGithubIssueForInsight:
- Auth-gated, audited; idempotent (refuses if issue already exists)
- Labels: insight-driven, severity-<P>, type-<T>, <project>-pilot
- Sets status=in_backlog, stores githubIssueUrl/Id/Number/State

Webhook /api/webhooks/github:
- Signature verify with GITHUB_WEBHOOK_SECRET
- issues.closed → status=shipped + shippedAt + validationStartedAt
- issues.reopened → status=in_progress + clear validation state
- issues.opened → status=in_backlog
- PR linking placeholder (passthrough only for now)

Validation cron (worker, daily 5:00 UTC):
- For each insight in 'shipped' state:
  - Count sessions with same fingerprint after shippedAt
  - >= INSIGHT_REGRESSION_THRESHOLD (default 3) → status=regressed + regressionDetected=true
  - validationPeriodDays elapsed with no regression → status=validated + validatedAt

UI:
- Insight detail: GithubActions card — Create button (when no issue),
  external link + issue # + state (when present)
- New /insights/patterns page: clusters insights by type + affected_route/provider,
  shows ≥2-insight or ≥5-occurrence groups sorted by max severity
- Inbox header link to Patterns
- Cmd+K palette: Patterns entry

Env needed: GITHUB_TOKEN, GITHUB_REPO_SASE, GITHUB_WEBHOOK_SECRET.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 05:45:48 +00:00
Semih
7606e1b0bf feat(insights): /api/insights/reprocess auth-gated endpoint to re-run pipeline on existing sessions
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 22:26:13 +00:00
Semih
ed79f4eacd feat(phase3c): SSE /api/events/stream + live /events page 2026-05-13 11:06:32 +00:00
Semih
67a7c5b887 feat: phase 0 skeleton — next.js 16 + better-auth + prisma 2026-05-13 09:17:50 +00:00