@jaguilar87/gaia 5.0.2 → 5.0.5

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.
Files changed (154) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/ARCHITECTURE.md +0 -1
  4. package/CHANGELOG.md +110 -0
  5. package/INSTALL.md +0 -2
  6. package/README.md +1 -6
  7. package/bin/README.md +0 -1
  8. package/bin/cli/_install_helpers.py +1 -1
  9. package/bin/cli/approvals.py +23 -21
  10. package/bin/cli/cleanup.py +0 -1
  11. package/bin/cli/doctor.py +1 -1
  12. package/bin/cli/memory.py +2 -0
  13. package/bin/cli/update.py +1 -1
  14. package/bin/pre-publish-validate.js +48 -5
  15. package/config/README.md +22 -44
  16. package/config/surface-routing.json +0 -2
  17. package/dist/gaia-ops/.claude-plugin/plugin.json +1 -1
  18. package/dist/gaia-ops/config/README.md +22 -44
  19. package/dist/gaia-ops/config/surface-routing.json +0 -2
  20. package/dist/gaia-ops/hooks/modules/agents/contract_validator.py +18 -0
  21. package/dist/gaia-ops/hooks/modules/agents/handoff_persister.py +214 -2
  22. package/dist/gaia-ops/hooks/modules/agents/response_contract.py +26 -0
  23. package/dist/gaia-ops/hooks/modules/agents/transcript_reader.py +15 -0
  24. package/dist/gaia-ops/hooks/modules/security/__init__.py +0 -5
  25. package/dist/gaia-ops/hooks/modules/security/approval_grants.py +124 -19
  26. package/dist/gaia-ops/hooks/modules/security/mutative_verbs.py +99 -7
  27. package/dist/gaia-ops/hooks/modules/tools/bash_validator.py +127 -24
  28. package/dist/gaia-ops/hooks/modules/validation/commit_validator.py +90 -55
  29. package/dist/gaia-ops/skills/README.md +1 -1
  30. package/dist/gaia-ops/skills/agent-contract-handoff/SKILL.md +3 -0
  31. package/dist/gaia-ops/skills/agent-response/SKILL.md +4 -2
  32. package/dist/gaia-ops/skills/gaia-patterns/SKILL.md +1 -1
  33. package/dist/gaia-ops/skills/gaia-patterns/reference.md +2 -3
  34. package/dist/gaia-ops/skills/gaia-release/SKILL.md +60 -24
  35. package/dist/gaia-ops/skills/gaia-release/reference.md +35 -11
  36. package/dist/gaia-ops/skills/git-conventions/SKILL.md +6 -2
  37. package/dist/gaia-ops/skills/orchestrator-present-approval/SKILL.md +30 -7
  38. package/dist/gaia-ops/skills/orchestrator-present-approval/reference.md +32 -15
  39. package/dist/gaia-ops/skills/readme-writing/SKILL.md +1 -1
  40. package/dist/gaia-ops/skills/readme-writing/reference.md +0 -1
  41. package/dist/gaia-ops/skills/security-tiers/SKILL.md +5 -1
  42. package/dist/gaia-ops/skills/security-tiers/reference.md +3 -1
  43. package/dist/gaia-ops/skills/subagent-request-approval/SKILL.md +43 -6
  44. package/dist/gaia-ops/skills/subagent-request-approval/reference.md +66 -16
  45. package/dist/gaia-ops/tools/context/README.md +1 -1
  46. package/dist/gaia-ops/tools/gaia_simulator/extractor.py +0 -1
  47. package/dist/gaia-ops/tools/scan/ui.py +20 -4
  48. package/dist/gaia-ops/tools/scan/verify.py +3 -3
  49. package/dist/gaia-ops/tools/validation/README.md +15 -24
  50. package/dist/gaia-security/.claude-plugin/plugin.json +1 -1
  51. package/dist/gaia-security/hooks/modules/agents/contract_validator.py +18 -0
  52. package/dist/gaia-security/hooks/modules/agents/handoff_persister.py +214 -2
  53. package/dist/gaia-security/hooks/modules/agents/response_contract.py +26 -0
  54. package/dist/gaia-security/hooks/modules/agents/transcript_reader.py +15 -0
  55. package/dist/gaia-security/hooks/modules/security/__init__.py +0 -5
  56. package/dist/gaia-security/hooks/modules/security/approval_grants.py +124 -19
  57. package/dist/gaia-security/hooks/modules/security/mutative_verbs.py +99 -7
  58. package/dist/gaia-security/hooks/modules/tools/bash_validator.py +127 -24
  59. package/dist/gaia-security/hooks/modules/validation/commit_validator.py +90 -55
  60. package/gaia/state/transitions.py +4 -4
  61. package/gaia/store/writer.py +56 -0
  62. package/hooks/modules/README.md +2 -4
  63. package/hooks/modules/agents/contract_validator.py +18 -0
  64. package/hooks/modules/agents/handoff_persister.py +214 -2
  65. package/hooks/modules/agents/response_contract.py +26 -0
  66. package/hooks/modules/agents/transcript_reader.py +15 -0
  67. package/hooks/modules/security/__init__.py +0 -5
  68. package/hooks/modules/security/approval_grants.py +124 -19
  69. package/hooks/modules/security/mutative_verbs.py +99 -7
  70. package/hooks/modules/tools/bash_validator.py +127 -24
  71. package/hooks/modules/validation/commit_validator.py +90 -55
  72. package/index.js +2 -12
  73. package/package.json +4 -6
  74. package/pyproject.toml +3 -3
  75. package/scripts/bootstrap_database.sh +88 -439
  76. package/scripts/check_schema_drift.py +208 -0
  77. package/scripts/migrations/README.md +78 -28
  78. package/scripts/migrations/schema.checksum +8 -0
  79. package/scripts/release-prepare.mjs +199 -0
  80. package/skills/README.md +1 -1
  81. package/skills/agent-contract-handoff/SKILL.md +3 -0
  82. package/skills/agent-response/SKILL.md +4 -2
  83. package/skills/gaia-patterns/SKILL.md +1 -1
  84. package/skills/gaia-patterns/reference.md +2 -3
  85. package/skills/gaia-release/SKILL.md +60 -24
  86. package/skills/gaia-release/reference.md +35 -11
  87. package/skills/git-conventions/SKILL.md +6 -2
  88. package/skills/orchestrator-present-approval/SKILL.md +30 -7
  89. package/skills/orchestrator-present-approval/reference.md +32 -15
  90. package/skills/readme-writing/SKILL.md +1 -1
  91. package/skills/readme-writing/reference.md +0 -1
  92. package/skills/security-tiers/SKILL.md +5 -1
  93. package/skills/security-tiers/reference.md +3 -1
  94. package/skills/subagent-request-approval/SKILL.md +43 -6
  95. package/skills/subagent-request-approval/reference.md +66 -16
  96. package/tools/context/README.md +1 -1
  97. package/tools/gaia_simulator/extractor.py +0 -1
  98. package/tools/scan/ui.py +20 -4
  99. package/tools/scan/verify.py +3 -3
  100. package/tools/validation/README.md +15 -24
  101. package/commands/README.md +0 -64
  102. package/commands/gaia.md +0 -37
  103. package/commands/scan-project.md +0 -74
  104. package/config/crons-schema.md +0 -81
  105. package/config/git_standards.json +0 -72
  106. package/dist/gaia-ops/commands/gaia.md +0 -37
  107. package/dist/gaia-ops/config/crons-schema.md +0 -81
  108. package/dist/gaia-ops/config/git_standards.json +0 -72
  109. package/dist/gaia-ops/hooks/modules/security/gitops_validator.py +0 -179
  110. package/dist/gaia-ops/tools/agentic-loop/decide-status.py +0 -210
  111. package/dist/gaia-ops/tools/agentic-loop/parse-metric.py +0 -106
  112. package/dist/gaia-ops/tools/agentic-loop/record-iteration.py +0 -223
  113. package/dist/gaia-security/hooks/modules/security/gitops_validator.py +0 -179
  114. package/git-hooks/commit-msg +0 -41
  115. package/hooks/modules/security/gitops_validator.py +0 -179
  116. package/scripts/migrations/v10_to_v11.sql +0 -170
  117. package/scripts/migrations/v10_to_v11_fresh.sql +0 -18
  118. package/scripts/migrations/v11_to_v12.sql +0 -195
  119. package/scripts/migrations/v11_to_v12_fresh.sql +0 -19
  120. package/scripts/migrations/v12_to_v13.sql +0 -48
  121. package/scripts/migrations/v12_to_v13_fresh.sql +0 -17
  122. package/scripts/migrations/v13_to_v14.sql +0 -44
  123. package/scripts/migrations/v13_to_v14_fresh.sql +0 -17
  124. package/scripts/migrations/v14_to_v15.sql +0 -71
  125. package/scripts/migrations/v14_to_v15_fresh.sql +0 -19
  126. package/scripts/migrations/v15_to_v16.sql +0 -57
  127. package/scripts/migrations/v15_to_v16_fresh.sql +0 -18
  128. package/scripts/migrations/v16_to_v17.sql +0 -51
  129. package/scripts/migrations/v16_to_v17_fresh.sql +0 -18
  130. package/scripts/migrations/v17_to_v18.sql +0 -66
  131. package/scripts/migrations/v17_to_v18_fresh.sql +0 -24
  132. package/scripts/migrations/v1_to_v2.sql +0 -97
  133. package/scripts/migrations/v2_to_v3.sql +0 -68
  134. package/scripts/migrations/v2_to_v3_merge.sql +0 -69
  135. package/scripts/migrations/v3_to_v4.sql +0 -67
  136. package/scripts/migrations/v3_to_v4_fresh.sql +0 -20
  137. package/scripts/migrations/v4_to_v5.sql +0 -55
  138. package/scripts/migrations/v4_to_v5_fresh.sql +0 -20
  139. package/scripts/migrations/v5_to_v6.sql +0 -48
  140. package/scripts/migrations/v5_to_v6_fresh.sql +0 -17
  141. package/scripts/migrations/v6_to_v7.sql +0 -26
  142. package/scripts/migrations/v6_to_v7_fresh.sql +0 -13
  143. package/scripts/migrations/v7_to_v8.sql +0 -44
  144. package/scripts/migrations/v7_to_v8_fresh.sql +0 -14
  145. package/scripts/migrations/v8_to_v9.sql +0 -87
  146. package/scripts/migrations/v8_to_v9_fresh.sql +0 -15
  147. package/scripts/migrations/v9_to_v10.sql +0 -109
  148. package/scripts/migrations/v9_to_v10_episodes_workspace.sql +0 -109
  149. package/scripts/migrations/v9_to_v10_fresh.sql +0 -18
  150. package/templates/README.md +0 -70
  151. package/templates/managed-settings.template.json +0 -43
  152. package/tools/agentic-loop/decide-status.py +0 -210
  153. package/tools/agentic-loop/parse-metric.py +0 -106
  154. package/tools/agentic-loop/record-iteration.py +0 -223
@@ -1,57 +0,0 @@
1
- -- Migration v15 -> v16 (gaia-scan-overhaul: projects soft-delete columns)
2
- --
3
- -- Background
4
- -- ----------
5
- -- The prune step in scan_workspace_to_store currently issues hard DELETEs for
6
- -- projects that are no longer found on disk. Brief gaia-scan-overhaul replaces
7
- -- that with a soft-delete: projects are marked 'missing' instead of removed,
8
- -- so historical context (memory atoms, episodes, briefs keyed on that project)
9
- -- survives the scan cycle.
10
- --
11
- -- This migration adds two scanner-owned columns to the `projects` table:
12
- --
13
- -- status TEXT NOT NULL DEFAULT 'active'
14
- -- Values: 'active' | 'missing'.
15
- -- 'active' -- project was present on the last scan run.
16
- -- 'missing' -- project was NOT found on the most recent scan; kept as a
17
- -- tombstone so child-table data and historical context survive.
18
- -- Default 'active': existing rows (which were present at migration time)
19
- -- are classified as active. New rows inserted by the scanner also default
20
- -- to 'active' without requiring callers to supply the column.
21
- --
22
- -- missing_since TEXT (nullable)
23
- -- ISO8601 UTC timestamp of when status was first set to 'missing'.
24
- -- NULL when status='active'. The scanner sets this on the first cycle
25
- -- where it cannot find the project; subsequent cycles leave it unchanged
26
- -- (the timestamp records the FIRST disappearance, not the most recent).
27
- --
28
- -- Scope of this migration
29
- -- -----------------------
30
- -- ONLY the DDL changes. The prune logic (DELETE -> UPDATE status='missing') is
31
- -- implemented in the NEXT task (scan populator changes). This migration only
32
- -- ensures the columns exist and that the writer accepts them.
33
- --
34
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT. A failure
35
- -- mid-flight rolls back to v15 state; the ledger row is NOT inserted, so the
36
- -- next bootstrap retry sees the same pending migration.
37
-
38
- -- ---------------------------------------------------------------------------
39
- -- Step 1: Add status column to projects (NOT NULL DEFAULT 'active')
40
- -- ---------------------------------------------------------------------------
41
- ALTER TABLE projects ADD COLUMN status TEXT NOT NULL DEFAULT 'active';
42
-
43
- -- ---------------------------------------------------------------------------
44
- -- Step 2: Add missing_since column to projects (nullable)
45
- -- ---------------------------------------------------------------------------
46
- ALTER TABLE projects ADD COLUMN missing_since TEXT;
47
-
48
- -- ---------------------------------------------------------------------------
49
- -- Step 3: Bump schema_version to 16
50
- -- ---------------------------------------------------------------------------
51
- INSERT OR IGNORE INTO schema_version (version, applied_at, description)
52
- VALUES (16, strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),
53
- 'projects soft-delete: status + missing_since columns');
54
-
55
- -- Verification queries (run after applying):
56
- -- SELECT MAX(version) FROM schema_version; -- expect: 16
57
- -- PRAGMA table_info(projects); -- expect: status, missing_since columns present
@@ -1,18 +0,0 @@
1
- -- Migration v15 -> v16 fresh-install variant
2
- --
3
- -- Used by bootstrap_database.sh when the live DB was created directly from
4
- -- schema.sql at v16 state -- i.e. the `projects` table already carries the
5
- -- `status` and `missing_since` columns because schema.sql declared them that way.
6
- --
7
- -- On a fresh install there are no legacy rows to alter, so the ALTER TABLE
8
- -- statements in v15_to_v16.sql would fail with "duplicate column name".
9
- -- This variant is a no-op; it exists only so the bootstrap guard-probe branch
10
- -- (Section 3c, case 16) can select it and stamp the ledger without attempting
11
- -- the ALTER.
12
- --
13
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT.
14
- -- No DDL is executed; the COMMIT is harmless.
15
-
16
- -- No-op: fresh install already at v16 state (schema.sql created projects with
17
- -- status and missing_since columns).
18
- SELECT 1;
@@ -1,51 +0,0 @@
1
- -- Migration v16 -> v17 (DEMOTE case: workspaces soft-delete columns)
2
- --
3
- -- Background
4
- -- ----------
5
- -- v16 added soft-delete (status / missing_since) to the `projects` table. The
6
- -- multi-workspace DEMOTE test revealed the same gap one level up: when a
7
- -- workspace loses its Gaia install footprint (the user removes its `.claude/`,
8
- -- "demoting" it), the `workspaces` row had no way to record that. A re-scan of
9
- -- a demoted directory would persist the row and refresh `last_scan_at` as if it
10
- -- were still a live workspace.
11
- --
12
- -- This migration adds two scanner-owned columns to the `workspaces` table,
13
- -- mirroring the v16 projects soft-delete contract:
14
- --
15
- -- status TEXT NOT NULL DEFAULT 'active'
16
- -- Values: 'active' | 'missing'.
17
- -- 'active' -- the workspace carried a Gaia install on the last scan.
18
- -- 'missing' -- the workspace's install footprint disappeared (demoted);
19
- -- kept as a tombstone so its projects/history survive.
20
- -- Default 'active': existing rows (live workspaces at migration time) are
21
- -- classified active without requiring callers to supply the column.
22
- --
23
- -- missing_since TEXT (nullable)
24
- -- ISO8601 UTC timestamp of when status was first set to 'missing'.
25
- -- NULL when status='active'. Set on the first scan that finds the install
26
- -- gone; subsequent scans leave it unchanged (records the FIRST demote).
27
- --
28
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT. A failure
29
- -- mid-flight rolls back to v16 state; the ledger row is NOT inserted, so the
30
- -- next bootstrap retry sees the same pending migration.
31
-
32
- -- ---------------------------------------------------------------------------
33
- -- Step 1: Add status column to workspaces (NOT NULL DEFAULT 'active')
34
- -- ---------------------------------------------------------------------------
35
- ALTER TABLE workspaces ADD COLUMN status TEXT NOT NULL DEFAULT 'active';
36
-
37
- -- ---------------------------------------------------------------------------
38
- -- Step 2: Add missing_since column to workspaces (nullable)
39
- -- ---------------------------------------------------------------------------
40
- ALTER TABLE workspaces ADD COLUMN missing_since TEXT;
41
-
42
- -- ---------------------------------------------------------------------------
43
- -- Step 3: Bump schema_version to 17
44
- -- ---------------------------------------------------------------------------
45
- INSERT OR IGNORE INTO schema_version (version, applied_at, description)
46
- VALUES (17, strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),
47
- 'workspaces soft-delete: status + missing_since columns (DEMOTE)');
48
-
49
- -- Verification queries (run after applying):
50
- -- SELECT MAX(version) FROM schema_version; -- expect: 17
51
- -- PRAGMA table_info(workspaces); -- expect: status, missing_since columns present
@@ -1,18 +0,0 @@
1
- -- Migration v16 -> v17 fresh-install variant
2
- --
3
- -- Used by bootstrap_database.sh when the live DB was created directly from
4
- -- schema.sql at v17 state -- i.e. the `workspaces` table already carries the
5
- -- `status` and `missing_since` columns because schema.sql declared them that way.
6
- --
7
- -- On a fresh install there are no legacy rows to alter, so the ALTER TABLE
8
- -- statements in v16_to_v17.sql would fail with "duplicate column name".
9
- -- This variant is a no-op; it exists only so the bootstrap guard-probe branch
10
- -- (Section 3c, case 17) can select it and stamp the ledger without attempting
11
- -- the ALTER.
12
- --
13
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT.
14
- -- No DDL is executed; the COMMIT is harmless.
15
-
16
- -- No-op: fresh install already at v17 state (schema.sql created workspaces with
17
- -- status and missing_since columns).
18
- SELECT 1;
@@ -1,66 +0,0 @@
1
- -- Migration v17 -> v18 (stable project identity: collapse same repo across vantages)
2
- --
3
- -- Background
4
- -- ----------
5
- -- The `projects` table is keyed (workspace, name). The scanner derives `name`
6
- -- from the on-disk basename and `workspace` from the scan vantage's identity.
7
- -- The SAME physical repository scanned from two different roots -- e.g. once
8
- -- from the workspace root and once from inside the repo's own subdirectory
9
- -- (which resolves a different *workspace* identity) -- produced TWO distinct
10
- -- (workspace, name) rows: a duplicate of one physical project.
11
- --
12
- -- This migration adds a stable, vantage-independent project identity so the
13
- -- UPSERT can collapse those duplicates into one row. Identity is resolved by
14
- -- the scanner (tools/scan/store_populator.resolve_project_identity) in this
15
- -- order: git-common-dir (realpath) > normalized remote (host/owner/repo) >
16
- -- realpath of the project path.
17
- --
18
- -- Two additive, NON-DESTRUCTIVE changes:
19
- --
20
- -- 1. ALTER TABLE projects ADD COLUMN project_identity TEXT (nullable)
21
- -- Scanner-owned. NULL allowed for legacy/uninitialized rows so the column
22
- -- adds cleanly to an existing DB without backfill. A subsequent `gaia scan`
23
- -- populates it for every live project.
24
- --
25
- -- 2. CREATE UNIQUE INDEX idx_projects_identity ... WHERE project_identity IS NOT NULL
26
- -- PARTIAL unique index. Enforces one row per physical repo for rows that
27
- -- HAVE an identity, while exempting legacy NULL-identity rows from the
28
- -- uniqueness constraint (so multiple NULL rows can coexist until the next
29
- -- scan backfills them). This is the ON CONFLICT target the writer uses.
30
- --
31
- -- Backfill note
32
- -- -------------
33
- -- This migration does NOT backfill project_identity for existing rows -- it
34
- -- cannot derive git-common-dir from SQL alone, and the value is cheap to
35
- -- recompute on the next scan. Existing duplicate rows (if any) are left intact;
36
- -- the first scan after migration assigns identities and any genuine duplicate
37
- -- collapses on the following scan once both rows share an identity. Because the
38
- -- index is PARTIAL, the ADD COLUMN (all rows NULL) cannot fail on existing
39
- -- duplicates.
40
- --
41
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT. A failure
42
- -- mid-flight rolls back to v17 state; the ledger row is NOT inserted, so the
43
- -- next bootstrap retry sees the same pending migration.
44
-
45
- -- ---------------------------------------------------------------------------
46
- -- Step 1: Add project_identity column to projects (nullable, scanner-owned)
47
- -- ---------------------------------------------------------------------------
48
- ALTER TABLE projects ADD COLUMN project_identity TEXT;
49
-
50
- -- ---------------------------------------------------------------------------
51
- -- Step 2: Partial UNIQUE index that collapses the same physical repo to one row
52
- -- ---------------------------------------------------------------------------
53
- CREATE UNIQUE INDEX IF NOT EXISTS idx_projects_identity
54
- ON projects(project_identity) WHERE project_identity IS NOT NULL;
55
-
56
- -- ---------------------------------------------------------------------------
57
- -- Step 3: Bump schema_version to 18
58
- -- ---------------------------------------------------------------------------
59
- INSERT OR IGNORE INTO schema_version (version, applied_at, description)
60
- VALUES (18, strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),
61
- 'projects stable identity: project_identity column + partial unique index (collapse same repo across vantages)');
62
-
63
- -- Verification queries (run after applying):
64
- -- SELECT MAX(version) FROM schema_version; -- expect: 18
65
- -- SELECT name FROM pragma_table_info('projects') WHERE name='project_identity'; -- expect: project_identity
66
- -- SELECT name FROM sqlite_master WHERE type='index' AND name='idx_projects_identity'; -- expect: idx_projects_identity
@@ -1,24 +0,0 @@
1
- -- Migration v17 -> v18 fresh-install variant
2
- --
3
- -- Used by bootstrap_database.sh when the live DB was created directly from
4
- -- schema.sql at v18 state -- i.e. the `projects` table already carries the
5
- -- `project_identity` column because schema.sql declared it that way.
6
- --
7
- -- On a fresh install there are no legacy rows to alter, so the ALTER TABLE
8
- -- statement in v17_to_v18.sql would fail with "duplicate column name".
9
- -- This variant therefore SKIPS the ALTER but still creates the partial unique
10
- -- index -- the index is intentionally NOT declared in schema.sql because
11
- -- referencing project_identity there would parse-fail when bootstrapping a
12
- -- legacy (pre-v18) DB whose CREATE TABLE IF NOT EXISTS short-circuits before
13
- -- the column exists. Creating it here (idempotent IF NOT EXISTS) is the only
14
- -- place the index lands on a fresh install. Same convention as
15
- -- idx_memory_class_status (scripts/migrations/v3_to_v4_fresh.sql).
16
- --
17
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT.
18
-
19
- -- Step 1 (skipped on fresh install): the project_identity column already exists.
20
-
21
- -- Step 2: create the partial unique index that collapses the same physical repo
22
- -- (same project_identity) to one row, exempting NULL-identity legacy rows.
23
- CREATE UNIQUE INDEX IF NOT EXISTS idx_projects_identity
24
- ON projects(project_identity) WHERE project_identity IS NOT NULL;
@@ -1,97 +0,0 @@
1
- -- Migration v1 -> v2: widen memory.type CHECK constraint.
2
- --
3
- -- Background
4
- -- ----------
5
- -- v1 schema: memory.type CHECK (type IN ('project', 'user', 'feedback'))
6
- -- v2 schema: memory.type CHECK (type IN ('project', 'user', 'feedback', 'atom', 'decision', 'negative'))
7
- --
8
- -- The bug this migration fixes
9
- -- ----------------------------
10
- -- bootstrap_database.sh applies schema.sql with `CREATE TABLE IF NOT EXISTS`.
11
- -- On DBs created under v1 the CREATE short-circuits and the widened CHECK in
12
- -- schema.sql never lands -- yet the bootstrap historically stamped the
13
- -- schema_version ledger row for v2 unconditionally. Result: the ledger lied
14
- -- and writes of the new types ('atom', 'decision', 'negative') failed at
15
- -- runtime with CHECK constraint errors while `gaia doctor` reported OK.
16
- --
17
- -- Pattern: SQLite cannot ALTER a CHECK constraint. Canonical workaround:
18
- -- 1. Drop the FTS5 mirror triggers (avoid duplicate writes during copy).
19
- -- 2. Rename the old table out of the way.
20
- -- 3. Create the new table with the widened CHECK matching schema.sql.
21
- -- 4. Copy rows preserving rowid (memory_fts joins on rowid).
22
- -- 5. Drop the renamed old table.
23
- -- 6. Recreate the indexes on the new table.
24
- -- 7. Recreate the FTS5 mirror triggers verbatim.
25
- -- 8. Rebuild memory_fts from the new memory table.
26
- --
27
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT. A
28
- -- failure mid-flight rolls back to the v1 state and the ledger row is NOT
29
- -- inserted -- the next bootstrap retry will see the same pending migration.
30
- --
31
- -- Pre-conditions: caller has verified that the live memory.type CHECK does
32
- -- NOT yet include 'atom'. Applying this on an already-widened table would
33
- -- still succeed (the rename-create-copy ends in the same state) but the
34
- -- caller skips it to avoid unnecessary work.
35
-
36
- -- 1. Drop the FTS5 trigger trio. They mirror writes into memory_fts; we do
37
- -- not want them to fire during the bulk copy below. memory_fts itself is
38
- -- untouched here -- we will rebuild it after the copy completes.
39
- DROP TRIGGER IF EXISTS memory_ai;
40
- DROP TRIGGER IF EXISTS memory_ad;
41
- DROP TRIGGER IF EXISTS memory_au;
42
-
43
- -- 2. Rename the old table out of the way. The indexes (idx_memory_project,
44
- -- idx_memory_workspace, idx_memory_type) move with the table under their
45
- -- original names -- SQLite ALTER TABLE RENAME carries indexes along.
46
- ALTER TABLE memory RENAME TO memory_v1_legacy;
47
-
48
- -- 3. Create the new memory table with the widened CHECK constraint that
49
- -- matches schema.sql verbatim (6 allowed types).
50
- CREATE TABLE memory (
51
- workspace TEXT NOT NULL,
52
- name TEXT NOT NULL,
53
- type TEXT NOT NULL CHECK (type IN ('project', 'user', 'feedback', 'atom', 'decision', 'negative')),
54
- description TEXT,
55
- body TEXT NOT NULL,
56
- origin_session_id TEXT,
57
- updated_at TEXT,
58
- PRIMARY KEY (workspace, name),
59
- FOREIGN KEY (workspace) REFERENCES workspaces(name) ON DELETE CASCADE
60
- );
61
-
62
- -- 4. Copy all rows preserving rowid. This is critical: memory_fts joins on
63
- -- rowid, so changing them would invalidate the FTS5 index. We list rowid
64
- -- explicitly to bypass SQLite's default rowid allocation.
65
- INSERT INTO memory (rowid, workspace, name, type, description, body, origin_session_id, updated_at)
66
- SELECT rowid, workspace, name, type, description, body, origin_session_id, updated_at
67
- FROM memory_v1_legacy;
68
-
69
- -- 5. Drop the renamed old table. Its indexes go with it.
70
- DROP TABLE memory_v1_legacy;
71
-
72
- -- 6. Recreate the indexes on the new table. Same names as schema.sql.
73
- CREATE INDEX IF NOT EXISTS idx_memory_workspace ON memory(workspace);
74
- CREATE INDEX IF NOT EXISTS idx_memory_type ON memory(type);
75
-
76
- -- 7. Recreate the three FTS5 mirror triggers verbatim from schema.sql.
77
- CREATE TRIGGER memory_ai AFTER INSERT ON memory BEGIN
78
- INSERT INTO memory_fts(rowid, workspace, name, description, body)
79
- VALUES (new.rowid, new.workspace, new.name, new.description, new.body);
80
- END;
81
-
82
- CREATE TRIGGER memory_ad AFTER DELETE ON memory BEGIN
83
- INSERT INTO memory_fts(memory_fts, rowid, workspace, name, description, body)
84
- VALUES ('delete', old.rowid, old.workspace, old.name, old.description, old.body);
85
- END;
86
-
87
- CREATE TRIGGER memory_au AFTER UPDATE ON memory BEGIN
88
- INSERT INTO memory_fts(memory_fts, rowid, workspace, name, description, body)
89
- VALUES ('delete', old.rowid, old.workspace, old.name, old.description, old.body);
90
- INSERT INTO memory_fts(rowid, workspace, name, description, body)
91
- VALUES (new.rowid, new.workspace, new.name, new.description, new.body);
92
- END;
93
-
94
- -- 8. Rebuild memory_fts from the new memory table. FTS5's native reindex:
95
- -- clears the internal state and rescans the backing table. Cleaner than
96
- -- DELETE + INSERT loops.
97
- INSERT INTO memory_fts(memory_fts) VALUES('rebuild');
@@ -1,68 +0,0 @@
1
- -- Migration v2 -> v3 (rename path).
2
- --
3
- -- Background
4
- -- ----------
5
- -- v2 schema had:
6
- -- context_contracts (workspace, section_name, payload, metadata, updated_at)
7
- -- PK: (workspace, section_name)
8
- --
9
- -- v3 schema has:
10
- -- project_context_contracts (workspace, contract_name, payload, metadata, updated_at)
11
- -- PK: (workspace, contract_name)
12
- -- + new table agent_contract_permissions
13
- -- + new index idx_agent_contract_perms_agent
14
- --
15
- -- The rename of `context_contracts` -> `project_context_contracts` reflects
16
- -- its actual role: rows are project-context contracts, not permission grants.
17
- -- The column rename `section_name` -> `contract_name` aligns the vocabulary
18
- -- with the permission model introduced alongside it.
19
- --
20
- -- Three real-world entry states
21
- -- -----------------------------
22
- -- State 1 (only old): context_contracts exists, project_context_contracts does NOT.
23
- -- Typical of a clean v2 install upgrading for the first time.
24
- -- This script handles state 1 via ALTER TABLE RENAME.
25
- -- State 2 (only new): project_context_contracts exists, context_contracts does NOT,
26
- -- agent_contract_permissions exists. Nothing to do -- the guard
27
- -- probe in bootstrap_database.sh detects state 2 and stamps the
28
- -- ledger without invoking this script.
29
- -- State 3 (both): Both tables exist. Caused by a previous bootstrap where
30
- -- schema.sql under v3 was applied (CREATE TABLE IF NOT EXISTS
31
- -- created the new table) but the legacy context_contracts was
32
- -- never dropped. State 3 needs row migration + drop, handled
33
- -- by v2_to_v3_merge.sql -- not by this script.
34
- --
35
- -- bootstrap_database.sh selects state 1 vs 3 via the guard probe and runs the
36
- -- matching script. Each script is therefore single-purpose and atomic.
37
- --
38
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT. A failure
39
- -- mid-flight rolls back to v2 state and the ledger row is NOT inserted, so the
40
- -- next bootstrap retry will see the same pending migration.
41
-
42
- -- 1. Rename the table.
43
- ALTER TABLE context_contracts RENAME TO project_context_contracts;
44
-
45
- -- 2. Rename the column.
46
- ALTER TABLE project_context_contracts RENAME COLUMN section_name TO contract_name;
47
-
48
- -- 3. Index on workspace -- name reflects the new table identity. The old
49
- -- index moved with the table on RENAME TO, but its name still references
50
- -- the legacy table. Drop + recreate keeps the index name consistent
51
- -- with the new table identity.
52
- DROP INDEX IF EXISTS idx_context_contracts_workspace;
53
- CREATE INDEX IF NOT EXISTS idx_project_context_contracts_workspace
54
- ON project_context_contracts(workspace);
55
-
56
- -- 4. New permissions table + its index. IF NOT EXISTS makes the script
57
- -- safe to re-run if a partial earlier attempt already created them.
58
- CREATE TABLE IF NOT EXISTS agent_contract_permissions (
59
- agent_name TEXT NOT NULL,
60
- contract_name TEXT NOT NULL,
61
- can_read INTEGER NOT NULL DEFAULT 0,
62
- can_write INTEGER NOT NULL DEFAULT 0,
63
- cloud_scope TEXT,
64
- PRIMARY KEY (agent_name, contract_name, cloud_scope)
65
- );
66
-
67
- CREATE INDEX IF NOT EXISTS idx_agent_contract_perms_agent
68
- ON agent_contract_permissions(agent_name);
@@ -1,69 +0,0 @@
1
- -- Migration v2 -> v3 (merge path).
2
- --
3
- -- Runs when the guard probe in bootstrap_database.sh detects "state 3" --
4
- -- both `context_contracts` (legacy v2 table) AND `project_context_contracts`
5
- -- (new v3 table) exist. This happens when an earlier bootstrap under v3
6
- -- code applied schema.sql (which created the new table with IF NOT EXISTS)
7
- -- but the old table was never dropped because schema.sql no longer declares
8
- -- it and IF NOT EXISTS only creates -- it never drops.
9
- --
10
- -- See v2_to_v3.sql for the rename path (state 1) and for the full discussion
11
- -- of the three entry states.
12
- --
13
- -- Strategy
14
- -- --------
15
- -- The new table is created in v3 shape (column `contract_name`), the old
16
- -- table is in v2 shape (column `section_name`). The two columns are
17
- -- semantically identical, so we copy rows with column aliasing:
18
- --
19
- -- INSERT INTO project_context_contracts (workspace, contract_name, ...)
20
- -- SELECT workspace, section_name, ...
21
- -- FROM context_contracts
22
- -- WHERE (workspace, section_name) NOT IN (
23
- -- SELECT workspace, contract_name FROM project_context_contracts
24
- -- );
25
- --
26
- -- The NOT IN guard makes the copy idempotent: rows that were already
27
- -- migrated by a prior partial run are skipped instead of duplicating PK
28
- -- conflicts. Once the copy is complete and verified, the legacy table is
29
- -- dropped along with its index.
30
- --
31
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT. If
32
- -- the copy or the drop fails, the transaction rolls back, the ledger is
33
- -- NOT stamped, and the next bootstrap retry observes state 3 again.
34
-
35
- -- 1. Copy rows from the legacy table into the new one, skipping any whose
36
- -- primary key already exists in the new table.
37
- INSERT INTO project_context_contracts (workspace, contract_name, payload, metadata, updated_at)
38
- SELECT workspace, section_name, payload, metadata, updated_at
39
- FROM context_contracts
40
- WHERE (workspace, section_name) NOT IN (
41
- SELECT workspace, contract_name FROM project_context_contracts
42
- );
43
-
44
- -- 2. Drop the legacy index explicitly. SQLite drops indexes with their
45
- -- table on DROP TABLE, but being explicit catches the case where the
46
- -- index was created standalone or renamed without us noticing.
47
- DROP INDEX IF EXISTS idx_context_contracts_workspace;
48
-
49
- -- 3. Drop the legacy table. Its rows have already been copied above.
50
- DROP TABLE context_contracts;
51
-
52
- -- 4. Ensure the new index exists with the canonical name (in case an
53
- -- earlier partial run created the table but not its index).
54
- CREATE INDEX IF NOT EXISTS idx_project_context_contracts_workspace
55
- ON project_context_contracts(workspace);
56
-
57
- -- 5. Permissions table + index. IF NOT EXISTS makes this safe whether
58
- -- schema.sql already created them in a previous bootstrap or not.
59
- CREATE TABLE IF NOT EXISTS agent_contract_permissions (
60
- agent_name TEXT NOT NULL,
61
- contract_name TEXT NOT NULL,
62
- can_read INTEGER NOT NULL DEFAULT 0,
63
- can_write INTEGER NOT NULL DEFAULT 0,
64
- cloud_scope TEXT,
65
- PRIMARY KEY (agent_name, contract_name, cloud_scope)
66
- );
67
-
68
- CREATE INDEX IF NOT EXISTS idx_agent_contract_perms_agent
69
- ON agent_contract_permissions(agent_name);
@@ -1,67 +0,0 @@
1
- -- Migration v3 -> v4 (memory model: class + status + memory_links).
2
- --
3
- -- Background
4
- -- ----------
5
- -- v3 schema had:
6
- -- memory(workspace, name, type, description, body, origin_session_id, updated_at)
7
- -- PK: (workspace, name)
8
- -- type CHECK constraint: ('project','user','feedback','atom','decision','negative')
9
- --
10
- -- v4 schema adds two ortogonal axes to the memory model:
11
- -- * class -- semantic role (anchor | thread | log), nullable for legacy rows.
12
- -- * status -- lifecycle for class=thread (open | carry_forward | graduated | closed).
13
- --
14
- -- Plus a new table:
15
- -- memory_links(workspace, src_name, dst_name, kind, created_at)
16
- -- PK: (workspace, src_name, dst_name, kind)
17
- -- kind CHECK: ('relates_to','supersedes','derived_from','graduated_to')
18
- --
19
- -- Design decision: NO CHECK constraint on memory.class / memory.status
20
- -- ---------------------------------------------------------------------
21
- -- SQLite's ALTER TABLE ADD COLUMN cannot attach a CHECK that depends on
22
- -- the new column's enum values. The standard workaround is a full table
23
- -- rebuild (CREATE new, INSERT SELECT, DROP old, RENAME), which would:
24
- -- 1. Force re-creation of memory_fts triggers (drift risk on live DBs).
25
- -- 2. Move all existing memory rows through a copy step, complicating the
26
- -- AC-2 contract that "the 36 me-workspace rows survive intact" with
27
- -- byte-identical bodies.
28
- -- 3. Add complexity disproportionate to the gain -- the writer layer
29
- -- validates the enum on every upsert in T3 anyway.
30
- --
31
- -- Therefore: schema declares class/status as plain TEXT nullable columns.
32
- -- Enum enforcement is the writer's job (gaia/store/writer.py, T3).
33
- -- memory_links.kind keeps its CHECK because it is a brand-new table with
34
- -- no rebuild cost.
35
- --
36
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT. A
37
- -- failure mid-flight rolls back to v3 state and the ledger row is NOT
38
- -- inserted, so the next bootstrap retry sees the same pending migration.
39
-
40
- -- 1. Add the two nullable columns to memory. Existing rows get NULL,
41
- -- which is the documented "legacy row" state and is what T8/T9 will
42
- -- reclassify interactively.
43
- ALTER TABLE memory ADD COLUMN class TEXT;
44
- ALTER TABLE memory ADD COLUMN status TEXT;
45
-
46
- -- 2. Index on (workspace, class, status) -- supports the injector path
47
- -- that picks class=thread/status=carry_forward first.
48
- CREATE INDEX IF NOT EXISTS idx_memory_class_status
49
- ON memory(workspace, class, status);
50
-
51
- -- 3. New table memory_links + indexes. IF NOT EXISTS makes the script
52
- -- idempotent if a partial earlier attempt already created them.
53
- CREATE TABLE IF NOT EXISTS memory_links (
54
- workspace TEXT NOT NULL,
55
- src_name TEXT NOT NULL,
56
- dst_name TEXT NOT NULL,
57
- kind TEXT NOT NULL CHECK (kind IN ('relates_to', 'supersedes', 'derived_from', 'graduated_to')),
58
- created_at TEXT,
59
- PRIMARY KEY (workspace, src_name, dst_name, kind),
60
- FOREIGN KEY (workspace) REFERENCES workspaces(name) ON DELETE CASCADE
61
- );
62
-
63
- CREATE INDEX IF NOT EXISTS memory_links_src
64
- ON memory_links(workspace, src_name);
65
-
66
- CREATE INDEX IF NOT EXISTS idx_memory_links_dst_kind
67
- ON memory_links(workspace, dst_name, kind);
@@ -1,20 +0,0 @@
1
- -- v3 -> v4 fresh-install variant.
2
- --
3
- -- Used by bootstrap_database.sh Section 3c case 4 when the live DDL is
4
- -- already at the v4 target state (memory.class column present, memory_links
5
- -- table present). This happens on a clean install where schema.sql already
6
- -- created the v4 column layout and the memory_links table.
7
- --
8
- -- The default v3_to_v4.sql cannot run here because ALTER TABLE ADD COLUMN
9
- -- fails when the column already exists. This variant carries only the DDL
10
- -- that schema.sql cannot declare safely:
11
- -- * idx_memory_class_status -- references columns added at ALTER time,
12
- -- so schema.sql cannot pre-declare it (the replay on v3 DBs would parse-
13
- -- fail before the migration ran).
14
- --
15
- -- Everything else (memory_links table, memory_links indexes) is declared in
16
- -- schema.sql and is therefore already present on a fresh install. CREATE
17
- -- INDEX IF NOT EXISTS makes this script safe to re-run.
18
-
19
- CREATE INDEX IF NOT EXISTS idx_memory_class_status
20
- ON memory(workspace, class, status);
@@ -1,55 +0,0 @@
1
- -- Migration v4 -> v5 (state-machine completion: status columns on AC + milestones).
2
- --
3
- -- Background
4
- -- ----------
5
- -- v4 schema has:
6
- -- acceptance_criteria(id, brief_id, ac_id, description, evidence_type,
7
- -- evidence_shape, artifact_path)
8
- -- milestones(id, brief_id, order_num, name, description)
9
- --
10
- -- v5 schema adds a status lifecycle column to both tables:
11
- -- * acceptance_criteria.status -- lifecycle (pending | done | blocked)
12
- -- * milestones.status -- lifecycle (pending | done | blocked)
13
- --
14
- -- Design decision: CHECK constraint inline on ADD COLUMN
15
- -- -------------------------------------------------------
16
- -- SQLite >= 3.37 supports ADD COLUMN with NOT NULL + DEFAULT + CHECK in a
17
- -- single statement without a full table rebuild. The bootstrap environment
18
- -- is confirmed at SQLite 3.45.1, so this is safe.
19
- --
20
- -- The enum values are intentionally small: pending (default), done (AC
21
- -- satisfied / milestone reached), blocked (cannot progress, needs action).
22
- -- They mirror the task lifecycle (pending|done|skipped) but substitute
23
- -- 'blocked' for 'skipped' to distinguish an actively-stuck entity from one
24
- -- that was explicitly bypassed.
25
- --
26
- -- D2 (backfill): explicit UPDATE after ALTER ensures pre-existing rows
27
- -- carry 'pending'. The NOT NULL DEFAULT 'pending' covers new rows; the
28
- -- UPDATE covers rows inserted before this migration.
29
- --
30
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT.
31
- -- A failure mid-flight rolls back to v4 state and the ledger row is NOT
32
- -- inserted, so the next bootstrap retry sees the same pending migration.
33
-
34
- -- 1. Add status column to acceptance_criteria.
35
- ALTER TABLE acceptance_criteria
36
- ADD COLUMN status TEXT NOT NULL DEFAULT 'pending'
37
- CHECK (status IN ('pending', 'done', 'blocked'));
38
-
39
- -- 2. Backfill existing AC rows (D2: explicit UPDATE, all rows -> 'pending').
40
- UPDATE acceptance_criteria SET status = 'pending' WHERE status IS NULL;
41
-
42
- -- 3. Add status column to milestones.
43
- ALTER TABLE milestones
44
- ADD COLUMN status TEXT NOT NULL DEFAULT 'pending'
45
- CHECK (status IN ('pending', 'done', 'blocked'));
46
-
47
- -- 4. Backfill existing milestone rows (D2).
48
- UPDATE milestones SET status = 'pending' WHERE status IS NULL;
49
-
50
- -- 5. Indexes to support brief-scoped status queries efficiently.
51
- CREATE INDEX IF NOT EXISTS idx_ac_brief_status
52
- ON acceptance_criteria(brief_id, status);
53
-
54
- CREATE INDEX IF NOT EXISTS idx_milestones_brief_status
55
- ON milestones(brief_id, status);
@@ -1,20 +0,0 @@
1
- -- v4 -> v5 fresh-install variant.
2
- --
3
- -- Used by bootstrap_database.sh Section 3c case 5 when the live DDL is
4
- -- already at the v5 target state (acceptance_criteria.status column present,
5
- -- milestones.status column present). This happens on a clean install where
6
- -- schema.sql already created the v5 column layout.
7
- --
8
- -- The default v4_to_v5.sql cannot run here because ALTER TABLE ADD COLUMN
9
- -- fails when the column already exists. This variant carries only the DDL
10
- -- that schema.sql cannot declare safely:
11
- -- * idx_ac_brief_status -- index on acceptance_criteria(brief_id, status)
12
- -- * idx_milestones_brief_status -- index on milestones(brief_id, status)
13
- --
14
- -- Both indexes are CREATE INDEX IF NOT EXISTS, making this script safe to re-run.
15
-
16
- CREATE INDEX IF NOT EXISTS idx_ac_brief_status
17
- ON acceptance_criteria(brief_id, status);
18
-
19
- CREATE INDEX IF NOT EXISTS idx_milestones_brief_status
20
- ON milestones(brief_id, status);