meminbox 0.0.0-stage → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,156 @@
1
+ -- meminbox: initial schema
2
+ -- Design rule: `documents` is the source of truth (human-readable Markdown).
3
+ -- `document_chunks` (with embeddings) is a DERIVED index and can be dropped
4
+ -- and rebuilt at any time with `meminbox reindex`.
5
+
6
+ create extension if not exists vector;
7
+ create extension if not exists pg_trgm;
8
+
9
+ -- ---------------------------------------------------------------------------
10
+ -- projects: a namespace for a body of knowledge (one repo, one domain, ...)
11
+ -- ---------------------------------------------------------------------------
12
+ create table if not exists public.projects (
13
+ id uuid primary key default gen_random_uuid(),
14
+ slug text not null unique,
15
+ name text not null,
16
+ created_at timestamptz not null default now()
17
+ );
18
+
19
+ -- ---------------------------------------------------------------------------
20
+ -- documents: the source of truth
21
+ -- status:
22
+ -- inbox = written by an agent, not yet reviewed/promoted (the default)
23
+ -- active = trusted memory, returned by search by default
24
+ -- archived = retired; kept for history, excluded from search
25
+ -- provenance: who/what wrote this and from what, e.g.
26
+ -- {"agent": "claude-code", "session": "...", "source": "conversation", "uri": "..."}
27
+ -- ---------------------------------------------------------------------------
28
+ create table if not exists public.documents (
29
+ id uuid primary key default gen_random_uuid(),
30
+ project_id uuid not null references public.projects(id) on delete cascade,
31
+ title text not null,
32
+ content_md text not null,
33
+ status text not null default 'inbox' check (status in ('inbox', 'active', 'archived')),
34
+ source_agent text,
35
+ provenance jsonb not null default '{}'::jsonb,
36
+ version int not null default 1,
37
+ search_vector tsvector generated always as (
38
+ to_tsvector('simple', coalesce(title, '') || ' ' || coalesce(content_md, ''))
39
+ ) stored,
40
+ created_at timestamptz not null default now(),
41
+ updated_at timestamptz not null default now()
42
+ );
43
+
44
+ create index if not exists documents_project_status_idx
45
+ on public.documents (project_id, status);
46
+ create index if not exists documents_search_vector_idx
47
+ on public.documents using gin (search_vector);
48
+ -- trigram index so substring search also works for Japanese text
49
+ create index if not exists documents_content_trgm_idx
50
+ on public.documents using gin (content_md gin_trgm_ops);
51
+
52
+ -- ---------------------------------------------------------------------------
53
+ -- document_chunks: derived index for semantic search (rebuildable)
54
+ -- ---------------------------------------------------------------------------
55
+ create table if not exists public.document_chunks (
56
+ id uuid primary key default gen_random_uuid(),
57
+ document_id uuid not null references public.documents(id) on delete cascade,
58
+ chunk_index int not null,
59
+ content text not null,
60
+ embedding vector(1536),
61
+ created_at timestamptz not null default now(),
62
+ unique (document_id, chunk_index)
63
+ );
64
+
65
+ create index if not exists document_chunks_document_idx
66
+ on public.document_chunks (document_id);
67
+ create index if not exists document_chunks_embedding_idx
68
+ on public.document_chunks using hnsw (embedding vector_cosine_ops);
69
+
70
+ -- ---------------------------------------------------------------------------
71
+ -- audit_log: every write / promote / archive lands here
72
+ -- ---------------------------------------------------------------------------
73
+ create table if not exists public.audit_log (
74
+ id uuid primary key default gen_random_uuid(),
75
+ actor text,
76
+ action text not null,
77
+ document_id uuid references public.documents(id) on delete set null,
78
+ metadata jsonb not null default '{}'::jsonb,
79
+ created_at timestamptz not null default now()
80
+ );
81
+
82
+ -- ---------------------------------------------------------------------------
83
+ -- search helpers
84
+ -- ---------------------------------------------------------------------------
85
+
86
+ -- Keyword search: FTS rank, with an ILIKE fallback so Japanese substring
87
+ -- queries (no word boundaries) still hit.
88
+ create or replace function public.search_documents(
89
+ p_query text,
90
+ p_limit int default 10,
91
+ p_status text default 'active'
92
+ )
93
+ returns table (
94
+ id uuid,
95
+ project_id uuid,
96
+ title text,
97
+ content_md text,
98
+ status text,
99
+ rank real,
100
+ updated_at timestamptz
101
+ )
102
+ language sql stable
103
+ as $$
104
+ select d.id, d.project_id, d.title, d.content_md, d.status,
105
+ ts_rank(d.search_vector, websearch_to_tsquery('simple', p_query)) as rank,
106
+ d.updated_at
107
+ from public.documents d
108
+ where d.status = p_status
109
+ and (
110
+ d.search_vector @@ websearch_to_tsquery('simple', p_query)
111
+ or d.title ilike '%' || p_query || '%'
112
+ or d.content_md ilike '%' || p_query || '%'
113
+ )
114
+ order by rank desc, d.updated_at desc
115
+ limit p_limit;
116
+ $$;
117
+
118
+ -- Vector search over the derived chunks.
119
+ create or replace function public.match_chunks(
120
+ p_embedding vector(1536),
121
+ p_limit int default 10,
122
+ p_status text default 'active'
123
+ )
124
+ returns table (
125
+ document_id uuid,
126
+ project_id uuid,
127
+ title text,
128
+ chunk text,
129
+ similarity float
130
+ )
131
+ language sql stable
132
+ as $$
133
+ select c.document_id, d.project_id, d.title, c.content,
134
+ 1 - (c.embedding <=> p_embedding) as similarity
135
+ from public.document_chunks c
136
+ join public.documents d on d.id = c.document_id
137
+ where d.status = p_status
138
+ and c.embedding is not null
139
+ order by c.embedding <=> p_embedding
140
+ limit p_limit;
141
+ $$;
142
+
143
+ -- ---------------------------------------------------------------------------
144
+ -- Row Level Security: enabled with NO public policies on purpose.
145
+ -- All access in v0 goes through the service role key from the owner's own
146
+ -- machines (MCP server / CLI). If a hosted/remote endpoint is added later,
147
+ -- write explicit policies then — do not open these tables to anon.
148
+ -- ---------------------------------------------------------------------------
149
+ alter table public.projects enable row level security;
150
+ alter table public.documents enable row level security;
151
+ alter table public.document_chunks enable row level security;
152
+ alter table public.audit_log enable row level security;
153
+
154
+ insert into public.projects (slug, name)
155
+ values ('default', 'Default')
156
+ on conflict (slug) do nothing;