/** * Thread Supabase Service * * Provides Supabase CRUD operations for the threads table. * Supabase is the source of truth; local .gitmem/threads.json is a cache. * * Uses directQuery/directUpsert (PostgREST) like other Supabase operations * in this codebase. Graceful fallback: if Supabase is unreachable, callers * fall back to local file operations. */ import type { LifecycleStatus } from "./thread-vitality.js"; import type { ThreadWithEmbedding } from "./thread-dedup.js"; import type { ThreadScope, ThreadVisibility, ThreadScopeCounts } from "./thread-scope.js"; import type { ThreadObject, Project } from "../types/index.js"; /** Shape of a row in threads / threads_lite */ export interface ThreadRow { id: string; thread_id: string; text: string; status: string; thread_class: string; vitality_score: number; last_touched_at: string; touch_count: number; created_at: string; updated_at: string; resolved_at: string | null; resolution_note: string | null; source_session: string | null; resolved_by_session: string | null; related_issues: string[] | null; domain: string[] | null; project: string; metadata: Record; } /** Display-enriched thread info for session_start (Phase 6) */ export interface ThreadDisplayInfo { thread: ThreadObject; vitality_score: number; lifecycle_status: LifecycleStatus; thread_class: string; days_since_touch: number; } export type { ThreadScopeCounts } from "./thread-scope.js"; /** * Outcome of a thread sync, so a caller can decide whether it is safe to * discard local state (GIT-69 item 3). `skipped` means Supabase is not this * tier's SOT — nothing to sync, which is success, not failure. */ /** Ownership facts about a thread, used to explain refusals (GIT-69 item 4). */ export interface ThreadOwnership { thread_id: string; project: string; source_session: string | null; status: string; } /** * Whether the dedup candidate set for a sync was complete (GIT-70). * * `partial` and `unavailable` do not fail the sync — every thread still syncs * by identity. They mean a duplicate MAY have been created, which the * all_synced gate cannot detect: from the sync's perspective, writing a * duplicate row is a successful write. */ export type DedupCoverage = "complete" | "partial" | "unavailable"; export interface ThreadSyncResult { attempted: number; synced: string[]; failed: { id: string; error: string; }[]; skipped: boolean; all_synced: boolean; /** * GIT-70: how complete the duplicate check was. Distinct from all_synced, * which reports whether writes landed — not whether they were correct. */ dedup_coverage: DedupCoverage; /** Candidate rows actually considered, so the coverage claim is auditable. */ dedup_candidates: number; } export interface ActiveThreadsResult { open: ThreadObject[]; displayInfo: ThreadDisplayInfo[]; scope: ThreadScopeCounts; } /** * Map a local ThreadObject to a Supabase row for insert/upsert. */ export declare function threadObjectToRow(thread: ThreadObject, project?: Project, embedding?: string | null): Record; /** * Map a Supabase row to a local ThreadObject. */ export declare function rowToThreadObject(row: ThreadRow): ThreadObject; /** * Create a thread in Supabase. * Returns the created row or null if Supabase is unavailable. */ export declare function createThreadInSupabase(thread: ThreadObject, project?: Project, embedding?: string | null): Promise; /** * Resolve a thread in Supabase by thread_id. * Updates status, resolved_at, resolution_note, resolved_by_session. * Returns true if update succeeded. */ export declare function resolveThreadInSupabase(threadId: string, options?: { resolvedAt?: string; resolutionNote?: string; resolvedBySession?: string; }): Promise; /** * Fetch a single thread from Supabase by its thread_id ("t-XXXX"). * * Used by resolve_thread's cross-session fallback (GIT-46): a thread can live * in the Supabase source-of-truth (and show up in list_threads) while being * absent from this session's local cache because another session created it. * * Deliberately NOT scoped by project — thread_id is globally unique, and * scoping would re-introduce the "can only resolve threads I own" constraint * this fallback exists to remove. * * Returns the mapped ThreadObject, or null if not found / Supabase unavailable. */ /** * Who owns a thread, regardless of the caller's scope (GIT-69 item 4). * * Deliberately UNSCOPED — this is the one lookup allowed to see across * projects, because its purpose is to explain a refusal rather than to grant * access. Per R9c: a miss must return ownership information where it exists * ("owned by session Y in project P") rather than a bare not-found, and a * cross-project resolve must be refused by name rather than silently missing. * * Callers must never use this to widen what they resolve — only to say why * they did not. */ export declare function getThreadOwnership(threadId: string): Promise; export declare function getThreadFromSupabaseById(threadId: string): Promise; /** * List threads from Supabase with project filter. * Uses threads_lite view (no embedding column). * Returns null if Supabase is unavailable (caller should fall back to local). */ export declare function listThreadsFromSupabase(scope: ThreadScope, options?: { statusFilter?: string; includeResolved?: boolean; }): Promise; /** * Load active (non-archived, non-resolved) threads from Supabase for session_start. * Uses threads_lite view ordered by vitality_score DESC. * Returns null if Supabase is unavailable. */ export declare function loadActiveThreadsFromSupabase(scope: ThreadScope): Promise; /** * Touch threads in Supabase — increment touch_count and update last_touched_at. * Called during session_close for threads that were referenced during the session. */ export declare function touchThreadsInSupabase(threadIds: string[]): Promise; /** * Batch create/update threads in Supabase. * Used by session_close to sync thread state. * New threads (not in Supabase) get created; existing threads get touched. */ export declare function syncThreadsToSupabase(threads: ThreadObject[], project?: Project, sessionId?: string): Promise; /** * Archive dormant threads that have been dormant for 30+ days. * Reads metadata.dormant_since to determine eligibility. * Called at session_start as fire-and-forget. */ export declare function archiveDormantThreads(project?: Project, dormantDays?: number): Promise<{ archived_count: number; archived_ids: string[]; }>; /** * Load open threads WITH embeddings from Supabase for dedup comparison. * Uses the full threads table (not _lite view) to include embedding column. * Returns null if Supabase is unavailable. */ export declare function loadOpenThreadEmbeddings(scope: ThreadScope, visibility?: ThreadVisibility): Promise; //# sourceMappingURL=thread-supabase.d.ts.map