@automagik/genie 4.260428.6 → 4.260428.7
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automagik/genie",
|
|
3
|
-
"version": "4.260428.
|
|
3
|
+
"version": "4.260428.7",
|
|
4
4
|
"description": "Collaborative terminal toolkit for human + AI workflows. NOTE: the npm distribution is being soft-deprecated — the canonical install is `curl -fsSL https://get.automagik.dev/genie | bash` (cosign + SLSA verified). See https://automagik.dev/genie/security/distribution-sovereignty",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genie",
|
|
3
|
-
"version": "4.260428.
|
|
3
|
+
"version": "4.260428.7",
|
|
4
4
|
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Namastex Labs"
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
-- 055_runtime_events_partition_drain.sql
|
|
2
|
+
-- Bug: genie_runtime_events_maintain_partitions never drained the DEFAULT
|
|
3
|
+
-- partition. Rows that landed in genie_runtime_events_default (e.g. inserts
|
|
4
|
+
-- after UTC midnight before the next maintenance call ran) permanently
|
|
5
|
+
-- blocked creation of new dated partitions, because PG validates that no
|
|
6
|
+
-- existing default-partition row would belong to the new partition. Symptom:
|
|
7
|
+
-- SQLSTATE 23514 "updated partition constraint for default partition
|
|
8
|
+
-- genie_runtime_events_default would be violated by some row" on every
|
|
9
|
+
-- subsequent `genie serve start`. Migration 038's docstring claimed the
|
|
10
|
+
-- nightly scheduler converted DEFAULT rows into named partitions; no such
|
|
11
|
+
-- code ever existed.
|
|
12
|
+
--
|
|
13
|
+
-- Fix:
|
|
14
|
+
-- 1. New helper genie_runtime_events_drain_default() detaches DEFAULT,
|
|
15
|
+
-- ensures dated partitions exist for every day represented by its rows,
|
|
16
|
+
-- re-inserts the rows (parent-routing them to the correct dated
|
|
17
|
+
-- partition) with notify-trigger suppression, truncates the now-empty
|
|
18
|
+
-- detached default, and re-attaches it as DEFAULT.
|
|
19
|
+
-- 2. genie_runtime_events_maintain_partitions calls drain_default() before
|
|
20
|
+
-- creating today..today+forward_days, so a stuck DEFAULT can no longer
|
|
21
|
+
-- poison subsequent partition creation.
|
|
22
|
+
-- 3. One-shot drain at migration time so existing installs unstick on the
|
|
23
|
+
-- first post-upgrade `genie serve start`.
|
|
24
|
+
|
|
25
|
+
CREATE OR REPLACE FUNCTION genie_runtime_events_drain_default()
|
|
26
|
+
RETURNS INTEGER AS $$
|
|
27
|
+
DECLARE
|
|
28
|
+
drained INTEGER := 0;
|
|
29
|
+
d_rec RECORD;
|
|
30
|
+
prev_role TEXT;
|
|
31
|
+
BEGIN
|
|
32
|
+
IF NOT EXISTS (
|
|
33
|
+
SELECT 1
|
|
34
|
+
FROM pg_class c
|
|
35
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
36
|
+
WHERE c.relname = 'genie_runtime_events_default'
|
|
37
|
+
AND n.nspname = current_schema()
|
|
38
|
+
) THEN
|
|
39
|
+
RETURN 0;
|
|
40
|
+
END IF;
|
|
41
|
+
|
|
42
|
+
IF NOT EXISTS (SELECT 1 FROM genie_runtime_events_default LIMIT 1) THEN
|
|
43
|
+
RETURN 0;
|
|
44
|
+
END IF;
|
|
45
|
+
|
|
46
|
+
-- Detach so the re-insert below routes to dated partitions instead of
|
|
47
|
+
-- looping back into DEFAULT. Non-CONCURRENTLY form is transaction-safe.
|
|
48
|
+
EXECUTE 'ALTER TABLE genie_runtime_events DETACH PARTITION genie_runtime_events_default';
|
|
49
|
+
|
|
50
|
+
-- Ensure a dated partition exists for every day represented in the
|
|
51
|
+
-- detached default. create_partition is CREATE TABLE IF NOT EXISTS, so
|
|
52
|
+
-- pre-existing partitions are fine.
|
|
53
|
+
FOR d_rec IN
|
|
54
|
+
SELECT DISTINCT date_trunc('day', created_at)::DATE AS d
|
|
55
|
+
FROM genie_runtime_events_default
|
|
56
|
+
LOOP
|
|
57
|
+
PERFORM genie_runtime_events_create_partition(d_rec.d);
|
|
58
|
+
END LOOP;
|
|
59
|
+
|
|
60
|
+
-- Suppress AFTER INSERT triggers (notify_runtime_event_split, audit chain
|
|
61
|
+
-- triggers, etc.) for the drain. These rows already fired their notifies
|
|
62
|
+
-- on their original insert; re-firing would broadcast duplicates to every
|
|
63
|
+
-- LISTEN'er.
|
|
64
|
+
prev_role := current_setting('session_replication_role');
|
|
65
|
+
PERFORM set_config('session_replication_role', 'replica', true);
|
|
66
|
+
|
|
67
|
+
EXECUTE $sql$
|
|
68
|
+
INSERT INTO genie_runtime_events (
|
|
69
|
+
id, repo_path, subject, kind, source, agent, team, direction, peer,
|
|
70
|
+
text, data, thread_id, trace_id, parent_event_id, span_id, parent_span_id,
|
|
71
|
+
severity, schema_version, duration_ms, dedup_key, source_subsystem, created_at
|
|
72
|
+
)
|
|
73
|
+
OVERRIDING SYSTEM VALUE
|
|
74
|
+
SELECT id, repo_path, subject, kind, source, agent, team, direction, peer,
|
|
75
|
+
text, data, thread_id, trace_id, parent_event_id, span_id, parent_span_id,
|
|
76
|
+
severity, schema_version, duration_ms, dedup_key, source_subsystem, created_at
|
|
77
|
+
FROM genie_runtime_events_default
|
|
78
|
+
$sql$;
|
|
79
|
+
|
|
80
|
+
GET DIAGNOSTICS drained = ROW_COUNT;
|
|
81
|
+
|
|
82
|
+
PERFORM set_config('session_replication_role', prev_role, true);
|
|
83
|
+
|
|
84
|
+
EXECUTE 'TRUNCATE genie_runtime_events_default';
|
|
85
|
+
EXECUTE 'ALTER TABLE genie_runtime_events ATTACH PARTITION genie_runtime_events_default DEFAULT';
|
|
86
|
+
|
|
87
|
+
RETURN drained;
|
|
88
|
+
END;
|
|
89
|
+
$$ LANGUAGE plpgsql;
|
|
90
|
+
|
|
91
|
+
CREATE OR REPLACE FUNCTION genie_runtime_events_maintain_partitions(
|
|
92
|
+
forward_days INTEGER DEFAULT 2,
|
|
93
|
+
retention_days INTEGER DEFAULT 30
|
|
94
|
+
)
|
|
95
|
+
RETURNS JSONB AS $$
|
|
96
|
+
DECLARE
|
|
97
|
+
drained INTEGER;
|
|
98
|
+
created INTEGER := 0;
|
|
99
|
+
dropped INTEGER := 0;
|
|
100
|
+
i INTEGER;
|
|
101
|
+
BEGIN
|
|
102
|
+
-- Drain DEFAULT first so any rows that accumulated there (UTC-midnight
|
|
103
|
+
-- rollover between maintenance calls) get routed to their proper dated
|
|
104
|
+
-- partitions before we try to CREATE the new ones.
|
|
105
|
+
drained := genie_runtime_events_drain_default();
|
|
106
|
+
|
|
107
|
+
FOR i IN 0..forward_days LOOP
|
|
108
|
+
PERFORM genie_runtime_events_create_partition((CURRENT_DATE + i)::DATE);
|
|
109
|
+
created := created + 1;
|
|
110
|
+
END LOOP;
|
|
111
|
+
SELECT genie_runtime_events_drop_old_partitions(retention_days) INTO dropped;
|
|
112
|
+
RETURN jsonb_build_object(
|
|
113
|
+
'created_or_present', created,
|
|
114
|
+
'dropped', dropped,
|
|
115
|
+
'drained_from_default', drained,
|
|
116
|
+
'next_rotation_at', (CURRENT_DATE + 1)::TIMESTAMPTZ
|
|
117
|
+
);
|
|
118
|
+
END;
|
|
119
|
+
$$ LANGUAGE plpgsql;
|
|
120
|
+
|
|
121
|
+
-- Mirror the role grants from migration 041 for the new helper. Conditional
|
|
122
|
+
-- so this works on installs that ran 041 before events_admin existed.
|
|
123
|
+
DO $$
|
|
124
|
+
BEGIN
|
|
125
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'events_admin') THEN
|
|
126
|
+
EXECUTE 'GRANT EXECUTE ON FUNCTION genie_runtime_events_drain_default() TO events_admin';
|
|
127
|
+
END IF;
|
|
128
|
+
END$$;
|
|
129
|
+
|
|
130
|
+
DO $$
|
|
131
|
+
DECLARE
|
|
132
|
+
drained INTEGER;
|
|
133
|
+
BEGIN
|
|
134
|
+
drained := genie_runtime_events_drain_default();
|
|
135
|
+
IF drained > 0 THEN
|
|
136
|
+
RAISE NOTICE 'genie_runtime_events_default: drained % stuck row(s) into dated partitions', drained;
|
|
137
|
+
END IF;
|
|
138
|
+
END$$;
|
|
@@ -196,6 +196,53 @@ describe.skipIf(!DB_AVAILABLE)('Group 1 observability migrations', () => {
|
|
|
196
196
|
expect(deleteErr?.message).toMatch(/append-only/);
|
|
197
197
|
});
|
|
198
198
|
|
|
199
|
+
test('maintain_partitions drains rows stuck in DEFAULT (regression: SQLSTATE 23514)', async () => {
|
|
200
|
+
const sql = await getConnection();
|
|
201
|
+
|
|
202
|
+
// Pick a far-future date that no rolling-window partition covers.
|
|
203
|
+
// create_partition uses session-TZ midnight, so the date string suffices.
|
|
204
|
+
const stuckDate = '2099-12-31';
|
|
205
|
+
|
|
206
|
+
// Pre-clean any leftovers from a previous test run.
|
|
207
|
+
await sql`DELETE FROM genie_runtime_events WHERE kind = 'partition.drain.test'`;
|
|
208
|
+
|
|
209
|
+
// Insert a row whose created_at falls outside every dated partition. It
|
|
210
|
+
// must route to genie_runtime_events_default.
|
|
211
|
+
await sql`
|
|
212
|
+
INSERT INTO genie_runtime_events (repo_path, kind, source, agent, text, created_at)
|
|
213
|
+
VALUES ('test', 'partition.drain.test', 'test', 'test', 'stuck', ${`${stuckDate} 12:00:00+00`}::TIMESTAMPTZ)
|
|
214
|
+
`;
|
|
215
|
+
|
|
216
|
+
const beforeDefault = await sql<{ n: number }[]>`
|
|
217
|
+
SELECT count(*)::INT AS n FROM genie_runtime_events_default
|
|
218
|
+
WHERE kind = 'partition.drain.test'
|
|
219
|
+
`;
|
|
220
|
+
expect(beforeDefault[0].n).toBe(1);
|
|
221
|
+
|
|
222
|
+
const result = await sql<
|
|
223
|
+
Array<{ r: { created_or_present: number; drained_from_default: number } }>
|
|
224
|
+
>`SELECT genie_runtime_events_maintain_partitions(2, 30)::jsonb AS r`;
|
|
225
|
+
expect(result[0].r.drained_from_default).toBeGreaterThanOrEqual(1);
|
|
226
|
+
|
|
227
|
+
const afterDefault = await sql<{ n: number }[]>`
|
|
228
|
+
SELECT count(*)::INT AS n FROM genie_runtime_events_default
|
|
229
|
+
WHERE kind = 'partition.drain.test'
|
|
230
|
+
`;
|
|
231
|
+
expect(afterDefault[0].n).toBe(0);
|
|
232
|
+
|
|
233
|
+
const inDated = await sql<{ relname: string }[]>`
|
|
234
|
+
SELECT tableoid::regclass::TEXT AS relname
|
|
235
|
+
FROM genie_runtime_events
|
|
236
|
+
WHERE kind = 'partition.drain.test'
|
|
237
|
+
`;
|
|
238
|
+
expect(inDated.length).toBe(1);
|
|
239
|
+
expect(inDated[0].relname).toMatch(/genie_runtime_events_p20991231$/);
|
|
240
|
+
|
|
241
|
+
// Cleanup so the dated partition created above doesn't haunt the rolling
|
|
242
|
+
// window or the retention sweep on subsequent runs.
|
|
243
|
+
await sql`DELETE FROM genie_runtime_events WHERE kind = 'partition.drain.test'`;
|
|
244
|
+
});
|
|
245
|
+
|
|
199
246
|
test('Group 1 migrations are idempotent on re-apply', async () => {
|
|
200
247
|
// Re-run each migration body against the already-migrated schema and
|
|
201
248
|
// confirm the guarded statements are all no-ops.
|