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>
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>
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>
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>
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>
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>
apps/worker/src/lib/prompts.ts:
- SASE_CONTEXT'e Türkçe çıktı kuralı eklendi (tüm doğal dil alanları Türkçe,
enum/const/teknik tanımlayıcılar İngilizce kalır)
- 7 seed prompt v2'ye bump edildi (bug_triage, ux_friction, payment_issue,
onboarding_stuck, upgrade_hesitation, pattern_bundle, provider_quality)
- name suffix "v2 (TR)"
apps/worker/src/lib/seed-runtime.ts:
- Yeni version insert ederken, aynı tag'in eski active versiyonlarını otomatik
deactivate eder (publishPromptVersion ile aynı davranış)
apps/web/src/lib/gitea.ts (buildIssueBody):
- Tüm bölüm başlıkları Türkçe: Bağlam, Hipotez, Kullanıcı niyeti, Friction
noktası, Tekrarlama adımları, Etkilenen alan, Hata imzası, Şüpheli provider,
Hata modu, Hızlı çözüm, Uzun vadeli çözüm, Önerilen aksiyon, Araştırma
önerisi, Tıkanma noktası, Anlaşılmayan kavram, Dokümantasyon eksiği,
Önerilen in-app yardım, Tahmini efor, Kanıt, Definition of Done
- Önem/Tekrar/İlk/Son görüldü etiketleri
Sonraki analyze döngüsü itibarıyla yeni insight'ların metin alanları Türkçe.
Mevcut insight #cmp4o*, #cmp5e* (v1 ile üretilmiş) değişmez.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
apps/web/src/lib/telegram.ts: server-side Telegram client + alertIssueCreated.
createGithubIssueForInsight action fires the alert after issue is created (after
audit log, before revalidate). Severity emoji + insight link + issue link.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
8-item insight category covering pilot success closure, push-notification
gap, Sase.tr Faz 0+1 prerequisites that unblock 9 dormant tag rules, eval
golden set requirement, and deferred volume-dependent work (bundle mode,
embedding similarity, multi-project routing).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
7 new event names added to TRACKED_EVENTS so enrich.ts fetches them for compression
timeline interleaving + tagger context. No new tag rules yet (Faz 2 is enrichment,
not signal extraction).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Webhook can't reach sp.semih.ai from internet (Tailscale-only DNS). Switched to a
worker job that polls GitHub for tracked issue states every 10 minutes.
apps/worker/src/lib/github.ts: read-only getIssue() client
apps/worker/src/jobs/github-sync.ts: scan insights with githubIssueNumber + not validated/dismissed/duplicate,
fetch remote state, reflect transitions:
open → closed = shipped + shippedAt + validationStartedAt
closed → open = in_progress + clear validation state
Scheduler: github-sync@*/10min added.
The /api/webhooks/github route stays (works if reachable in future) but is no longer
the source of truth.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- thinking={type:disabled} skips reasoning trace → output_tokens drop 10x
- find first content block of type='text' (V4 may emit thinking before text)
- DEEPSEEK_THINKING=true env to re-enable if needed for hard problems
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- prisma/sase/schema.prisma with User + UserSubscription (partial view)
- lib/db-sase.ts exports typed RO client
- /projects/sase shows live metrics (users count, new users 7d, active subs)
- seed marks sase as active
DATABASE_URL_SASE_RO wired to super_panel_reader (SELECT-only)