@automagik/genie 4.260508.1 → 4.260508.2

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.260508.1",
3
+ "version": "4.260508.2",
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.260508.1",
3
+ "version": "4.260508.2",
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"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie-plugin",
3
- "version": "4.260508.1",
3
+ "version": "4.260508.2",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",
@@ -19,21 +19,43 @@
19
19
  -- silent default-off that triggered the misleading message in the first
20
20
  -- place.
21
21
  --
22
+ -- Heal-not-wipe interaction with migration 061
23
+ -- --------------------------------------------
24
+ -- Migration 061 added `agents_id_shape_check` as `NOT VALID` so legacy
25
+ -- bare-name rows (archived by 050/053) stay in the table. Postgres still
26
+ -- enforces the constraint on every UPDATE — even when the id column is
27
+ -- not in the SET list — so an unconditional `UPDATE agents SET auto_resume
28
+ -- = true` errors out with `agents_id_shape_check` violation on any host
29
+ -- carrying archived bare-name rows. The whole boot path then fails,
30
+ -- because runMigrations runs inside getConnection's post-connect setup.
31
+ --
32
+ -- Filter the UPDATEs to UUID/dir-prefixed ids only. Bare-name rows are
33
+ -- already archived (state='archived', auto_resume=false locked by 050/053)
34
+ -- so flipping their auto_resume is meaningless — they are not resume
35
+ -- candidates regardless of the column value.
36
+ --
22
37
  -- Idempotent: re-running this migration is a no-op on rows that are already
23
38
  -- true and on a column whose default is already true.
24
39
 
25
40
  BEGIN;
26
41
 
27
- -- 1. Flip every existing false row. No WHERE narrowing Felipe directive
28
- -- is to flip ALL of them so resume works out-of-the-box.
42
+ -- 1. Flip every existing false row that satisfies the id shape constraint.
43
+ -- Bare-name rows (archived legacy) are skipped see header comment.
29
44
  UPDATE agents
30
45
  SET auto_resume = true
31
- WHERE auto_resume = false;
46
+ WHERE auto_resume = false
47
+ AND (id ~ '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
48
+ OR id LIKE 'dir:%');
32
49
 
33
50
  -- 2. Change the column DEFAULT so future spawns inherit the safe value.
34
51
  -- NULLs (legacy rows that pre-date the column) become true — same as
35
- -- the safe-default the runtime treats them as.
52
+ -- the safe-default the runtime treats them as. Same id-shape filter
53
+ -- applies for the same reason.
36
54
  ALTER TABLE agents ALTER COLUMN auto_resume SET DEFAULT true;
37
- UPDATE agents SET auto_resume = true WHERE auto_resume IS NULL;
55
+ UPDATE agents
56
+ SET auto_resume = true
57
+ WHERE auto_resume IS NULL
58
+ AND (id ~ '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
59
+ OR id LIKE 'dir:%');
38
60
 
39
61
  COMMIT;
@@ -0,0 +1,108 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { readFile } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { type Sql, getConnection } from '../../lib/db.js';
6
+ import { DB_AVAILABLE, setupTestDatabase } from '../../lib/test-db.js';
7
+
8
+ const MIGRATION_PATH = join(import.meta.dir, '055_default_auto_resume_true.sql');
9
+
10
+ async function runMigration(sql: Sql): Promise<void> {
11
+ const sqlText = await readFile(MIGRATION_PATH, 'utf-8');
12
+ // Mirror runMigrations() in db-migrations.ts: wrap in sql.begin so the
13
+ // pool sees a single reserved connection. The migration file's own
14
+ // BEGIN/COMMIT becomes a no-op inside the outer transaction (postgres
15
+ // emits a "there is already a transaction in progress" warning and
16
+ // COMMIT closes the outer txn, same as production).
17
+ await sql.begin(async (tx) => {
18
+ await tx.unsafe(sqlText);
19
+ });
20
+ }
21
+
22
+ describe.skipIf(!DB_AVAILABLE)('migration 055 — default auto_resume true', () => {
23
+ let cleanup: () => Promise<void>;
24
+
25
+ beforeAll(async () => {
26
+ cleanup = await setupTestDatabase();
27
+ });
28
+
29
+ afterAll(async () => {
30
+ await cleanup();
31
+ });
32
+
33
+ test('does not fail when archived bare-name rows are present', async () => {
34
+ const sql = await getConnection();
35
+
36
+ // The template DB has migration 055 already applied. To replay it as if
37
+ // it were pending against a host that carries archived bare-name rows
38
+ // (legacy 050/053 grandfather), we have to re-create the failure shape:
39
+ // 1. Drop the id-shape CHECK so we can insert a bare-name row.
40
+ // 2. Insert one archived bare-name agent (mirrors production data).
41
+ // 3. Re-add the CHECK as NOT VALID (mirrors migration 061's pattern).
42
+ // 4. Reset a UUID row's auto_resume to false (something for 055 to flip).
43
+ // 5. Re-run 055.
44
+ // The pre-fix migration would error here with agents_id_shape_check;
45
+ // the post-fix migration filters bare-name rows out of the UPDATE.
46
+ await sql`ALTER TABLE agents DROP CONSTRAINT IF EXISTS agents_id_shape_check`;
47
+
48
+ const bareName = `legacy-bare-${Date.now()}`;
49
+ await sql`
50
+ INSERT INTO agents (id, role, custom_name, team, repo_path, started_at, state, auto_resume)
51
+ VALUES (${bareName}, 'legacy', NULL, 'legacy-team', '/tmp/legacy', now(), 'archived', false)
52
+ ON CONFLICT (id) DO NOTHING
53
+ `;
54
+
55
+ await sql.unsafe(
56
+ `ALTER TABLE agents
57
+ ADD CONSTRAINT agents_id_shape_check
58
+ CHECK (id ~ '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' OR id LIKE 'dir:%')
59
+ NOT VALID`,
60
+ );
61
+
62
+ const liveId = randomUUID();
63
+ await sql`
64
+ INSERT INTO agents (id, role, custom_name, team, repo_path, started_at, state, auto_resume)
65
+ VALUES (${liveId}, 'engineer', 'live-engineer', 'live-team', '/tmp/live', now(), 'idle', false)
66
+ ON CONFLICT (id) DO NOTHING
67
+ `;
68
+
69
+ // The pre-fix migration would throw here with
70
+ // `new row for relation "agents" violates check constraint "agents_id_shape_check"`.
71
+ await expect(runMigration(sql)).resolves.toBeUndefined();
72
+
73
+ const live = await sql<{ auto_resume: boolean }[]>`
74
+ SELECT auto_resume FROM agents WHERE id = ${liveId}
75
+ `;
76
+ expect(live[0].auto_resume).toBe(true);
77
+
78
+ const bare = await sql<{ auto_resume: boolean }[]>`
79
+ SELECT auto_resume FROM agents WHERE id = ${bareName}
80
+ `;
81
+ // Bare-name rows are archived legacy; their auto_resume is intentionally
82
+ // not flipped because the row would fail the id-shape check.
83
+ expect(bare[0].auto_resume).toBe(false);
84
+ });
85
+
86
+ test('flips auto_resume on UUID and dir: rows, leaves NULL rows true', async () => {
87
+ const sql = await getConnection();
88
+
89
+ const uuidId = randomUUID();
90
+ const dirId = `dir:test-dir-${Date.now()}`;
91
+ await sql`
92
+ INSERT INTO agents (id, role, custom_name, team, repo_path, started_at, state, auto_resume)
93
+ VALUES
94
+ (${uuidId}, 'engineer', 'a', 'team-a', '/tmp/a', now(), 'idle', false),
95
+ (${dirId}, 'engineer', 'b', 'team-b', '/tmp/b', now(), 'idle', false)
96
+ ON CONFLICT (id) DO NOTHING
97
+ `;
98
+
99
+ await runMigration(sql);
100
+
101
+ const rows = await sql<{ id: string; auto_resume: boolean }[]>`
102
+ SELECT id, auto_resume FROM agents WHERE id IN (${uuidId}, ${dirId})
103
+ `;
104
+ for (const r of rows) {
105
+ expect(r.auto_resume).toBe(true);
106
+ }
107
+ });
108
+ });