@codified/cli 0.1.0
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.
- package/assets/docker/docker-compose.yml +34 -0
- package/assets/docker/init/01-extensions.sql +10 -0
- package/assets/docker/postgres/Dockerfile +33 -0
- package/assets/migrations/001_init.sql +391 -0
- package/assets/migrations/002_change_embedding_dim.sql +12 -0
- package/dist/index.js +750 -0
- package/package.json +51 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
build:
|
|
4
|
+
context: ./postgres
|
|
5
|
+
dockerfile: Dockerfile
|
|
6
|
+
environment:
|
|
7
|
+
POSTGRES_USER: codify
|
|
8
|
+
POSTGRES_PASSWORD: codify
|
|
9
|
+
POSTGRES_DB: codify
|
|
10
|
+
ports:
|
|
11
|
+
- "5432:5432"
|
|
12
|
+
volumes:
|
|
13
|
+
- postgres-data:/var/lib/postgresql/data
|
|
14
|
+
- ./init:/docker-entrypoint-initdb.d
|
|
15
|
+
healthcheck:
|
|
16
|
+
test: ["CMD-SHELL", "pg_isready -U codify"]
|
|
17
|
+
interval: 5s
|
|
18
|
+
timeout: 5s
|
|
19
|
+
retries: 5
|
|
20
|
+
restart: unless-stopped
|
|
21
|
+
|
|
22
|
+
nats:
|
|
23
|
+
image: nats:2.11-alpine
|
|
24
|
+
command: --jetstream --store_dir /data
|
|
25
|
+
ports:
|
|
26
|
+
- "4222:4222"
|
|
27
|
+
- "8222:8222"
|
|
28
|
+
volumes:
|
|
29
|
+
- nats-data:/data
|
|
30
|
+
restart: unless-stopped
|
|
31
|
+
|
|
32
|
+
volumes:
|
|
33
|
+
postgres-data:
|
|
34
|
+
nats-data:
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Enable required PostgreSQL extensions
|
|
2
|
+
CREATE EXTENSION IF NOT EXISTS age;
|
|
3
|
+
CREATE EXTENSION IF NOT EXISTS vector;
|
|
4
|
+
|
|
5
|
+
-- Load Apache AGE
|
|
6
|
+
LOAD 'age';
|
|
7
|
+
SET search_path = ag_catalog, "$user", public;
|
|
8
|
+
|
|
9
|
+
-- Create the context graph
|
|
10
|
+
SELECT create_graph('context_graph');
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# PostgreSQL 16 with pgvector + Apache AGE extensions
|
|
2
|
+
# Start from pgvector (stable), add AGE from source
|
|
3
|
+
|
|
4
|
+
FROM pgvector/pgvector:pg16
|
|
5
|
+
|
|
6
|
+
USER root
|
|
7
|
+
|
|
8
|
+
# Install build dependencies for AGE
|
|
9
|
+
RUN apt-get update && \
|
|
10
|
+
apt-get install -y --no-install-recommends \
|
|
11
|
+
build-essential \
|
|
12
|
+
libreadline-dev \
|
|
13
|
+
zlib1g-dev \
|
|
14
|
+
flex \
|
|
15
|
+
bison \
|
|
16
|
+
git \
|
|
17
|
+
ca-certificates \
|
|
18
|
+
postgresql-server-dev-16 && \
|
|
19
|
+
# Build and install Apache AGE
|
|
20
|
+
cd /tmp && \
|
|
21
|
+
git clone --branch release/PG16/1.6.0 https://github.com/apache/age.git && \
|
|
22
|
+
cd age && \
|
|
23
|
+
make && \
|
|
24
|
+
make install && \
|
|
25
|
+
# Cleanup
|
|
26
|
+
cd / && \
|
|
27
|
+
rm -rf /tmp/age && \
|
|
28
|
+
apt-get purge -y build-essential libreadline-dev zlib1g-dev flex bison git postgresql-server-dev-16 && \
|
|
29
|
+
apt-get autoremove -y && \
|
|
30
|
+
apt-get clean && \
|
|
31
|
+
rm -rf /var/lib/apt/lists/*
|
|
32
|
+
|
|
33
|
+
USER postgres
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
-- =============================================================================
|
|
2
|
+
-- Codify Context Graph: Initial Schema Migration
|
|
3
|
+
-- =============================================================================
|
|
4
|
+
-- IMPORTANT: AGE search_path operations are at the END of this file.
|
|
5
|
+
-- Table creation happens first in the default (public) schema.
|
|
6
|
+
-- =============================================================================
|
|
7
|
+
|
|
8
|
+
-- ---------------------------------------------------------------------------
|
|
9
|
+
-- 1. Extensions (safe to re-run)
|
|
10
|
+
-- ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
CREATE EXTENSION IF NOT EXISTS age;
|
|
13
|
+
CREATE EXTENSION IF NOT EXISTS vector;
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
-- ---------------------------------------------------------------------------
|
|
17
|
+
-- 2. Node Type Registry
|
|
18
|
+
-- ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
CREATE TABLE IF NOT EXISTS node_types (
|
|
21
|
+
name TEXT PRIMARY KEY,
|
|
22
|
+
schema JSONB NOT NULL DEFAULT '{}',
|
|
23
|
+
parent_type TEXT REFERENCES node_types(name),
|
|
24
|
+
description TEXT,
|
|
25
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
26
|
+
version INT NOT NULL DEFAULT 1
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
INSERT INTO node_types (name, description) VALUES
|
|
30
|
+
('Decision', 'A choice made by a person or team, with provenance'),
|
|
31
|
+
('Feature', 'A product capability — proposed through shipped'),
|
|
32
|
+
('Metric', 'A measurable quantity the organization tracks'),
|
|
33
|
+
('CustomerSignal', 'Signal from a customer — feedback, ticket, churn indicator'),
|
|
34
|
+
('CodeArtifact', 'A code-level entity — PR, commit, file, service, deploy'),
|
|
35
|
+
('Discussion', 'A conversation — Slack, meeting, doc comment, email'),
|
|
36
|
+
('Gap', 'A detected hole, staleness, or contradiction in the graph'),
|
|
37
|
+
('Study', 'A research study tied to Codify interview system'),
|
|
38
|
+
('Person', 'A human in or around the organization')
|
|
39
|
+
ON CONFLICT (name) DO NOTHING;
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
-- ---------------------------------------------------------------------------
|
|
43
|
+
-- 3. Edge Type Registry
|
|
44
|
+
-- ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
CREATE TABLE IF NOT EXISTS edge_types (
|
|
47
|
+
name TEXT PRIMARY KEY,
|
|
48
|
+
source_types TEXT[] NOT NULL DEFAULT '{}',
|
|
49
|
+
target_types TEXT[] NOT NULL DEFAULT '{}',
|
|
50
|
+
schema JSONB NOT NULL DEFAULT '{}',
|
|
51
|
+
description TEXT,
|
|
52
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
53
|
+
version INT NOT NULL DEFAULT 1
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
INSERT INTO edge_types (name, description) VALUES
|
|
57
|
+
('caused_by', 'A was caused by B'),
|
|
58
|
+
('informed_by', 'A was informed by B'),
|
|
59
|
+
('derived_from', 'A was derived/computed from B'),
|
|
60
|
+
('evidence_for', 'A provides evidence supporting B'),
|
|
61
|
+
('contradicts', 'A and B are in conflict'),
|
|
62
|
+
('validates', 'A validates/confirms B'),
|
|
63
|
+
('invalidates', 'A disproves B'),
|
|
64
|
+
('resolved_by', 'Gap/contradiction A was resolved by B'),
|
|
65
|
+
('supersedes', 'A replaces B as the current truth'),
|
|
66
|
+
('blocks', 'A blocks progress on B'),
|
|
67
|
+
('depends_on', 'A depends on B'),
|
|
68
|
+
('part_of', 'A is a component of B'),
|
|
69
|
+
('owned_by', 'A is owned/authored by B'),
|
|
70
|
+
('participated_in', 'Person A participated in B'),
|
|
71
|
+
('evolved_from', 'A is an evolution of B'),
|
|
72
|
+
('preceded_by', 'A happened after B (temporal)'),
|
|
73
|
+
('deprioritized_because', 'A was deprioritized due to B'),
|
|
74
|
+
('triggered', 'A triggered the creation of B'),
|
|
75
|
+
('fills_gap', 'Study/signal A fills Gap B'),
|
|
76
|
+
('collected_for', 'Signal A was collected for Study B'),
|
|
77
|
+
('assigned_to', 'Gap A assigned to agent/person B'),
|
|
78
|
+
('authored_by', 'A was authored/created by person B'),
|
|
79
|
+
('contributed_to', 'A contributed to artifact/decision B'),
|
|
80
|
+
('implements', 'A implements feature/decision B'),
|
|
81
|
+
('relates_to', 'General relationship between A and B'),
|
|
82
|
+
('addresses', 'A addresses gap/issue B'),
|
|
83
|
+
('discussed_in', 'A was discussed in discussion B'),
|
|
84
|
+
('measured_by', 'A is measured by metric B'),
|
|
85
|
+
('blocked_by', 'A is blocked by issue/dependency B'),
|
|
86
|
+
('mentions', 'A mentions entity B'),
|
|
87
|
+
('corroborates', 'A corroborates/supports claim B'),
|
|
88
|
+
('refines', 'A refines or improves upon B'),
|
|
89
|
+
('competed_by', 'A has competition/alternative B'),
|
|
90
|
+
('precedes', 'A came before/preceded B')
|
|
91
|
+
ON CONFLICT (name) DO NOTHING;
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
-- ---------------------------------------------------------------------------
|
|
95
|
+
-- 4. Branches (must exist before nodes/edges reference them)
|
|
96
|
+
-- ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
CREATE TABLE IF NOT EXISTS branches (
|
|
99
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
100
|
+
name TEXT NOT NULL,
|
|
101
|
+
parent_branch UUID REFERENCES branches(id),
|
|
102
|
+
branch_point TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
103
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
104
|
+
CHECK (status IN ('active', 'merged', 'pruned', 'archived')),
|
|
105
|
+
created_by UUID,
|
|
106
|
+
purpose TEXT NOT NULL DEFAULT '',
|
|
107
|
+
merge_policy TEXT NOT NULL DEFAULT 'manual'
|
|
108
|
+
CHECK (merge_policy IN ('manual', 'auto_on_confidence', 'auto_on_approval')),
|
|
109
|
+
prune_reason TEXT,
|
|
110
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
-- ---------------------------------------------------------------------------
|
|
115
|
+
-- 5. Nodes
|
|
116
|
+
-- ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
CREATE TABLE IF NOT EXISTS nodes (
|
|
119
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
120
|
+
type TEXT NOT NULL REFERENCES node_types(name),
|
|
121
|
+
layer TEXT NOT NULL DEFAULT 'permanent'
|
|
122
|
+
CHECK (layer IN ('ephemeral', 'hot', 'draft', 'permanent')),
|
|
123
|
+
branch_id UUID REFERENCES branches(id),
|
|
124
|
+
confidence REAL NOT NULL DEFAULT 0.5 CHECK (confidence >= 0 AND confidence <= 1),
|
|
125
|
+
freshness REAL NOT NULL DEFAULT 1.0 CHECK (freshness >= 0 AND freshness <= 1),
|
|
126
|
+
version INT NOT NULL DEFAULT 1,
|
|
127
|
+
superseded_by UUID,
|
|
128
|
+
source_system TEXT NOT NULL DEFAULT 'manual',
|
|
129
|
+
source_reference TEXT NOT NULL DEFAULT '',
|
|
130
|
+
source_agent_id TEXT,
|
|
131
|
+
source_reliability REAL NOT NULL DEFAULT 0.7 CHECK (source_reliability >= 0 AND source_reliability <= 1),
|
|
132
|
+
properties JSONB NOT NULL DEFAULT '{}',
|
|
133
|
+
embedding vector(1024),
|
|
134
|
+
tags TEXT[] NOT NULL DEFAULT '{}',
|
|
135
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
136
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
137
|
+
expires_at TIMESTAMPTZ,
|
|
138
|
+
metadata JSONB NOT NULL DEFAULT '{}'
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
DO $$
|
|
142
|
+
BEGIN
|
|
143
|
+
ALTER TABLE nodes ADD CONSTRAINT fk_superseded_by
|
|
144
|
+
FOREIGN KEY (superseded_by) REFERENCES nodes(id);
|
|
145
|
+
EXCEPTION WHEN duplicate_object THEN
|
|
146
|
+
NULL;
|
|
147
|
+
END;
|
|
148
|
+
$$;
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
-- ---------------------------------------------------------------------------
|
|
152
|
+
-- 6. Edges
|
|
153
|
+
-- ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
156
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
157
|
+
type TEXT NOT NULL REFERENCES edge_types(name),
|
|
158
|
+
source_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
159
|
+
target_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
160
|
+
branch_id UUID REFERENCES branches(id),
|
|
161
|
+
confidence REAL NOT NULL DEFAULT 0.5 CHECK (confidence >= 0 AND confidence <= 1),
|
|
162
|
+
weight REAL NOT NULL DEFAULT 0.5,
|
|
163
|
+
evidence TEXT,
|
|
164
|
+
properties JSONB NOT NULL DEFAULT '{}',
|
|
165
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
166
|
+
UNIQUE (type, source_id, target_id, branch_id)
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
-- ---------------------------------------------------------------------------
|
|
171
|
+
-- 7. Node Version History
|
|
172
|
+
-- ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
CREATE TABLE IF NOT EXISTS node_versions (
|
|
175
|
+
id UUID NOT NULL,
|
|
176
|
+
version INT NOT NULL,
|
|
177
|
+
type TEXT NOT NULL,
|
|
178
|
+
layer TEXT NOT NULL,
|
|
179
|
+
branch_id UUID,
|
|
180
|
+
confidence REAL NOT NULL,
|
|
181
|
+
freshness REAL NOT NULL,
|
|
182
|
+
properties JSONB NOT NULL,
|
|
183
|
+
source_system TEXT NOT NULL,
|
|
184
|
+
source_reference TEXT NOT NULL,
|
|
185
|
+
source_agent_id TEXT,
|
|
186
|
+
tags TEXT[] NOT NULL DEFAULT '{}',
|
|
187
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
188
|
+
updated_at TIMESTAMPTZ NOT NULL,
|
|
189
|
+
archived_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
190
|
+
PRIMARY KEY (id, version)
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
-- ---------------------------------------------------------------------------
|
|
195
|
+
-- 8. Indexes
|
|
196
|
+
-- ---------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type);
|
|
199
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_layer ON nodes(layer);
|
|
200
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_branch ON nodes(branch_id) WHERE branch_id IS NOT NULL;
|
|
201
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_freshness ON nodes(freshness);
|
|
202
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_type_layer ON nodes(type, layer);
|
|
203
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_type_freshness ON nodes(type, freshness);
|
|
204
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_superseded_by ON nodes(superseded_by) WHERE superseded_by IS NOT NULL;
|
|
205
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_expires_at ON nodes(expires_at) WHERE expires_at IS NOT NULL;
|
|
206
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_tags ON nodes USING GIN(tags);
|
|
207
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_properties ON nodes USING GIN(properties jsonb_path_ops);
|
|
208
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_embedding ON nodes USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64);
|
|
209
|
+
|
|
210
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);
|
|
211
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);
|
|
212
|
+
CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(type);
|
|
213
|
+
CREATE INDEX IF NOT EXISTS idx_edges_branch ON edges(branch_id) WHERE branch_id IS NOT NULL;
|
|
214
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source_type ON edges(source_id, type);
|
|
215
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target_type ON edges(target_id, type);
|
|
216
|
+
|
|
217
|
+
CREATE INDEX IF NOT EXISTS idx_branches_status ON branches(status);
|
|
218
|
+
|
|
219
|
+
CREATE INDEX IF NOT EXISTS idx_node_versions_id ON node_versions(id);
|
|
220
|
+
CREATE INDEX IF NOT EXISTS idx_node_versions_archived ON node_versions(archived_at);
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
-- ---------------------------------------------------------------------------
|
|
224
|
+
-- 9. Helper Functions
|
|
225
|
+
-- ---------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
CREATE OR REPLACE FUNCTION compute_freshness(p_node_id UUID)
|
|
228
|
+
RETURNS REAL AS $$
|
|
229
|
+
DECLARE
|
|
230
|
+
v_node RECORD;
|
|
231
|
+
v_decay_rate REAL;
|
|
232
|
+
v_age_days REAL;
|
|
233
|
+
v_recency REAL;
|
|
234
|
+
v_corroboration_count INT;
|
|
235
|
+
v_corroboration REAL;
|
|
236
|
+
v_freshness REAL;
|
|
237
|
+
BEGIN
|
|
238
|
+
SELECT * INTO v_node FROM public.nodes WHERE id = p_node_id;
|
|
239
|
+
IF NOT FOUND THEN RETURN 0; END IF;
|
|
240
|
+
|
|
241
|
+
v_decay_rate := CASE v_node.type
|
|
242
|
+
WHEN 'CustomerSignal' THEN 0.0231
|
|
243
|
+
WHEN 'Metric' THEN 0.0990
|
|
244
|
+
WHEN 'Decision' THEN 0.00385
|
|
245
|
+
WHEN 'Feature' THEN 0.00770
|
|
246
|
+
WHEN 'CodeArtifact' THEN 0.01155
|
|
247
|
+
WHEN 'Discussion' THEN 0.0495
|
|
248
|
+
WHEN 'Gap' THEN 0.0495
|
|
249
|
+
WHEN 'Study' THEN 0.00578
|
|
250
|
+
WHEN 'Person' THEN 0.00190
|
|
251
|
+
ELSE 0.01155
|
|
252
|
+
END;
|
|
253
|
+
|
|
254
|
+
v_age_days := EXTRACT(EPOCH FROM (now() - v_node.updated_at)) / 86400.0;
|
|
255
|
+
IF v_age_days < 0 THEN v_age_days := 0; END IF;
|
|
256
|
+
v_recency := exp(-v_decay_rate * v_age_days);
|
|
257
|
+
|
|
258
|
+
SELECT COUNT(DISTINCT e.properties->>'source_system')
|
|
259
|
+
INTO v_corroboration_count
|
|
260
|
+
FROM public.edges e
|
|
261
|
+
WHERE e.target_id = p_node_id
|
|
262
|
+
AND e.type IN ('validates', 'evidence_for', 'informed_by');
|
|
263
|
+
v_corroboration := LEAST(1.0, v_corroboration_count::REAL / 3.0);
|
|
264
|
+
|
|
265
|
+
v_freshness := 0.5 * v_recency + 0.3 * v_corroboration + 0.2 * v_node.source_reliability;
|
|
266
|
+
RETURN GREATEST(0, LEAST(1, v_freshness));
|
|
267
|
+
END;
|
|
268
|
+
$$ LANGUAGE plpgsql STABLE;
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
CREATE OR REPLACE FUNCTION refresh_all_freshness()
|
|
272
|
+
RETURNS INT AS $$
|
|
273
|
+
DECLARE
|
|
274
|
+
v_count INT := 0;
|
|
275
|
+
v_node RECORD;
|
|
276
|
+
BEGIN
|
|
277
|
+
FOR v_node IN
|
|
278
|
+
SELECT id FROM public.nodes WHERE layer = 'permanent' AND superseded_by IS NULL
|
|
279
|
+
LOOP
|
|
280
|
+
UPDATE public.nodes
|
|
281
|
+
SET freshness = compute_freshness(v_node.id),
|
|
282
|
+
updated_at = updated_at
|
|
283
|
+
WHERE id = v_node.id;
|
|
284
|
+
v_count := v_count + 1;
|
|
285
|
+
END LOOP;
|
|
286
|
+
RETURN v_count;
|
|
287
|
+
END;
|
|
288
|
+
$$ LANGUAGE plpgsql;
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
CREATE OR REPLACE FUNCTION expire_hot_nodes()
|
|
292
|
+
RETURNS TABLE(node_id UUID, action TEXT) AS $$
|
|
293
|
+
DECLARE
|
|
294
|
+
v_node RECORD;
|
|
295
|
+
v_ref_count INT;
|
|
296
|
+
BEGIN
|
|
297
|
+
FOR v_node IN
|
|
298
|
+
SELECT id FROM public.nodes
|
|
299
|
+
WHERE layer = 'hot' AND expires_at IS NOT NULL AND expires_at <= now()
|
|
300
|
+
LOOP
|
|
301
|
+
SELECT COUNT(*) INTO v_ref_count
|
|
302
|
+
FROM public.edges WHERE target_id = v_node.id OR source_id = v_node.id;
|
|
303
|
+
|
|
304
|
+
IF v_ref_count >= 2 THEN
|
|
305
|
+
UPDATE public.nodes SET layer = 'draft', expires_at = NULL WHERE id = v_node.id;
|
|
306
|
+
node_id := v_node.id;
|
|
307
|
+
action := 'promoted_to_draft';
|
|
308
|
+
ELSE
|
|
309
|
+
INSERT INTO public.node_versions (id, version, type, layer, branch_id, confidence, freshness,
|
|
310
|
+
properties, source_system, source_reference, source_agent_id, tags, created_at, updated_at)
|
|
311
|
+
SELECT id, version, type, layer, branch_id, confidence, freshness,
|
|
312
|
+
properties, source_system, source_reference, source_agent_id, tags, created_at, updated_at
|
|
313
|
+
FROM public.nodes WHERE id = v_node.id;
|
|
314
|
+
DELETE FROM public.nodes WHERE id = v_node.id;
|
|
315
|
+
node_id := v_node.id;
|
|
316
|
+
action := 'deleted';
|
|
317
|
+
END IF;
|
|
318
|
+
RETURN NEXT;
|
|
319
|
+
END LOOP;
|
|
320
|
+
END;
|
|
321
|
+
$$ LANGUAGE plpgsql;
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
CREATE OR REPLACE FUNCTION archive_node_version()
|
|
325
|
+
RETURNS TRIGGER AS $$
|
|
326
|
+
BEGIN
|
|
327
|
+
IF OLD.properties IS DISTINCT FROM NEW.properties
|
|
328
|
+
OR OLD.confidence IS DISTINCT FROM NEW.confidence
|
|
329
|
+
OR OLD.layer IS DISTINCT FROM NEW.layer
|
|
330
|
+
THEN
|
|
331
|
+
INSERT INTO public.node_versions (id, version, type, layer, branch_id, confidence, freshness,
|
|
332
|
+
properties, source_system, source_reference, source_agent_id, tags, created_at, updated_at)
|
|
333
|
+
VALUES (OLD.id, OLD.version, OLD.type, OLD.layer, OLD.branch_id, OLD.confidence, OLD.freshness,
|
|
334
|
+
OLD.properties, OLD.source_system, OLD.source_reference, OLD.source_agent_id, OLD.tags,
|
|
335
|
+
OLD.created_at, OLD.updated_at);
|
|
336
|
+
NEW.version := OLD.version + 1;
|
|
337
|
+
NEW.updated_at := now();
|
|
338
|
+
END IF;
|
|
339
|
+
RETURN NEW;
|
|
340
|
+
END;
|
|
341
|
+
$$ LANGUAGE plpgsql;
|
|
342
|
+
|
|
343
|
+
DROP TRIGGER IF EXISTS trg_node_versioning ON nodes;
|
|
344
|
+
CREATE TRIGGER trg_node_versioning
|
|
345
|
+
BEFORE UPDATE ON nodes
|
|
346
|
+
FOR EACH ROW
|
|
347
|
+
EXECUTE FUNCTION archive_node_version();
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
-- ---------------------------------------------------------------------------
|
|
351
|
+
-- 10. AGE Graph Labels (requires search_path change — MUST be last)
|
|
352
|
+
-- ---------------------------------------------------------------------------
|
|
353
|
+
-- NOTE: The SET search_path below changes the default schema.
|
|
354
|
+
-- All table operations above are complete so this is safe.
|
|
355
|
+
|
|
356
|
+
SET search_path = ag_catalog, "$user", public;
|
|
357
|
+
|
|
358
|
+
DO $$ BEGIN PERFORM create_vlabel('context_graph', 'Decision'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
359
|
+
DO $$ BEGIN PERFORM create_vlabel('context_graph', 'Feature'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
360
|
+
DO $$ BEGIN PERFORM create_vlabel('context_graph', 'Metric'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
361
|
+
DO $$ BEGIN PERFORM create_vlabel('context_graph', 'CustomerSignal'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
362
|
+
DO $$ BEGIN PERFORM create_vlabel('context_graph', 'CodeArtifact'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
363
|
+
DO $$ BEGIN PERFORM create_vlabel('context_graph', 'Discussion'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
364
|
+
DO $$ BEGIN PERFORM create_vlabel('context_graph', 'Gap'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
365
|
+
DO $$ BEGIN PERFORM create_vlabel('context_graph', 'Study'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
366
|
+
DO $$ BEGIN PERFORM create_vlabel('context_graph', 'Person'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
367
|
+
|
|
368
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'caused_by'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
369
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'informed_by'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
370
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'derived_from'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
371
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'evidence_for'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
372
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'contradicts'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
373
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'validates'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
374
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'invalidates'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
375
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'resolved_by'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
376
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'supersedes'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
377
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'blocks'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
378
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'depends_on'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
379
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'part_of'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
380
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'owned_by'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
381
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'participated_in'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
382
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'evolved_from'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
383
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'preceded_by'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
384
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'deprioritized_because'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
385
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'triggered'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
386
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'fills_gap'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
387
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'collected_for'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
388
|
+
DO $$ BEGIN PERFORM create_elabel('context_graph', 'assigned_to'); EXCEPTION WHEN others THEN NULL; END; $$;
|
|
389
|
+
|
|
390
|
+
-- Reset search_path back to default
|
|
391
|
+
SET search_path = "$user", public;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- =============================================================================
|
|
2
|
+
-- Change embedding dimension from 1536 to 1024 (Voyage AI voyage-3-lite)
|
|
3
|
+
-- =============================================================================
|
|
4
|
+
|
|
5
|
+
-- Drop the HNSW index first (required before altering the column type)
|
|
6
|
+
DROP INDEX IF EXISTS idx_nodes_embedding;
|
|
7
|
+
|
|
8
|
+
-- Alter the column dimension
|
|
9
|
+
ALTER TABLE nodes ALTER COLUMN embedding TYPE vector(1024);
|
|
10
|
+
|
|
11
|
+
-- Recreate the HNSW index
|
|
12
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_embedding ON nodes USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64);
|