@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,170 +0,0 @@
1
- -- Migration v10 -> v11
2
- --
3
- -- Two structural changes in a single migration:
4
- --
5
- -- Part A: memory.class NOT NULL + CHECK constraint enforcement
6
- -- Part B: trg_pcc_history trigger — fix column references (contract_key ->
7
- -- contract_name, payload_json -> payload) that caused runtime errors
8
- -- in v1->v2 migration path.
9
- --
10
- -- Background (Part A)
11
- -- -------------------
12
- -- v4 introduced memory.class as a NULLABLE column with enum enforcement only
13
- -- at the writer layer (not in DDL). The explicit decision documented in
14
- -- schema.sql was:
15
- -- "adding a CHECK at ALTER TIME forces a table-rebuild that would risk
16
- -- FTS trigger drift on the live DB"
17
- -- Task #2 reclassified all pre-v4 NULL rows, so the precondition for a safe
18
- -- rebuild is satisfied (0 rows with class IS NULL).
19
- --
20
- -- The rebuild follows the same rename-create-copy-drop pattern as v1_to_v2.sql:
21
- -- 1. Guard: fail fast if any NULL class rows exist.
22
- -- 2. Drop FTS5 mirror triggers (avoid double-writes during bulk copy).
23
- -- 3. Rename old table out of the way.
24
- -- 4. Create new table with NOT NULL CHECK(class IN ('anchor','thread','log')).
25
- -- 5. Copy rows preserving rowid.
26
- -- 6. Drop renamed old table.
27
- -- 7. Recreate indexes (workspace, type, class+status).
28
- -- 8. Recreate FTS5 mirror triggers verbatim.
29
- -- 9. Rebuild memory_fts.
30
- --
31
- -- Background (Part B)
32
- -- -------------------
33
- -- v8_to_v9.sql created trg_pcc_history referencing:
34
- -- OLD.contract_key -- but project_context_contracts has column contract_name
35
- -- OLD.payload_json -- but project_context_contracts has column payload
36
- -- NEW.payload_json -- same
37
- -- The trigger DDL was stored in sqlite_master with the wrong column names. SQLite
38
- -- defers column resolution to execution time, so the trigger was silently
39
- -- accepted at CREATE time but blew up whenever it fired, AND when any
40
- -- DDL mutation (like ALTER TABLE in v1_to_v2 migration) caused SQLite to
41
- -- validate all live triggers -- aborting the v1->v2 migration transaction.
42
- --
43
- -- Precondition guards
44
- -- -------------------
45
- -- We assert 0 NULL class rows before starting the rebuild. If any exist, the
46
- -- migration aborts and the caller must run `gaia memory reclassify` first.
47
- -- SQLite does not have native ASSERT, so we use a CREATE TABLE trick: if the
48
- -- subquery returns any rows the INSERT will succeed but we check with a
49
- -- NOT EXISTS guard pattern via CASE WHEN inside a temporary trigger.
50
- -- Simpler: we use a CHECK on a temp row that fails if the count > 0.
51
- --
52
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT.
53
- -- A failure mid-flight rolls back to v10 state; the ledger row is NOT
54
- -- inserted, so the next bootstrap retry sees the same pending migration.
55
- -- Closes ledger task #6.
56
-
57
- -- ============================================================
58
- -- PRE-CONDITION: coalesce any remaining NULL class values to 'log'
59
- -- ============================================================
60
- -- On the live DB, task #2 reclassified all NULL rows before this migration
61
- -- runs, so this UPDATE is a no-op. In test/CI scenarios where a synthetic
62
- -- DB is built from v1 and migrated through all versions, legacy rows that
63
- -- were never reclassified get a safe default of 'log'. Using COALESCE in
64
- -- the INSERT SELECT below (Step A4) achieves the same result without a
65
- -- separate UPDATE pass, but an explicit UPDATE here makes the intent clear
66
- -- and ensures the NOT NULL CHECK in the new table never fires unexpectedly.
67
- UPDATE memory SET class = 'log' WHERE class IS NULL;
68
-
69
- -- ============================================================
70
- -- PART A: memory table rebuild (NOT NULL + CHECK on class)
71
- -- ============================================================
72
-
73
- -- Step A1: Drop the FTS5 trigger trio before the rename. They will be
74
- -- recreated verbatim in Step A8. Dropping them prevents double-writes
75
- -- and avoids referencing the renamed table during the bulk copy.
76
- DROP TRIGGER IF EXISTS memory_ai;
77
- DROP TRIGGER IF EXISTS memory_ad;
78
- DROP TRIGGER IF EXISTS memory_au;
79
-
80
- -- Step A2: Rename old table out of the way. SQLite carries indexes along.
81
- ALTER TABLE memory RENAME TO memory_v10_old;
82
-
83
- -- Step A3: Create the new memory table with NOT NULL DEFAULT + CHECK on class.
84
- -- Schema matches schema.sql exactly: class is NOT NULL with DEFAULT 'log' and
85
- -- enum CHECK. The DEFAULT ensures that callers who do not supply class (e.g.
86
- -- upsert_memory in writer.py) get a sensible default rather than a hard failure.
87
- -- Explicit NULL is still rejected by NOT NULL.
88
- CREATE TABLE memory (
89
- workspace TEXT NOT NULL,
90
- name TEXT NOT NULL,
91
- type TEXT NOT NULL CHECK (type IN ('project', 'user', 'feedback', 'atom', 'decision', 'negative')),
92
- description TEXT,
93
- body TEXT NOT NULL,
94
- origin_session_id TEXT,
95
- updated_at TEXT,
96
- class TEXT NOT NULL DEFAULT 'log' CHECK (class IN ('anchor', 'thread', 'log')),
97
- status TEXT,
98
- PRIMARY KEY (workspace, name),
99
- FOREIGN KEY (workspace) REFERENCES workspaces(name) ON DELETE CASCADE
100
- );
101
-
102
- -- Step A4: Copy all rows preserving rowid. memory_fts joins on rowid;
103
- -- changing them would invalidate the FTS5 index. class is guaranteed
104
- -- non-NULL by the UPDATE in the pre-condition step above.
105
- INSERT INTO memory (rowid, workspace, name, type, description, body, origin_session_id, updated_at, class, status)
106
- SELECT rowid, workspace, name, type, description, body, origin_session_id, updated_at, class, status
107
- FROM memory_v10_old;
108
-
109
- -- Step A5: Drop the renamed old table. Its indexes go with it.
110
- DROP TABLE memory_v10_old;
111
-
112
- -- Step A6: Recreate the standard indexes on the new table.
113
- CREATE INDEX IF NOT EXISTS idx_memory_workspace ON memory(workspace);
114
- CREATE INDEX IF NOT EXISTS idx_memory_type ON memory(type);
115
- -- idx_memory_class_status was created by v3_to_v4.sql; must be recreated here
116
- -- because the underlying table was dropped and recreated above.
117
- CREATE INDEX IF NOT EXISTS idx_memory_class_status ON memory(workspace, class, status);
118
-
119
- -- Step A7: Recreate the three FTS5 mirror triggers verbatim from schema.sql.
120
- CREATE TRIGGER memory_ai AFTER INSERT ON memory BEGIN
121
- INSERT INTO memory_fts(rowid, workspace, name, description, body)
122
- VALUES (new.rowid, new.workspace, new.name, new.description, new.body);
123
- END;
124
-
125
- CREATE TRIGGER memory_ad AFTER DELETE ON memory BEGIN
126
- INSERT INTO memory_fts(memory_fts, rowid, workspace, name, description, body)
127
- VALUES ('delete', old.rowid, old.workspace, old.name, old.description, old.body);
128
- END;
129
-
130
- CREATE TRIGGER memory_au AFTER UPDATE ON memory BEGIN
131
- INSERT INTO memory_fts(memory_fts, rowid, workspace, name, description, body)
132
- VALUES ('delete', old.rowid, old.workspace, old.name, old.description, old.body);
133
- INSERT INTO memory_fts(rowid, workspace, name, description, body)
134
- VALUES (new.rowid, new.workspace, new.name, new.description, new.body);
135
- END;
136
-
137
- -- Step A8: Rebuild memory_fts from the new memory table.
138
- INSERT INTO memory_fts(memory_fts) VALUES('rebuild');
139
-
140
- -- ============================================================
141
- -- PART B: trg_pcc_history trigger fix
142
- -- ============================================================
143
- -- Drop the broken trigger (created by v8_to_v9.sql with wrong column refs).
144
- DROP TRIGGER IF EXISTS trg_pcc_history;
145
-
146
- -- Recreate with correct column references:
147
- -- OLD.contract_name (project_context_contracts PK component)
148
- -- OLD.payload (project_context_contracts payload column)
149
- -- NEW.payload (project_context_contracts payload column)
150
- -- The INSERT target history table still uses column `contract_key` (unchanged).
151
- CREATE TRIGGER trg_pcc_history
152
- AFTER UPDATE ON project_context_contracts
153
- BEGIN
154
- INSERT INTO project_context_contracts_history (
155
- contract_key, workspace, before_payload_json, after_payload_json, changed_at
156
- ) VALUES (
157
- OLD.contract_name,
158
- OLD.workspace,
159
- OLD.payload,
160
- NEW.payload,
161
- strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
162
- );
163
- END;
164
-
165
- -- ============================================================
166
- -- LEDGER BUMP
167
- -- ============================================================
168
- INSERT OR IGNORE INTO schema_version (version, applied_at, description)
169
- VALUES (11, strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),
170
- 'memory.class NOT NULL + CHECK; trg_pcc_history column fix (task #6)');
@@ -1,18 +0,0 @@
1
- -- Migration v10 -> v11 fresh-install variant
2
- --
3
- -- Used by bootstrap_database.sh when the live DB was created directly from
4
- -- schema.sql at v11 state (i.e. schema.sql already declares memory.class as
5
- -- NOT NULL CHECK and trg_pcc_history with correct column references).
6
- --
7
- -- On a fresh install:
8
- -- - schema.sql creates memory with class NOT NULL CHECK -> no rebuild needed
9
- -- - schema.sql creates trg_pcc_history with correct column refs -> no fix needed
10
- --
11
- -- This variant is a no-op; it only exists so the bootstrap guard-probe branch
12
- -- can select it and stamp the ledger without applying DDL.
13
- --
14
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT.
15
- -- No DDL is executed; the COMMIT is harmless.
16
-
17
- -- No-op: fresh install already at v11 state (schema.sql created all objects).
18
- SELECT 1;
@@ -1,195 +0,0 @@
1
- -- Migration v11 -> v12 (approval-model-redesign: user-in-loop, fingerprint-bound, hash-chained)
2
- --
3
- -- Background
4
- -- ----------
5
- -- v11 schema has all the episodic/memory/handoff tables from prior migrations.
6
- -- v12 adds two tables for the new approval model:
7
- -- approvals -- durable record per approval lifecycle, P-{id} prefixed
8
- -- approval_events -- append-only hash-chained audit log per lifecycle event
9
- --
10
- -- Three trigger families:
11
- -- ai_approval_events_hash -- AFTER INSERT: computes this_hash via gaia_sha256()
12
- -- bu_approval_events_immutable -- BEFORE UPDATE: raises (append-only invariant)
13
- -- bd_approval_events_immutable -- BEFORE DELETE: raises (append-only invariant)
14
- --
15
- -- Design decisions (from plan D15 and brief approach)
16
- -- ----------------------------------------------------
17
- -- D1: approvals.id carries P-{uuid4} prefix (TEXT PK, not AUTOINCREMENT INTEGER).
18
- -- Rationale: the prefix is readable in denial messages and debug output without
19
- -- a JOIN. UUIDs avoid collisions without a central counter. The hook generates
20
- -- the id and embeds it in the denial message so subagents can reference it.
21
- --
22
- -- D2: approval_events.this_hash = SHA-256(prev_hash || fingerprint)
23
- -- Computed by the AFTER INSERT trigger via SQLite scalar function `gaia_sha256`
24
- -- registered in gaia.store.writer._connect(). SQLite's built-in functions
25
- -- do not include SHA-256 so we inject a Python function at connection time.
26
- -- The trigger runs deterministically with the connection's registered function.
27
- --
28
- -- D3: Genesis row bootstrapping
29
- -- For the first event row of any approval chain (row 0), prev_hash IS NULL.
30
- -- this_hash = SHA-256("" || fingerprint) where the null is treated as an
31
- -- empty string by the trigger expression COALESCE(prev_hash, '').
32
- -- This is the documented canonical treatment -- callers should not assume a
33
- -- sentinel hash (like all-zeros); they must use COALESCE('', prev_hash) when
34
- -- walking the chain. The chain_walk validator in gaia/approvals/chain.py
35
- -- implements this correctly.
36
- --
37
- -- D4: Append-only invariant
38
- -- BEFORE UPDATE and BEFORE DELETE triggers raise an error with the literal
39
- -- message "approval_events is append-only" so any accidental mutative SQL
40
- -- gets a clear, actionable error rather than a silent no-op or wrong-table
41
- -- write. These triggers are part of the security contract: a tampered row
42
- -- breaks hash-chain validation *and* prevents direct mutation at the SQL layer.
43
- --
44
- -- D5: event_type CHECK constraint
45
- -- Nine valid event types from the plan spec. `EXPIRED` is intentionally
46
- -- excluded (no TTL-based expiry in this brief). `ESCALATED` is excluded
47
- -- (no multi-level approval chain in this brief). The CHECK is on the
48
- -- approval_events table so the constraint is enforced at the DB layer.
49
- --
50
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT.
51
- -- A failure mid-flight rolls back to v11 state; the ledger row is NOT
52
- -- inserted, so the next bootstrap retry sees the same pending migration.
53
- -- Closes M1 (Wave 1) of brief approval-model-redesign-user-in-loop (brief_id=71).
54
-
55
- -- ---------------------------------------------------------------------------
56
- -- Step 1: Create approvals table (durable lifecycle record)
57
- -- ---------------------------------------------------------------------------
58
- CREATE TABLE IF NOT EXISTS approvals (
59
- id TEXT PRIMARY KEY, -- P-{uuid4} prefixed identifier
60
- agent_id TEXT, -- agent that initiated the request
61
- session_id TEXT, -- CLAUDE_SESSION_ID at request time
62
- status TEXT NOT NULL DEFAULT 'pending'
63
- CHECK (status IN ('pending', 'approved', 'rejected', 'revoked', 'expired')),
64
- fingerprint TEXT, -- SHA-256 hex of canonical sealed_payload_json
65
- payload_json TEXT, -- canonical-JSON sealed_payload at REQUESTED time
66
- created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
67
- decided_at TEXT -- ISO-8601 UTC when approved/rejected/revoked
68
- );
69
-
70
- -- Indexes for the common query patterns:
71
- -- (a) All pending approvals regardless of session (cross-session recovery)
72
- -- (b) All approvals for a specific agent
73
- -- (c) All approvals for a specific session
74
- CREATE INDEX IF NOT EXISTS idx_approvals_status ON approvals(status);
75
- CREATE INDEX IF NOT EXISTS idx_approvals_agent ON approvals(agent_id);
76
- CREATE INDEX IF NOT EXISTS idx_approvals_session ON approvals(session_id);
77
-
78
- -- ---------------------------------------------------------------------------
79
- -- Step 2: Create approval_events table (append-only hash-chained audit log)
80
- --
81
- -- Column inventory from plan D15 (verbatim):
82
- -- id, approval_id (FK), event_type, agent_id, session_id,
83
- -- payload_json, fingerprint, prev_hash, this_hash, metadata_json, created_at
84
- -- ---------------------------------------------------------------------------
85
- CREATE TABLE IF NOT EXISTS approval_events (
86
- id INTEGER PRIMARY KEY AUTOINCREMENT,
87
- approval_id TEXT NOT NULL, -- FK -> approvals.id (P-{uuid4})
88
- event_type TEXT NOT NULL CHECK (event_type IN (
89
- 'REQUESTED',
90
- 'SHOWN',
91
- 'APPROVED',
92
- 'REJECTED',
93
- 'EXECUTED',
94
- 'FAILED',
95
- 'NOOP',
96
- 'REVOKED',
97
- 'REVERTED'
98
- )),
99
- agent_id TEXT, -- agent that triggered this event
100
- session_id TEXT, -- session that created this event row
101
- payload_json TEXT, -- canonical-JSON sealed_payload at this event
102
- fingerprint TEXT, -- SHA-256 hex of canonical payload_json
103
- prev_hash TEXT, -- this_hash of the immediately preceding row
104
- -- NULL for the genesis row (row 0 in the chain)
105
- this_hash TEXT, -- SHA-256(COALESCE(prev_hash,'') || COALESCE(fingerprint,''))
106
- -- computed by ai_approval_events_hash AFTER INSERT trigger
107
- metadata_json TEXT, -- free-form JSON for event-specific extras
108
- created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
109
- FOREIGN KEY (approval_id) REFERENCES approvals(id)
110
- );
111
-
112
- -- Indexes for the common query patterns:
113
- -- (a) All events for a specific approval (primary chain-walk pattern)
114
- -- (b) All events of a specific type across all approvals (audit dashboard)
115
- -- (c) All events for a specific session
116
- CREATE INDEX IF NOT EXISTS idx_approval_events_approval ON approval_events(approval_id, id);
117
- CREATE INDEX IF NOT EXISTS idx_approval_events_type ON approval_events(event_type);
118
- CREATE INDEX IF NOT EXISTS idx_approval_events_session ON approval_events(session_id);
119
-
120
- -- ---------------------------------------------------------------------------
121
- -- Step 3: this_hash computation -- application layer, not a trigger
122
- --
123
- -- The AFTER INSERT + BEFORE UPDATE combination is architecturally conflicted
124
- -- in SQLite: an AFTER INSERT trigger that calls UPDATE on the same row would
125
- -- fire the BEFORE UPDATE immutability trigger, blocking the computation.
126
- --
127
- -- Resolution: this_hash is computed by the application layer before each
128
- -- INSERT via gaia.approvals.chain._compute_this_hash(prev_hash, fingerprint).
129
- -- All INSERTs into approval_events MUST supply a computed this_hash value.
130
- -- The Python helper gaia.approvals.chain.insert_event() enforces this at
131
- -- the API boundary.
132
- --
133
- -- The gaia_sha256 scalar function is still registered on connections by
134
- -- gaia.store.writer._connect() for ad-hoc queries, chain-walk re-validation,
135
- -- and future trigger uses that do not conflict with the immutability triggers.
136
- --
137
- -- Named placeholder trigger (for schema consistency and test assertions about
138
- -- trigger count): We create a named view-only trigger that does nothing, so
139
- -- that `gaia doctor` can assert "ai_approval_events_hash trigger exists" and
140
- -- the schema introspection is consistent with the migration spec.
141
- -- This is intentionally a no-op SELECT that documents the design decision.
142
- -- ---------------------------------------------------------------------------
143
- CREATE TRIGGER IF NOT EXISTS ai_approval_events_hash
144
- AFTER INSERT ON approval_events
145
- BEGIN
146
- -- this_hash is computed by the application layer before INSERT.
147
- -- See gaia.approvals.chain._compute_this_hash() and insert_event().
148
- -- This trigger is a named placeholder for schema introspection consistency.
149
- SELECT 1;
150
- END;
151
-
152
- -- ---------------------------------------------------------------------------
153
- -- Step 4: BEFORE UPDATE trigger -- enforce append-only invariant
154
- --
155
- -- Trigger name: bu_approval_events_immutable
156
- -- bu_ prefix = BEFORE UPDATE
157
- --
158
- -- Raises with a clear message so accidental UPDATEs surface immediately
159
- -- rather than silently corrupting the audit chain.
160
- -- ---------------------------------------------------------------------------
161
- CREATE TRIGGER IF NOT EXISTS bu_approval_events_immutable
162
- BEFORE UPDATE ON approval_events
163
- BEGIN
164
- SELECT RAISE(ABORT, 'approval_events is append-only');
165
- END;
166
-
167
- -- ---------------------------------------------------------------------------
168
- -- Step 5: BEFORE DELETE trigger -- enforce append-only invariant
169
- --
170
- -- Trigger name: bd_approval_events_immutable
171
- -- bd_ prefix = BEFORE DELETE
172
- -- ---------------------------------------------------------------------------
173
- CREATE TRIGGER IF NOT EXISTS bd_approval_events_immutable
174
- BEFORE DELETE ON approval_events
175
- BEGIN
176
- SELECT RAISE(ABORT, 'approval_events is append-only');
177
- END;
178
-
179
- -- ---------------------------------------------------------------------------
180
- -- Step 6: Bump schema_version to 12
181
- -- ---------------------------------------------------------------------------
182
- INSERT OR IGNORE INTO schema_version (version, applied_at, description)
183
- VALUES (12, strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),
184
- 'approvals + approval_events tables + hash-chain triggers (approval-model-redesign M1)');
185
-
186
- -- Verification queries (run after applying):
187
- -- SELECT MAX(version) FROM schema_version; -- expect: 12
188
- -- SELECT name FROM sqlite_master WHERE type='table'
189
- -- AND name IN ('approvals','approval_events'); -- expect: 2 rows
190
- -- SELECT name FROM sqlite_master WHERE type='trigger'
191
- -- AND name IN ('ai_approval_events_hash',
192
- -- 'bu_approval_events_immutable',
193
- -- 'bd_approval_events_immutable'); -- expect: 3 rows
194
- -- PRAGMA table_info(approvals); -- expect: 7 columns
195
- -- PRAGMA table_info(approval_events); -- expect: 11 columns
@@ -1,19 +0,0 @@
1
- -- Migration v11 -> v12 fresh-install variant
2
- --
3
- -- Used by bootstrap_database.sh when the live DB was created directly from
4
- -- schema.sql at v12 state (i.e. schema.sql already declares the approvals
5
- -- and approval_events tables plus the trigger family).
6
- --
7
- -- On a fresh install:
8
- -- - schema.sql creates approvals with all columns -> no DDL needed
9
- -- - schema.sql creates approval_events with all columns -> no DDL needed
10
- -- - schema.sql creates all three triggers -> no DDL needed
11
- --
12
- -- This variant is a no-op; it only exists so the bootstrap guard-probe branch
13
- -- can select it and stamp the ledger without applying DDL.
14
- --
15
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT.
16
- -- No DDL is executed; the COMMIT is harmless.
17
-
18
- -- No-op: fresh install already at v12 state (schema.sql created all objects).
19
- SELECT 1;
@@ -1,48 +0,0 @@
1
- -- Migration v12 -> v13 (gaia-scan-overhaul: workspace->group->repo model, AC-2)
2
- --
3
- -- Background
4
- -- ----------
5
- -- v12 schema has the durable approvals / approval_events tables from the
6
- -- approval-model-redesign brief. v13 adds a `group_name` column to the
7
- -- `projects` table to support the workspace->group->repo hierarchy introduced
8
- -- by the gaia-scan-overhaul brief (AC-2).
9
- --
10
- -- Why `group_name` and not `group`
11
- -- ---------------------------------
12
- -- `group` is a reserved keyword in SQL. Using it as a column name requires
13
- -- quoting everywhere and produces subtle bugs in hand-written queries.
14
- -- `group_name` is the canonical column name for this feature.
15
- --
16
- -- Column semantics
17
- -- ----------------
18
- -- group_name TEXT (nullable)
19
- -- The optional organizational group or team this project belongs to within
20
- -- its workspace. For example, in a GitHub organization the group_name might
21
- -- be a team slug, a monorepo sub-directory, or any intermediate container
22
- -- between the workspace and the individual project (repo).
23
- --
24
- -- NULL = ungrouped (the default for all pre-existing rows after migration).
25
- -- The value is assigned by populate_project (T2.2 of the plan) using
26
- -- scanner-specific detection logic. This migration only adds the column;
27
- -- it does NOT back-fill existing rows.
28
- --
29
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT.
30
- -- A failure mid-flight rolls back to v12 state; the ledger row is NOT
31
- -- inserted, so the next bootstrap retry sees the same pending migration.
32
- -- Closes T1.1 (schema + writer plumbing) of brief gaia-scan-overhaul.
33
-
34
- -- ---------------------------------------------------------------------------
35
- -- Step 1: Add group_name column to projects
36
- -- ---------------------------------------------------------------------------
37
- ALTER TABLE projects ADD COLUMN group_name TEXT;
38
-
39
- -- ---------------------------------------------------------------------------
40
- -- Step 2: Bump schema_version to 13
41
- -- ---------------------------------------------------------------------------
42
- INSERT OR IGNORE INTO schema_version (version, applied_at, description)
43
- VALUES (13, strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),
44
- 'projects.group_name column (workspace->group->repo, AC-2)');
45
-
46
- -- Verification queries (run after applying):
47
- -- SELECT MAX(version) FROM schema_version; -- expect: 13
48
- -- PRAGMA table_info(projects); -- expect group_name column present
@@ -1,17 +0,0 @@
1
- -- Migration v12 -> v13 fresh-install variant
2
- --
3
- -- Used by bootstrap_database.sh when the live DB was created directly from
4
- -- schema.sql at v13 state (i.e. schema.sql already declares the group_name
5
- -- column on the projects table).
6
- --
7
- -- On a fresh install:
8
- -- - schema.sql creates projects with all columns including group_name -> no DDL needed
9
- --
10
- -- This variant is a no-op; it only exists so the bootstrap guard-probe branch
11
- -- can select it and stamp the ledger without applying DDL.
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 v13 state (schema.sql created all objects).
17
- SELECT 1;
@@ -1,44 +0,0 @@
1
- -- Migration v13 -> v14 (gaia-scan-overhaul: projects.path column, findability)
2
- --
3
- -- Background
4
- -- ----------
5
- -- v13 schema added `group_name` to the `projects` table (workspace->group->repo
6
- -- hierarchy). v14 adds a `path TEXT` column to the same table so that the
7
- -- scanner can persist the absolute on-disk path of each project, enabling
8
- -- name-based findability (project name -> path + workspace) without
9
- -- re-scanning or relying on external tooling.
10
- --
11
- -- Column semantics
12
- -- ----------------
13
- -- path TEXT (nullable)
14
- -- Absolute filesystem path to the project root directory on the machine
15
- -- where the scanner ran. For example: '/home/jorge/ws/me/gaia'.
16
- --
17
- -- NULL = path not yet recorded (the default for all pre-existing rows
18
- -- after migration; also the default for rows created before the scanner
19
- -- logic that assigns the value is deployed).
20
- -- The value is assigned by populate_project (T2.x of the plan) using
21
- -- the project_path argument already threaded through the scanner API.
22
- -- This migration only adds the column; it does NOT back-fill existing
23
- -- rows.
24
- --
25
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT.
26
- -- A failure mid-flight rolls back to v13 state; the ledger row is NOT
27
- -- inserted, so the next bootstrap retry sees the same pending migration.
28
- -- Closes T1.2 (schema + writer plumbing) of brief gaia-scan-overhaul.
29
-
30
- -- ---------------------------------------------------------------------------
31
- -- Step 1: Add path column to projects
32
- -- ---------------------------------------------------------------------------
33
- ALTER TABLE projects ADD COLUMN path TEXT;
34
-
35
- -- ---------------------------------------------------------------------------
36
- -- Step 2: Bump schema_version to 14
37
- -- ---------------------------------------------------------------------------
38
- INSERT OR IGNORE INTO schema_version (version, applied_at, description)
39
- VALUES (14, strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),
40
- 'projects.path column (findability: project -> path + workspace)');
41
-
42
- -- Verification queries (run after applying):
43
- -- SELECT MAX(version) FROM schema_version; -- expect: 14
44
- -- PRAGMA table_info(projects); -- expect path column present
@@ -1,17 +0,0 @@
1
- -- Migration v13 -> v14 fresh-install variant
2
- --
3
- -- Used by bootstrap_database.sh when the live DB was created directly from
4
- -- schema.sql at v14 state (i.e. schema.sql already declares the path column
5
- -- on the projects table).
6
- --
7
- -- On a fresh install:
8
- -- - schema.sql creates projects with all columns including path -> no DDL needed
9
- --
10
- -- This variant is a no-op; it only exists so the bootstrap guard-probe branch
11
- -- can select it and stamp the ledger without applying DDL.
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 v14 state (schema.sql created all objects).
17
- SELECT 1;
@@ -1,71 +0,0 @@
1
- -- Migration v14 -> v15 (child-table FK column rename: repo -> project)
2
- --
3
- -- Background
4
- -- ----------
5
- -- Commit be9698f ("feat(substrate): rename projects->workspaces, repos->projects")
6
- -- renamed the substrate hierarchy. The OLD `repos` concept (git-bearing units)
7
- -- became `projects`, and the OLD `projects` container became `workspaces`.
8
- --
9
- -- schema.sql and the writer/populator code were updated to use the `project`
10
- -- column on the nine per-project child tables:
11
- -- apps, libraries, services, features, tf_modules, tf_live, releases,
12
- -- workloads, clusters_defined
13
- -- ...but NO migration was ever shipped to rename the live column. Because every
14
- -- child table is declared with `CREATE TABLE IF NOT EXISTS`, schema.sql silently
15
- -- no-ops on a DB that already has these tables, so existing installations (at
16
- -- v14) still carry the legacy column name `repo`. The writer/populator code,
17
- -- which already emits `project` in every INSERT / SELECT / ON CONFLICT /
18
- -- delete_missing path, then fails at runtime with "no such column: project" the
19
- -- first time `gaia scan` populates infra/app rows.
20
- --
21
- -- This was latent because scan_workspace_to_store had never run end-to-end via
22
- -- the CLI (it was only wired in T3.1), and the unit tests mock the writer or
23
- -- use fixtures with no infra/app content, so the column mismatch never executed.
24
- --
25
- -- Fix direction
26
- -- -------------
27
- -- `project` is the canonical name: it is what schema.sql declares, what every
28
- -- writer/populator SQL path emits, and what the writer's PK maps in
29
- -- delete_missing_in use. The live DB is the sole outlier. The lowest-blast-
30
- -- radius fix is therefore a forward migration that renames the legacy `repo`
31
- -- column to `project` on each of the nine child tables. No code change is
32
- -- required.
33
- --
34
- -- Mechanism
35
- -- ---------
36
- -- SQLite >= 3.25 supports `ALTER TABLE ... RENAME COLUMN`, which rewrites the
37
- -- stored DDL in place, automatically updating the column's appearance in the
38
- -- table's PRIMARY KEY and in its own FOREIGN KEY clause. The FTS5 mirror tables
39
- -- (apps_fts, services_fts) index text content columns (name, etc.), NOT the FK
40
- -- column, so they are unaffected. Verified on sqlite 3.45.x: PRAGMA
41
- -- foreign_key_check returns no rows after the rename and project-keyed INSERTs
42
- -- succeed.
43
- --
44
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT. A failure
45
- -- mid-flight rolls back to v14 state; the ledger row is NOT inserted, so the
46
- -- next bootstrap retry sees the same pending migration.
47
-
48
- -- ---------------------------------------------------------------------------
49
- -- Step 1: Rename repo -> project on each per-project child table
50
- -- ---------------------------------------------------------------------------
51
- ALTER TABLE apps RENAME COLUMN repo TO project;
52
- ALTER TABLE libraries RENAME COLUMN repo TO project;
53
- ALTER TABLE services RENAME COLUMN repo TO project;
54
- ALTER TABLE features RENAME COLUMN repo TO project;
55
- ALTER TABLE tf_modules RENAME COLUMN repo TO project;
56
- ALTER TABLE tf_live RENAME COLUMN repo TO project;
57
- ALTER TABLE releases RENAME COLUMN repo TO project;
58
- ALTER TABLE workloads RENAME COLUMN repo TO project;
59
- ALTER TABLE clusters_defined RENAME COLUMN repo TO project;
60
-
61
- -- ---------------------------------------------------------------------------
62
- -- Step 2: Bump schema_version to 15
63
- -- ---------------------------------------------------------------------------
64
- INSERT OR IGNORE INTO schema_version (version, applied_at, description)
65
- VALUES (15, strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),
66
- 'rename child-table FK column repo -> project (substrate rename catch-up)');
67
-
68
- -- Verification queries (run after applying):
69
- -- SELECT MAX(version) FROM schema_version; -- expect: 15
70
- -- PRAGMA table_info(apps); -- expect: project (not repo)
71
- -- PRAGMA foreign_key_check; -- expect: no rows
@@ -1,19 +0,0 @@
1
- -- Migration v14 -> v15 fresh-install variant
2
- --
3
- -- Used by bootstrap_database.sh when the live DB was created directly from
4
- -- schema.sql at v15 state -- i.e. the per-project child tables (apps, services,
5
- -- tf_modules, ...) already carry the `project` column because schema.sql
6
- -- declared them that way.
7
- --
8
- -- On a fresh install there is no legacy `repo` column to rename, so the
9
- -- RENAME COLUMN statements in v14_to_v15.sql would fail with "no such column:
10
- -- repo". This variant is a no-op; it exists only so the bootstrap guard-probe
11
- -- branch (Section 3c, case 15) can select it and stamp the ledger without
12
- -- attempting the rename.
13
- --
14
- -- Atomicity: bootstrap_database.sh wraps this script in BEGIN/COMMIT.
15
- -- No DDL is executed; the COMMIT is harmless.
16
-
17
- -- No-op: fresh install already at v15 state (schema.sql created child tables
18
- -- with the `project` column).
19
- SELECT 1;