f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
761 lines
32 KiB
SQL
761 lines
32 KiB
SQL
-- Sessions table
|
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
id TEXT PRIMARY KEY,
|
|
project TEXT NOT NULL,
|
|
machine TEXT NOT NULL DEFAULT 'local',
|
|
agent TEXT NOT NULL DEFAULT 'claude',
|
|
first_message TEXT,
|
|
display_name TEXT,
|
|
session_name TEXT,
|
|
started_at TEXT,
|
|
ended_at TEXT,
|
|
message_count INTEGER NOT NULL DEFAULT 0,
|
|
user_message_count INTEGER NOT NULL DEFAULT 0,
|
|
file_path TEXT,
|
|
file_size INTEGER,
|
|
file_mtime INTEGER,
|
|
next_ordinal INTEGER NOT NULL DEFAULT 0,
|
|
last_entry_uuid TEXT,
|
|
file_inode INTEGER,
|
|
file_device INTEGER,
|
|
file_hash TEXT,
|
|
local_modified_at TEXT,
|
|
transcript_revision TEXT NOT NULL DEFAULT '0',
|
|
parent_session_id TEXT,
|
|
relationship_type TEXT NOT NULL DEFAULT '',
|
|
total_output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
peak_context_tokens INTEGER NOT NULL DEFAULT 0,
|
|
has_total_output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
has_peak_context_tokens INTEGER NOT NULL DEFAULT 0,
|
|
is_automated INTEGER NOT NULL DEFAULT 0,
|
|
tool_failure_signal_count INTEGER NOT NULL DEFAULT 0,
|
|
tool_retry_count INTEGER NOT NULL DEFAULT 0,
|
|
edit_churn_count INTEGER NOT NULL DEFAULT 0,
|
|
consecutive_failure_max INTEGER NOT NULL DEFAULT 0,
|
|
outcome TEXT NOT NULL DEFAULT 'unknown',
|
|
outcome_confidence TEXT NOT NULL DEFAULT 'low',
|
|
ended_with_role TEXT NOT NULL DEFAULT '',
|
|
final_failure_streak INTEGER NOT NULL DEFAULT 0,
|
|
signals_pending_since TEXT,
|
|
compaction_count INTEGER NOT NULL DEFAULT 0,
|
|
mid_task_compaction_count INTEGER NOT NULL DEFAULT 0,
|
|
context_pressure_max REAL,
|
|
health_score INTEGER,
|
|
health_grade TEXT,
|
|
has_tool_calls INTEGER NOT NULL DEFAULT 0,
|
|
has_context_data INTEGER NOT NULL DEFAULT 0,
|
|
quality_signal_version INTEGER NOT NULL DEFAULT 0,
|
|
short_prompt_count INTEGER NOT NULL DEFAULT 0,
|
|
unstructured_start INTEGER NOT NULL DEFAULT 0,
|
|
missing_success_criteria_count INTEGER NOT NULL DEFAULT 0,
|
|
missing_verification_count INTEGER NOT NULL DEFAULT 0,
|
|
duplicate_prompt_count INTEGER NOT NULL DEFAULT 0,
|
|
no_code_context_count INTEGER NOT NULL DEFAULT 0,
|
|
runaway_tool_loop_count INTEGER NOT NULL DEFAULT 0,
|
|
data_version INTEGER NOT NULL DEFAULT 0,
|
|
cwd TEXT NOT NULL DEFAULT '',
|
|
git_branch TEXT NOT NULL DEFAULT '',
|
|
source_session_id TEXT NOT NULL DEFAULT '',
|
|
source_version TEXT NOT NULL DEFAULT '',
|
|
transcript_fidelity TEXT NOT NULL DEFAULT '',
|
|
parser_malformed_lines INTEGER NOT NULL DEFAULT 0,
|
|
is_truncated INTEGER NOT NULL DEFAULT 0,
|
|
-- SQLite-only sync bookkeeping (like next_ordinal): TRUE when the
|
|
-- last write to this row went through the incremental-append path
|
|
-- rather than a full re-normalization. Consumed only by parse-diff
|
|
-- to suppress benign incremental-vs-full skew; deliberately not
|
|
-- mirrored to PG/DuckDB.
|
|
last_write_incremental INTEGER NOT NULL DEFAULT 0,
|
|
deleted_at TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
termination_status TEXT,
|
|
secret_leak_count INTEGER NOT NULL DEFAULT 0,
|
|
secrets_rules_version TEXT NOT NULL DEFAULT ''
|
|
);
|
|
|
|
-- Messages table with ordinal for efficient range queries
|
|
CREATE TABLE IF NOT EXISTS messages (
|
|
id INTEGER PRIMARY KEY,
|
|
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
ordinal INTEGER NOT NULL,
|
|
role TEXT NOT NULL,
|
|
content TEXT NOT NULL,
|
|
thinking_text TEXT NOT NULL DEFAULT '',
|
|
timestamp TEXT,
|
|
has_thinking INTEGER NOT NULL DEFAULT 0,
|
|
has_tool_use INTEGER NOT NULL DEFAULT 0,
|
|
content_length INTEGER NOT NULL DEFAULT 0,
|
|
is_system INTEGER NOT NULL DEFAULT 0,
|
|
model TEXT NOT NULL DEFAULT '',
|
|
token_usage TEXT NOT NULL DEFAULT '',
|
|
context_tokens INTEGER NOT NULL DEFAULT 0,
|
|
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
has_context_tokens INTEGER NOT NULL DEFAULT 0,
|
|
has_output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
claude_message_id TEXT NOT NULL DEFAULT '',
|
|
claude_request_id TEXT NOT NULL DEFAULT '',
|
|
source_type TEXT NOT NULL DEFAULT '',
|
|
source_subtype TEXT NOT NULL DEFAULT '',
|
|
source_uuid TEXT NOT NULL DEFAULT '',
|
|
source_parent_uuid TEXT NOT NULL DEFAULT '',
|
|
is_sidechain INTEGER NOT NULL DEFAULT 0,
|
|
is_compact_boundary INTEGER NOT NULL DEFAULT 0,
|
|
UNIQUE(session_id, ordinal)
|
|
);
|
|
|
|
-- Stats table maintained by triggers
|
|
CREATE TABLE IF NOT EXISTS stats (
|
|
key TEXT PRIMARY KEY,
|
|
value INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
|
|
INSERT OR IGNORE INTO stats (key, value) VALUES ('session_count', 0);
|
|
INSERT OR IGNORE INTO stats (key, value) VALUES ('message_count', 0);
|
|
|
|
-- Triggers for stats maintenance
|
|
CREATE TRIGGER IF NOT EXISTS sessions_insert_stats AFTER INSERT ON sessions BEGIN
|
|
UPDATE stats SET value = value + 1 WHERE key = 'session_count';
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS sessions_delete_stats AFTER DELETE ON sessions BEGIN
|
|
UPDATE stats SET value = value - 1 WHERE key = 'session_count';
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS messages_insert_stats AFTER INSERT ON messages BEGIN
|
|
UPDATE stats SET value = value + 1 WHERE key = 'message_count';
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS messages_delete_stats AFTER DELETE ON messages BEGIN
|
|
UPDATE stats SET value = value - 1 WHERE key = 'message_count';
|
|
END;
|
|
|
|
-- Indexes
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_ended
|
|
ON sessions(ended_at DESC, id);
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_project
|
|
ON sessions(project);
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_machine
|
|
ON sessions(machine);
|
|
CREATE INDEX IF NOT EXISTS idx_messages_session_ordinal
|
|
ON messages(session_id, ordinal);
|
|
CREATE INDEX IF NOT EXISTS idx_messages_velocity
|
|
ON messages(session_id, ordinal, role, timestamp, content_length);
|
|
CREATE INDEX IF NOT EXISTS idx_messages_session_role
|
|
ON messages(session_id, role);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_parent
|
|
ON sessions(parent_session_id)
|
|
WHERE parent_session_id IS NOT NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_file_path
|
|
ON sessions(file_path)
|
|
WHERE file_path IS NOT NULL;
|
|
|
|
-- Analytics indexes
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_started
|
|
ON sessions(started_at);
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_message_count
|
|
ON sessions(message_count);
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_user_message_count
|
|
ON sessions(user_message_count);
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_agent
|
|
ON sessions(agent);
|
|
|
|
-- Session-level usage events. These complement message-level
|
|
-- messages.token_usage rows for agents that only expose aggregate
|
|
-- session accounting.
|
|
CREATE TABLE IF NOT EXISTS usage_events (
|
|
id INTEGER PRIMARY KEY,
|
|
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
message_ordinal INTEGER,
|
|
source TEXT NOT NULL,
|
|
model TEXT NOT NULL,
|
|
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
cache_creation_input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
cache_read_input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
reasoning_tokens INTEGER NOT NULL DEFAULT 0,
|
|
cost_usd REAL,
|
|
cost_status TEXT NOT NULL DEFAULT '',
|
|
cost_source TEXT NOT NULL DEFAULT '',
|
|
occurred_at TEXT,
|
|
dedup_key TEXT NOT NULL DEFAULT ''
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_usage_events_dedup
|
|
ON usage_events(session_id, source, dedup_key)
|
|
WHERE dedup_key != '';
|
|
CREATE INDEX IF NOT EXISTS idx_usage_events_session
|
|
ON usage_events(session_id);
|
|
CREATE INDEX IF NOT EXISTS idx_usage_events_occurred
|
|
ON usage_events(occurred_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS cursor_usage_events (
|
|
id INTEGER PRIMARY KEY,
|
|
occurred_at TEXT NOT NULL,
|
|
model TEXT NOT NULL,
|
|
kind TEXT NOT NULL DEFAULT '',
|
|
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
cache_write_tokens INTEGER NOT NULL DEFAULT 0,
|
|
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
charged_cents REAL NOT NULL DEFAULT 0,
|
|
cursor_token_fee REAL NOT NULL DEFAULT 0,
|
|
user_id TEXT NOT NULL DEFAULT '',
|
|
user_email TEXT NOT NULL DEFAULT '',
|
|
is_headless INTEGER NOT NULL DEFAULT 0,
|
|
dedup_key TEXT NOT NULL DEFAULT ''
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_cursor_usage_events_dedup
|
|
ON cursor_usage_events(dedup_key)
|
|
WHERE dedup_key != '';
|
|
CREATE INDEX IF NOT EXISTS idx_cursor_usage_events_occurred
|
|
ON cursor_usage_events(occurred_at);
|
|
CREATE INDEX IF NOT EXISTS idx_cursor_usage_events_model
|
|
ON cursor_usage_events(model);
|
|
|
|
-- Tool calls table
|
|
CREATE TABLE IF NOT EXISTS tool_calls (
|
|
id INTEGER PRIMARY KEY,
|
|
message_id INTEGER NOT NULL
|
|
REFERENCES messages(id) ON DELETE CASCADE,
|
|
session_id TEXT NOT NULL
|
|
REFERENCES sessions(id) ON DELETE CASCADE,
|
|
tool_name TEXT NOT NULL,
|
|
category TEXT NOT NULL,
|
|
tool_use_id TEXT,
|
|
input_json TEXT,
|
|
skill_name TEXT,
|
|
result_content_length INTEGER,
|
|
result_content TEXT,
|
|
subagent_session_id TEXT,
|
|
file_path TEXT,
|
|
call_index INTEGER
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tool_calls_session
|
|
ON tool_calls(session_id);
|
|
CREATE INDEX IF NOT EXISTS idx_tool_calls_session_category
|
|
ON tool_calls(session_id, category);
|
|
-- idx_tool_calls_message backs the ON DELETE CASCADE from
|
|
-- messages(id). Without it SQLite full-scans tool_calls per
|
|
-- deleted message row, which makes ReplaceSessionMessages
|
|
-- O(messages * tool_calls) and stalls sync once tool_calls
|
|
-- grows large.
|
|
CREATE INDEX IF NOT EXISTS idx_tool_calls_message
|
|
ON tool_calls(message_id);
|
|
CREATE INDEX IF NOT EXISTS idx_tool_calls_category
|
|
ON tool_calls(category);
|
|
CREATE INDEX IF NOT EXISTS idx_tool_calls_skill
|
|
ON tool_calls(skill_name)
|
|
WHERE skill_name IS NOT NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_tool_calls_subagent
|
|
ON tool_calls(subagent_session_id)
|
|
WHERE subagent_session_id IS NOT NULL;
|
|
|
|
-- Tool result events table: canonical chronological tool outputs.
|
|
CREATE TABLE IF NOT EXISTS tool_result_events (
|
|
id INTEGER PRIMARY KEY,
|
|
session_id TEXT NOT NULL
|
|
REFERENCES sessions(id) ON DELETE CASCADE,
|
|
tool_call_message_ordinal INTEGER NOT NULL,
|
|
call_index INTEGER NOT NULL DEFAULT 0,
|
|
tool_use_id TEXT,
|
|
agent_id TEXT,
|
|
subagent_session_id TEXT,
|
|
source TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
content TEXT NOT NULL,
|
|
content_length INTEGER NOT NULL DEFAULT 0,
|
|
timestamp TEXT,
|
|
event_index INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tool_result_events_session
|
|
ON tool_result_events(session_id);
|
|
CREATE INDEX IF NOT EXISTS idx_tool_result_events_call
|
|
ON tool_result_events(
|
|
session_id,
|
|
tool_call_message_ordinal,
|
|
call_index,
|
|
event_index
|
|
);
|
|
|
|
-- Insights table for AI-generated activity insights
|
|
CREATE TABLE IF NOT EXISTS insights (
|
|
id INTEGER PRIMARY KEY,
|
|
type TEXT NOT NULL,
|
|
date_from TEXT NOT NULL,
|
|
date_to TEXT NOT NULL,
|
|
project TEXT,
|
|
agent TEXT NOT NULL,
|
|
model TEXT,
|
|
prompt TEXT,
|
|
content TEXT NOT NULL,
|
|
kind TEXT NOT NULL DEFAULT '',
|
|
schema_version TEXT NOT NULL DEFAULT '',
|
|
template_id TEXT NOT NULL DEFAULT '',
|
|
template_version TEXT NOT NULL DEFAULT '',
|
|
aggregate_hash TEXT NOT NULL DEFAULT '',
|
|
cache_key TEXT NOT NULL DEFAULT '',
|
|
cache_status TEXT NOT NULL DEFAULT '',
|
|
provenance_json TEXT NOT NULL DEFAULT '',
|
|
structured_json TEXT NOT NULL DEFAULT '',
|
|
created_at TEXT NOT NULL
|
|
DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_insights_lookup
|
|
ON insights(type, date_from, date_to, project);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_insights_created
|
|
ON insights(created_at DESC);
|
|
|
|
-- Recall entries: reviewed, reusable facts learned from prior sessions.
|
|
-- These are not raw transcript chunks: each row is an accepted recall entry with
|
|
-- provenance back to the session archive.
|
|
CREATE TABLE IF NOT EXISTS recall_entries (
|
|
id TEXT PRIMARY KEY,
|
|
type TEXT NOT NULL,
|
|
scope TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'accepted',
|
|
review_state TEXT NOT NULL DEFAULT 'unreviewed_auto'
|
|
CHECK (review_state IN (
|
|
'human_reviewed', 'unreviewed_auto', 'calibrated_auto', 'eval_raw'
|
|
)),
|
|
title TEXT NOT NULL,
|
|
body TEXT NOT NULL,
|
|
trigger TEXT NOT NULL DEFAULT '',
|
|
confidence REAL,
|
|
uncertainty TEXT NOT NULL DEFAULT '',
|
|
project TEXT NOT NULL DEFAULT '',
|
|
cwd TEXT NOT NULL DEFAULT '',
|
|
git_branch TEXT NOT NULL DEFAULT '',
|
|
agent TEXT NOT NULL DEFAULT '',
|
|
source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
source_episode_id TEXT NOT NULL DEFAULT '',
|
|
source_run_id TEXT NOT NULL DEFAULT '',
|
|
extractor_method TEXT NOT NULL DEFAULT '',
|
|
model TEXT NOT NULL DEFAULT '',
|
|
transferable INTEGER NOT NULL DEFAULT 0,
|
|
provenance_ok INTEGER NOT NULL DEFAULT 0,
|
|
supersedes_entry_id TEXT NOT NULL DEFAULT '',
|
|
superseded_by_entry_id TEXT NOT NULL DEFAULT '',
|
|
created_at TEXT NOT NULL
|
|
DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
updated_at TEXT NOT NULL
|
|
DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_recall_entries_context
|
|
ON recall_entries(project, cwd, git_branch, agent);
|
|
CREATE INDEX IF NOT EXISTS idx_recall_entries_type_scope
|
|
ON recall_entries(type, scope, status);
|
|
CREATE INDEX IF NOT EXISTS idx_recall_entries_source_session
|
|
ON recall_entries(source_session_id);
|
|
CREATE INDEX IF NOT EXISTS idx_recall_entries_source_episode
|
|
ON recall_entries(source_episode_id);
|
|
CREATE INDEX IF NOT EXISTS idx_recall_entries_updated
|
|
ON recall_entries(updated_at DESC, id);
|
|
CREATE INDEX IF NOT EXISTS idx_recall_entries_supersession
|
|
ON recall_entries(supersedes_entry_id, superseded_by_entry_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS recall_evidence (
|
|
id INTEGER PRIMARY KEY,
|
|
entry_id TEXT NOT NULL
|
|
REFERENCES recall_entries(id) ON DELETE CASCADE,
|
|
session_id TEXT NOT NULL
|
|
REFERENCES sessions(id) ON DELETE CASCADE,
|
|
message_start_ordinal INTEGER NOT NULL,
|
|
message_end_ordinal INTEGER NOT NULL,
|
|
message_start_source_uuid TEXT NOT NULL DEFAULT '',
|
|
message_end_source_uuid TEXT NOT NULL DEFAULT '',
|
|
content_digest TEXT NOT NULL DEFAULT '',
|
|
tool_use_id TEXT NOT NULL DEFAULT '',
|
|
snippet TEXT NOT NULL DEFAULT ''
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_recall_evidence_entry
|
|
ON recall_evidence(entry_id);
|
|
CREATE INDEX IF NOT EXISTS idx_recall_evidence_session
|
|
ON recall_evidence(session_id);
|
|
|
|
-- Append-only demand and exposure snapshots. Exposures deliberately do not
|
|
-- reference recall_entries: measurements must survive recall/session deletion
|
|
-- and full resync even when an exposed entry no longer exists.
|
|
CREATE TABLE IF NOT EXISTS recall_query_events (
|
|
id TEXT PRIMARY KEY,
|
|
query_text TEXT NOT NULL,
|
|
surface TEXT NOT NULL,
|
|
filters_json TEXT NOT NULL DEFAULT '{}',
|
|
trusted_only INTEGER NOT NULL DEFAULT 0,
|
|
score_policy_version TEXT NOT NULL,
|
|
result_count INTEGER NOT NULL DEFAULT 0 CHECK (result_count >= 0),
|
|
packed_count INTEGER NOT NULL DEFAULT 0 CHECK (packed_count >= 0),
|
|
top_score REAL NOT NULL DEFAULT 0,
|
|
miss_reason TEXT NOT NULL DEFAULT '',
|
|
created_at TEXT NOT NULL
|
|
DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_recall_query_events_created
|
|
ON recall_query_events(created_at DESC, id);
|
|
CREATE INDEX IF NOT EXISTS idx_recall_query_events_surface
|
|
ON recall_query_events(surface, created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS recall_query_exposures (
|
|
query_id TEXT NOT NULL
|
|
REFERENCES recall_query_events(id) ON DELETE CASCADE,
|
|
rank INTEGER NOT NULL CHECK (rank >= 1),
|
|
entry_id TEXT NOT NULL,
|
|
score REAL NOT NULL,
|
|
packed INTEGER NOT NULL DEFAULT 0 CHECK (packed IN (0, 1)),
|
|
PRIMARY KEY (query_id, rank)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_recall_query_exposures_entry
|
|
ON recall_query_exposures(entry_id);
|
|
|
|
-- Pinned messages table
|
|
CREATE TABLE IF NOT EXISTS pinned_messages (
|
|
id INTEGER PRIMARY KEY,
|
|
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
ordinal INTEGER NOT NULL,
|
|
note TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
UNIQUE(session_id, message_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_pinned_session
|
|
ON pinned_messages(session_id);
|
|
-- idx_pinned_message backs the ON DELETE CASCADE from messages(id).
|
|
-- The UNIQUE(session_id, message_id) constraint creates an index
|
|
-- ordered (session_id, message_id), which the FK lookup on
|
|
-- message_id alone cannot use (leftmost-prefix rule).
|
|
CREATE INDEX IF NOT EXISTS idx_pinned_message
|
|
ON pinned_messages(message_id);
|
|
CREATE INDEX IF NOT EXISTS idx_pinned_created
|
|
ON pinned_messages(created_at DESC);
|
|
|
|
-- Starred sessions: persists user star/unstar decisions
|
|
CREATE TABLE IF NOT EXISTS starred_sessions (
|
|
session_id TEXT PRIMARY KEY REFERENCES sessions(id) ON DELETE CASCADE,
|
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
);
|
|
|
|
-- Excluded sessions: tracks session IDs that were permanently
|
|
-- deleted by the user so the sync engine does not re-import them.
|
|
CREATE TABLE IF NOT EXISTS excluded_sessions (
|
|
id TEXT PRIMARY KEY,
|
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
);
|
|
-- Skipped files cache: persists skip decisions for files that
|
|
-- produced no session (non-interactive, parse errors) so they
|
|
-- survive process restarts without re-parsing.
|
|
CREATE TABLE IF NOT EXISTS skipped_files (
|
|
file_path TEXT PRIMARY KEY,
|
|
file_mtime INTEGER NOT NULL
|
|
);
|
|
|
|
-- Remote skip cache: tracks file mtimes per remote host
|
|
-- for SSH sync incremental optimization.
|
|
CREATE TABLE IF NOT EXISTS remote_skipped_files (
|
|
host TEXT NOT NULL,
|
|
path TEXT NOT NULL,
|
|
file_mtime INTEGER NOT NULL,
|
|
PRIMARY KEY (host, path)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS worktree_project_mappings (
|
|
id INTEGER PRIMARY KEY,
|
|
machine TEXT NOT NULL,
|
|
path_prefix TEXT NOT NULL,
|
|
layout TEXT NOT NULL DEFAULT 'explicit',
|
|
project TEXT NOT NULL,
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
UNIQUE(machine, path_prefix)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_worktree_project_mappings_match
|
|
ON worktree_project_mappings(machine, enabled, path_prefix);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_worktree_project_mappings_project
|
|
ON worktree_project_mappings(machine, project);
|
|
|
|
CREATE TABLE IF NOT EXISTS archive_metadata (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL,
|
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS project_identity_observations (
|
|
session_id TEXT NOT NULL DEFAULT '',
|
|
source_archive_id TEXT NOT NULL DEFAULT '',
|
|
source_archive_salt TEXT NOT NULL DEFAULT '',
|
|
project TEXT NOT NULL,
|
|
machine TEXT NOT NULL,
|
|
root_path TEXT NOT NULL DEFAULT '',
|
|
git_remote TEXT NOT NULL DEFAULT '',
|
|
git_remote_name TEXT NOT NULL DEFAULT '',
|
|
repository_path TEXT NOT NULL DEFAULT '',
|
|
worktree_name TEXT NOT NULL DEFAULT '',
|
|
worktree_root_path TEXT NOT NULL DEFAULT '',
|
|
worktree_relationship TEXT NOT NULL DEFAULT 'unknown',
|
|
checkout_state TEXT NOT NULL DEFAULT 'unknown',
|
|
git_branch TEXT NOT NULL DEFAULT '',
|
|
remote_resolution TEXT NOT NULL DEFAULT 'unknown',
|
|
remote_candidate_count INTEGER NOT NULL DEFAULT 0,
|
|
observed_at TEXT NOT NULL,
|
|
normalized_remote TEXT NOT NULL DEFAULT '',
|
|
key_source TEXT NOT NULL DEFAULT '',
|
|
key TEXT NOT NULL DEFAULT '',
|
|
PRIMARY KEY (project, machine, root_path, git_remote)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_project_identity_observations_project
|
|
ON project_identity_observations(project);
|
|
|
|
CREATE TABLE IF NOT EXISTS session_project_identity_snapshots (
|
|
session_id TEXT PRIMARY KEY,
|
|
project TEXT NOT NULL,
|
|
machine TEXT NOT NULL,
|
|
root_path TEXT NOT NULL DEFAULT '',
|
|
git_remote TEXT NOT NULL DEFAULT '',
|
|
git_remote_name TEXT NOT NULL DEFAULT '',
|
|
repository_path TEXT NOT NULL DEFAULT '',
|
|
worktree_name TEXT NOT NULL DEFAULT '',
|
|
worktree_root_path TEXT NOT NULL DEFAULT '',
|
|
worktree_relationship TEXT NOT NULL DEFAULT 'unknown',
|
|
checkout_state TEXT NOT NULL DEFAULT 'unknown',
|
|
git_branch TEXT NOT NULL DEFAULT '',
|
|
remote_resolution TEXT NOT NULL DEFAULT 'unknown',
|
|
remote_candidate_count INTEGER NOT NULL DEFAULT 0,
|
|
observed_at TEXT NOT NULL,
|
|
normalized_remote TEXT NOT NULL DEFAULT '',
|
|
key_source TEXT NOT NULL DEFAULT '',
|
|
key TEXT NOT NULL DEFAULT '',
|
|
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS background_migrations (
|
|
name TEXT PRIMARY KEY,
|
|
state TEXT NOT NULL,
|
|
total_items INTEGER NOT NULL DEFAULT 0,
|
|
completed_items INTEGER NOT NULL DEFAULT 0,
|
|
last_error TEXT NOT NULL DEFAULT '',
|
|
started_at TEXT,
|
|
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
completed_at TEXT
|
|
);
|
|
|
|
-- Compact publication journals retain the latest change for each identity
|
|
-- key. They let mirror pushes publish bounded deltas while preserving
|
|
-- tombstones for targets that have been offline.
|
|
CREATE TABLE IF NOT EXISTS project_identity_observation_changes (
|
|
project TEXT NOT NULL,
|
|
machine TEXT NOT NULL,
|
|
root_path TEXT NOT NULL DEFAULT '',
|
|
git_remote TEXT NOT NULL DEFAULT '',
|
|
revision INTEGER NOT NULL,
|
|
deleted INTEGER NOT NULL DEFAULT 0 CHECK (deleted IN (0, 1)),
|
|
PRIMARY KEY (project, machine, root_path, git_remote)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_project_identity_observation_changes_revision
|
|
ON project_identity_observation_changes(revision);
|
|
|
|
CREATE TABLE IF NOT EXISTS session_project_identity_snapshot_changes (
|
|
session_id TEXT NOT NULL,
|
|
project TEXT NOT NULL,
|
|
revision INTEGER NOT NULL,
|
|
deleted INTEGER NOT NULL DEFAULT 0 CHECK (deleted IN (0, 1)),
|
|
PRIMARY KEY (session_id, project)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_session_project_identity_snapshot_changes_revision
|
|
ON session_project_identity_snapshot_changes(revision);
|
|
|
|
DROP TRIGGER IF EXISTS trg_project_identity_observations_revision_insert;
|
|
DROP TRIGGER IF EXISTS trg_project_identity_observations_revision_update;
|
|
DROP TRIGGER IF EXISTS trg_project_identity_observations_revision_delete;
|
|
DROP TRIGGER IF EXISTS trg_session_project_identity_snapshots_revision_insert;
|
|
DROP TRIGGER IF EXISTS trg_session_project_identity_snapshots_revision_update;
|
|
DROP TRIGGER IF EXISTS trg_session_project_identity_snapshots_revision_delete;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS trg_project_identity_observations_revision_insert
|
|
AFTER INSERT ON project_identity_observations BEGIN
|
|
INSERT INTO archive_metadata (key, value) VALUES ('project_identity_publication_revision', '1')
|
|
ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT),
|
|
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now');
|
|
INSERT INTO project_identity_observation_changes (
|
|
project, machine, root_path, git_remote, revision, deleted
|
|
) VALUES (
|
|
NEW.project, NEW.machine, NEW.root_path, NEW.git_remote,
|
|
(SELECT CAST(value AS INTEGER) FROM archive_metadata
|
|
WHERE key = 'project_identity_publication_revision'), 0
|
|
) ON CONFLICT(project, machine, root_path, git_remote) DO UPDATE SET
|
|
revision = excluded.revision, deleted = 0;
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS trg_project_identity_observations_revision_update
|
|
AFTER UPDATE ON project_identity_observations BEGIN
|
|
INSERT INTO archive_metadata (key, value) VALUES ('project_identity_publication_revision', '1')
|
|
ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT),
|
|
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now');
|
|
INSERT INTO project_identity_observation_changes (
|
|
project, machine, root_path, git_remote, revision, deleted
|
|
) VALUES (
|
|
OLD.project, OLD.machine, OLD.root_path, OLD.git_remote,
|
|
(SELECT CAST(value AS INTEGER) FROM archive_metadata
|
|
WHERE key = 'project_identity_publication_revision'), 1
|
|
) ON CONFLICT(project, machine, root_path, git_remote) DO UPDATE SET
|
|
revision = excluded.revision, deleted = 1;
|
|
INSERT INTO project_identity_observation_changes (
|
|
project, machine, root_path, git_remote, revision, deleted
|
|
) VALUES (
|
|
NEW.project, NEW.machine, NEW.root_path, NEW.git_remote,
|
|
(SELECT CAST(value AS INTEGER) FROM archive_metadata
|
|
WHERE key = 'project_identity_publication_revision'), 0
|
|
) ON CONFLICT(project, machine, root_path, git_remote) DO UPDATE SET
|
|
revision = excluded.revision, deleted = 0;
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS trg_project_identity_observations_revision_delete
|
|
AFTER DELETE ON project_identity_observations BEGIN
|
|
INSERT INTO archive_metadata (key, value) VALUES ('project_identity_publication_revision', '1')
|
|
ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT),
|
|
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now');
|
|
INSERT INTO project_identity_observation_changes (
|
|
project, machine, root_path, git_remote, revision, deleted
|
|
) VALUES (
|
|
OLD.project, OLD.machine, OLD.root_path, OLD.git_remote,
|
|
(SELECT CAST(value AS INTEGER) FROM archive_metadata
|
|
WHERE key = 'project_identity_publication_revision'), 1
|
|
) ON CONFLICT(project, machine, root_path, git_remote) DO UPDATE SET
|
|
revision = excluded.revision, deleted = 1;
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS trg_session_project_identity_snapshots_revision_insert
|
|
AFTER INSERT ON session_project_identity_snapshots BEGIN
|
|
INSERT INTO archive_metadata (key, value) VALUES ('project_identity_publication_revision', '1')
|
|
ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT),
|
|
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now');
|
|
INSERT INTO session_project_identity_snapshot_changes (
|
|
session_id, project, revision, deleted
|
|
) VALUES (
|
|
NEW.session_id, NEW.project,
|
|
(SELECT CAST(value AS INTEGER) FROM archive_metadata
|
|
WHERE key = 'project_identity_publication_revision'), 0
|
|
) ON CONFLICT(session_id, project) DO UPDATE SET
|
|
revision = excluded.revision, deleted = 0;
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS trg_session_project_identity_snapshots_revision_update
|
|
AFTER UPDATE ON session_project_identity_snapshots BEGIN
|
|
INSERT INTO archive_metadata (key, value) VALUES ('project_identity_publication_revision', '1')
|
|
ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT),
|
|
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now');
|
|
INSERT INTO session_project_identity_snapshot_changes (
|
|
session_id, project, revision, deleted
|
|
) VALUES (
|
|
OLD.session_id, OLD.project,
|
|
(SELECT CAST(value AS INTEGER) FROM archive_metadata
|
|
WHERE key = 'project_identity_publication_revision'), 1
|
|
) ON CONFLICT(session_id, project) DO UPDATE SET
|
|
revision = excluded.revision, deleted = 1;
|
|
INSERT INTO session_project_identity_snapshot_changes (
|
|
session_id, project, revision, deleted
|
|
) VALUES (
|
|
NEW.session_id, NEW.project,
|
|
(SELECT CAST(value AS INTEGER) FROM archive_metadata
|
|
WHERE key = 'project_identity_publication_revision'), 0
|
|
) ON CONFLICT(session_id, project) DO UPDATE SET
|
|
revision = excluded.revision, deleted = 0;
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS trg_session_project_identity_snapshots_revision_delete
|
|
AFTER DELETE ON session_project_identity_snapshots BEGIN
|
|
INSERT INTO archive_metadata (key, value) VALUES ('project_identity_publication_revision', '1')
|
|
ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT),
|
|
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now');
|
|
INSERT INTO session_project_identity_snapshot_changes (
|
|
session_id, project, revision, deleted
|
|
) VALUES (
|
|
OLD.session_id, OLD.project,
|
|
(SELECT CAST(value AS INTEGER) FROM archive_metadata
|
|
WHERE key = 'project_identity_publication_revision'), 1
|
|
) ON CONFLICT(session_id, project) DO UPDATE SET
|
|
revision = excluded.revision, deleted = 1;
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS trg_sessions_create_project_identity_snapshot
|
|
AFTER INSERT ON sessions BEGIN
|
|
INSERT INTO session_project_identity_snapshots (
|
|
session_id, project, machine, root_path, worktree_relationship,
|
|
checkout_state, git_branch, remote_resolution, observed_at
|
|
) VALUES (
|
|
NEW.id, NEW.project, NEW.machine, NEW.cwd, 'unknown',
|
|
CASE WHEN NEW.git_branch != '' THEN 'branch' ELSE 'unknown' END,
|
|
NEW.git_branch, 'unknown', strftime('%Y-%m-%dT%H:%M:%fZ','now')
|
|
) ON CONFLICT(session_id) DO NOTHING;
|
|
END;
|
|
|
|
-- PG sync state: stores watermarks for push sync
|
|
CREATE TABLE IF NOT EXISTS pg_sync_state (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL
|
|
);
|
|
|
|
-- Model pricing for cost calculation
|
|
CREATE TABLE IF NOT EXISTS model_pricing (
|
|
model_pattern TEXT PRIMARY KEY,
|
|
input_per_mtok REAL NOT NULL DEFAULT 0,
|
|
output_per_mtok REAL NOT NULL DEFAULT 0,
|
|
cache_creation_per_mtok REAL NOT NULL DEFAULT 0,
|
|
cache_read_per_mtok REAL NOT NULL DEFAULT 0,
|
|
updated_at TEXT NOT NULL
|
|
DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
);
|
|
|
|
-- Git aggregation TTL cache: memoizes `git log --numstat` and
|
|
-- `gh pr list` results per (repo, author, window) tuple so
|
|
-- repeated `agentsview stats` invocations don't re-shell out.
|
|
CREATE TABLE IF NOT EXISTS git_cache (
|
|
cache_key TEXT PRIMARY KEY, -- sha256(repo|author|since|until|kind)
|
|
kind TEXT NOT NULL, -- 'log' | 'pr'
|
|
payload TEXT NOT NULL, -- JSON-encoded result
|
|
computed_at TEXT NOT NULL -- RFC3339
|
|
);
|
|
|
|
-- Secret findings: persisted detections from internal/secrets.
|
|
-- Located by natural coordinates (no row IDs) so findings survive the
|
|
-- full-resync orphan copy. Only redacted values are stored.
|
|
CREATE TABLE IF NOT EXISTS secret_findings (
|
|
id INTEGER PRIMARY KEY,
|
|
session_id TEXT NOT NULL
|
|
REFERENCES sessions(id) ON DELETE CASCADE,
|
|
rule_name TEXT NOT NULL,
|
|
confidence TEXT NOT NULL,
|
|
location_kind TEXT NOT NULL,
|
|
message_ordinal INTEGER NOT NULL,
|
|
call_index INTEGER,
|
|
event_index INTEGER,
|
|
match_start INTEGER NOT NULL,
|
|
match_end INTEGER NOT NULL,
|
|
match_index INTEGER NOT NULL,
|
|
redacted_match TEXT NOT NULL,
|
|
rules_version TEXT NOT NULL,
|
|
created_at TEXT NOT NULL
|
|
DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_secret_findings_session
|
|
ON secret_findings(session_id);
|
|
CREATE INDEX IF NOT EXISTS idx_secret_findings_rule
|
|
ON secret_findings(rule_name);
|