@plazmodium/odin 0.3.2-beta

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 (219) hide show
  1. package/README.md +306 -0
  2. package/dist/adapters/archive/supabase.d.ts +19 -0
  3. package/dist/adapters/archive/supabase.d.ts.map +1 -0
  4. package/dist/adapters/archive/supabase.js +121 -0
  5. package/dist/adapters/archive/supabase.js.map +1 -0
  6. package/dist/adapters/archive/types.d.ts +26 -0
  7. package/dist/adapters/archive/types.d.ts.map +1 -0
  8. package/dist/adapters/archive/types.js +6 -0
  9. package/dist/adapters/archive/types.js.map +1 -0
  10. package/dist/adapters/formal-verification/tla-precheck.d.ts +22 -0
  11. package/dist/adapters/formal-verification/tla-precheck.d.ts.map +1 -0
  12. package/dist/adapters/formal-verification/tla-precheck.js +270 -0
  13. package/dist/adapters/formal-verification/tla-precheck.js.map +1 -0
  14. package/dist/adapters/formal-verification/types.d.ts +37 -0
  15. package/dist/adapters/formal-verification/types.d.ts.map +1 -0
  16. package/dist/adapters/formal-verification/types.js +6 -0
  17. package/dist/adapters/formal-verification/types.js.map +1 -0
  18. package/dist/adapters/review/semgrep.d.ts +12 -0
  19. package/dist/adapters/review/semgrep.d.ts.map +1 -0
  20. package/dist/adapters/review/semgrep.js +175 -0
  21. package/dist/adapters/review/semgrep.js.map +1 -0
  22. package/dist/adapters/review/types.d.ts +14 -0
  23. package/dist/adapters/review/types.d.ts.map +1 -0
  24. package/dist/adapters/review/types.js +6 -0
  25. package/dist/adapters/review/types.js.map +1 -0
  26. package/dist/adapters/skills/filesystem.d.ts +18 -0
  27. package/dist/adapters/skills/filesystem.d.ts.map +1 -0
  28. package/dist/adapters/skills/filesystem.js +398 -0
  29. package/dist/adapters/skills/filesystem.js.map +1 -0
  30. package/dist/adapters/skills/types.d.ts +19 -0
  31. package/dist/adapters/skills/types.d.ts.map +1 -0
  32. package/dist/adapters/skills/types.js +6 -0
  33. package/dist/adapters/skills/types.js.map +1 -0
  34. package/dist/adapters/sql-executor/direct-postgres.d.ts +15 -0
  35. package/dist/adapters/sql-executor/direct-postgres.d.ts.map +1 -0
  36. package/dist/adapters/sql-executor/direct-postgres.js +33 -0
  37. package/dist/adapters/sql-executor/direct-postgres.js.map +1 -0
  38. package/dist/adapters/sql-executor/supabase-management-api.d.ts +17 -0
  39. package/dist/adapters/sql-executor/supabase-management-api.d.ts.map +1 -0
  40. package/dist/adapters/sql-executor/supabase-management-api.js +40 -0
  41. package/dist/adapters/sql-executor/supabase-management-api.js.map +1 -0
  42. package/dist/adapters/sql-executor/types.d.ts +15 -0
  43. package/dist/adapters/sql-executor/types.d.ts.map +1 -0
  44. package/dist/adapters/sql-executor/types.js +6 -0
  45. package/dist/adapters/sql-executor/types.js.map +1 -0
  46. package/dist/adapters/workflow-state/in-memory.d.ts +69 -0
  47. package/dist/adapters/workflow-state/in-memory.d.ts.map +1 -0
  48. package/dist/adapters/workflow-state/in-memory.js +444 -0
  49. package/dist/adapters/workflow-state/in-memory.js.map +1 -0
  50. package/dist/adapters/workflow-state/supabase.d.ts +55 -0
  51. package/dist/adapters/workflow-state/supabase.d.ts.map +1 -0
  52. package/dist/adapters/workflow-state/supabase.js +823 -0
  53. package/dist/adapters/workflow-state/supabase.js.map +1 -0
  54. package/dist/adapters/workflow-state/types.d.ts +55 -0
  55. package/dist/adapters/workflow-state/types.d.ts.map +1 -0
  56. package/dist/adapters/workflow-state/types.js +6 -0
  57. package/dist/adapters/workflow-state/types.js.map +1 -0
  58. package/dist/cli.d.ts +3 -0
  59. package/dist/cli.d.ts.map +1 -0
  60. package/dist/cli.js +52 -0
  61. package/dist/cli.js.map +1 -0
  62. package/dist/config.d.ts +44 -0
  63. package/dist/config.d.ts.map +1 -0
  64. package/dist/config.js +115 -0
  65. package/dist/config.js.map +1 -0
  66. package/dist/domain/actors.d.ts +10 -0
  67. package/dist/domain/actors.d.ts.map +1 -0
  68. package/dist/domain/actors.js +60 -0
  69. package/dist/domain/actors.js.map +1 -0
  70. package/dist/domain/development-evals.d.ts +9 -0
  71. package/dist/domain/development-evals.d.ts.map +1 -0
  72. package/dist/domain/development-evals.js +164 -0
  73. package/dist/domain/development-evals.js.map +1 -0
  74. package/dist/domain/matching.d.ts +8 -0
  75. package/dist/domain/matching.d.ts.map +1 -0
  76. package/dist/domain/matching.js +24 -0
  77. package/dist/domain/matching.js.map +1 -0
  78. package/dist/domain/phases.d.ts +10 -0
  79. package/dist/domain/phases.d.ts.map +1 -0
  80. package/dist/domain/phases.js +165 -0
  81. package/dist/domain/phases.js.map +1 -0
  82. package/dist/domain/quality-gates.d.ts +7 -0
  83. package/dist/domain/quality-gates.d.ts.map +1 -0
  84. package/dist/domain/quality-gates.js +8 -0
  85. package/dist/domain/quality-gates.js.map +1 -0
  86. package/dist/domain/resonance.d.ts +33 -0
  87. package/dist/domain/resonance.d.ts.map +1 -0
  88. package/dist/domain/resonance.js +100 -0
  89. package/dist/domain/resonance.js.map +1 -0
  90. package/dist/domain/tasks.d.ts +9 -0
  91. package/dist/domain/tasks.d.ts.map +1 -0
  92. package/dist/domain/tasks.js +57 -0
  93. package/dist/domain/tasks.js.map +1 -0
  94. package/dist/init.d.ts +7 -0
  95. package/dist/init.d.ts.map +1 -0
  96. package/dist/init.js +387 -0
  97. package/dist/init.js.map +1 -0
  98. package/dist/schemas.d.ts +366 -0
  99. package/dist/schemas.d.ts.map +1 -0
  100. package/dist/schemas.js +184 -0
  101. package/dist/schemas.js.map +1 -0
  102. package/dist/server.d.ts +7 -0
  103. package/dist/server.d.ts.map +1 -0
  104. package/dist/server.js +243 -0
  105. package/dist/server.js.map +1 -0
  106. package/dist/tools/apply-migrations.d.ts +21 -0
  107. package/dist/tools/apply-migrations.d.ts.map +1 -0
  108. package/dist/tools/apply-migrations.js +286 -0
  109. package/dist/tools/apply-migrations.js.map +1 -0
  110. package/dist/tools/archive-feature-release.d.ts +13 -0
  111. package/dist/tools/archive-feature-release.d.ts.map +1 -0
  112. package/dist/tools/archive-feature-release.js +182 -0
  113. package/dist/tools/archive-feature-release.js.map +1 -0
  114. package/dist/tools/capture-learning.d.ts +9 -0
  115. package/dist/tools/capture-learning.d.ts.map +1 -0
  116. package/dist/tools/capture-learning.js +53 -0
  117. package/dist/tools/capture-learning.js.map +1 -0
  118. package/dist/tools/explore-knowledge.d.ts +9 -0
  119. package/dist/tools/explore-knowledge.d.ts.map +1 -0
  120. package/dist/tools/explore-knowledge.js +142 -0
  121. package/dist/tools/explore-knowledge.js.map +1 -0
  122. package/dist/tools/get-claims-needing-review.d.ts +8 -0
  123. package/dist/tools/get-claims-needing-review.d.ts.map +1 -0
  124. package/dist/tools/get-claims-needing-review.js +21 -0
  125. package/dist/tools/get-claims-needing-review.js.map +1 -0
  126. package/dist/tools/get-development-eval-status.d.ts +8 -0
  127. package/dist/tools/get-development-eval-status.d.ts.map +1 -0
  128. package/dist/tools/get-development-eval-status.js +49 -0
  129. package/dist/tools/get-development-eval-status.js.map +1 -0
  130. package/dist/tools/get-feature-status.d.ts +8 -0
  131. package/dist/tools/get-feature-status.d.ts.map +1 -0
  132. package/dist/tools/get-feature-status.js +68 -0
  133. package/dist/tools/get-feature-status.js.map +1 -0
  134. package/dist/tools/get-next-phase.d.ts +8 -0
  135. package/dist/tools/get-next-phase.d.ts.map +1 -0
  136. package/dist/tools/get-next-phase.js +26 -0
  137. package/dist/tools/get-next-phase.js.map +1 -0
  138. package/dist/tools/prepare-phase-context.d.ts +9 -0
  139. package/dist/tools/prepare-phase-context.d.ts.map +1 -0
  140. package/dist/tools/prepare-phase-context.js +151 -0
  141. package/dist/tools/prepare-phase-context.js.map +1 -0
  142. package/dist/tools/record-commit.d.ts +8 -0
  143. package/dist/tools/record-commit.d.ts.map +1 -0
  144. package/dist/tools/record-commit.js +28 -0
  145. package/dist/tools/record-commit.js.map +1 -0
  146. package/dist/tools/record-eval-plan.d.ts +8 -0
  147. package/dist/tools/record-eval-plan.d.ts.map +1 -0
  148. package/dist/tools/record-eval-plan.js +40 -0
  149. package/dist/tools/record-eval-plan.js.map +1 -0
  150. package/dist/tools/record-eval-run.d.ts +8 -0
  151. package/dist/tools/record-eval-run.d.ts.map +1 -0
  152. package/dist/tools/record-eval-run.js +42 -0
  153. package/dist/tools/record-eval-run.js.map +1 -0
  154. package/dist/tools/record-merge.d.ts +8 -0
  155. package/dist/tools/record-merge.d.ts.map +1 -0
  156. package/dist/tools/record-merge.js +16 -0
  157. package/dist/tools/record-merge.js.map +1 -0
  158. package/dist/tools/record-phase-artifact.d.ts +8 -0
  159. package/dist/tools/record-phase-artifact.d.ts.map +1 -0
  160. package/dist/tools/record-phase-artifact.js +26 -0
  161. package/dist/tools/record-phase-artifact.js.map +1 -0
  162. package/dist/tools/record-phase-result.d.ts +9 -0
  163. package/dist/tools/record-phase-result.d.ts.map +1 -0
  164. package/dist/tools/record-phase-result.js +122 -0
  165. package/dist/tools/record-phase-result.js.map +1 -0
  166. package/dist/tools/record-pull-request.d.ts +8 -0
  167. package/dist/tools/record-pull-request.d.ts.map +1 -0
  168. package/dist/tools/record-pull-request.js +16 -0
  169. package/dist/tools/record-pull-request.js.map +1 -0
  170. package/dist/tools/record-quality-gate.d.ts +8 -0
  171. package/dist/tools/record-quality-gate.d.ts.map +1 -0
  172. package/dist/tools/record-quality-gate.js +26 -0
  173. package/dist/tools/record-quality-gate.js.map +1 -0
  174. package/dist/tools/record-watcher-review.d.ts +8 -0
  175. package/dist/tools/record-watcher-review.d.ts.map +1 -0
  176. package/dist/tools/record-watcher-review.js +18 -0
  177. package/dist/tools/record-watcher-review.js.map +1 -0
  178. package/dist/tools/run-policy-checks.d.ts +8 -0
  179. package/dist/tools/run-policy-checks.d.ts.map +1 -0
  180. package/dist/tools/run-policy-checks.js +38 -0
  181. package/dist/tools/run-policy-checks.js.map +1 -0
  182. package/dist/tools/run-review-checks.d.ts +9 -0
  183. package/dist/tools/run-review-checks.d.ts.map +1 -0
  184. package/dist/tools/run-review-checks.js +45 -0
  185. package/dist/tools/run-review-checks.js.map +1 -0
  186. package/dist/tools/start-feature.d.ts +8 -0
  187. package/dist/tools/start-feature.d.ts.map +1 -0
  188. package/dist/tools/start-feature.js +33 -0
  189. package/dist/tools/start-feature.js.map +1 -0
  190. package/dist/tools/submit-claim.d.ts +8 -0
  191. package/dist/tools/submit-claim.d.ts.map +1 -0
  192. package/dist/tools/submit-claim.js +45 -0
  193. package/dist/tools/submit-claim.js.map +1 -0
  194. package/dist/tools/verify-claims.d.ts +8 -0
  195. package/dist/tools/verify-claims.d.ts.map +1 -0
  196. package/dist/tools/verify-claims.js +39 -0
  197. package/dist/tools/verify-claims.js.map +1 -0
  198. package/dist/tools/verify-design.d.ts +8 -0
  199. package/dist/tools/verify-design.d.ts.map +1 -0
  200. package/dist/tools/verify-design.js +31 -0
  201. package/dist/tools/verify-design.js.map +1 -0
  202. package/dist/types.d.ts +333 -0
  203. package/dist/types.d.ts.map +1 -0
  204. package/dist/types.js +52 -0
  205. package/dist/types.js.map +1 -0
  206. package/dist/utils.d.ts +24 -0
  207. package/dist/utils.d.ts.map +1 -0
  208. package/dist/utils.js +50 -0
  209. package/dist/utils.js.map +1 -0
  210. package/migrations/001_schema.sql +795 -0
  211. package/migrations/002_functions.sql +2126 -0
  212. package/migrations/003_views.sql +599 -0
  213. package/migrations/004_seed.sql +106 -0
  214. package/migrations/005_odin_v2_schema.sql +217 -0
  215. package/migrations/006_odin_v2_functions.sql +671 -0
  216. package/migrations/007_odin_v2_phase_alignment.sql +554 -0
  217. package/migrations/008_related_learnings.sql +80 -0
  218. package/migrations/README.md +23 -0
  219. package/package.json +63 -0
@@ -0,0 +1,671 @@
1
+ -- ============================================================================
2
+ -- Odin v2 Functions
3
+ -- Version: 2.0.0
4
+ -- Created: 2026-03-05
5
+ -- Description: Functions for Odin v2 features:
6
+ -- - Agent claims submission and verification
7
+ -- - Policy engine (deterministic checks)
8
+ -- - Watcher reviews (LLM escalation)
9
+ -- - Security findings management
10
+ --
11
+ -- Dependencies: Requires 005_odin_v2_schema.sql to be run first
12
+ --
13
+ -- IMPORTANT: DO NOT RUN ON SUPABASE UNTIL READY FOR DEPLOYMENT
14
+ -- ============================================================================
15
+
16
+ -- ============================================================================
17
+ -- AGENT CLAIMS FUNCTIONS
18
+ -- ============================================================================
19
+
20
+ -- Submit a claim from an agent
21
+ -- Used by Builder, Integrator, Release agents to assert work performed
22
+ CREATE OR REPLACE FUNCTION submit_claim(
23
+ p_feature_id TEXT,
24
+ p_phase phase,
25
+ p_agent_name TEXT,
26
+ p_claim_type claim_type,
27
+ p_description TEXT,
28
+ p_evidence_refs JSONB DEFAULT '{}'::jsonb,
29
+ p_risk_level TEXT DEFAULT 'LOW',
30
+ p_invocation_id UUID DEFAULT NULL
31
+ )
32
+ RETURNS TABLE (
33
+ claim_id UUID,
34
+ feature_id TEXT,
35
+ phase phase,
36
+ claim_type claim_type,
37
+ risk_level TEXT,
38
+ created_at TIMESTAMPTZ
39
+ )
40
+ LANGUAGE plpgsql
41
+ SET search_path = public
42
+ AS $$
43
+ DECLARE
44
+ v_claim_id UUID;
45
+ v_created_at TIMESTAMPTZ;
46
+ BEGIN
47
+ -- Validate risk level
48
+ IF p_risk_level NOT IN ('LOW', 'MEDIUM', 'HIGH') THEN
49
+ RAISE EXCEPTION 'Invalid risk_level: %. Must be LOW, MEDIUM, or HIGH.', p_risk_level;
50
+ END IF;
51
+
52
+ -- Insert claim
53
+ INSERT INTO agent_claims (
54
+ feature_id, phase, agent_name, claim_type,
55
+ claim_description, evidence_refs, risk_level, invocation_id
56
+ ) VALUES (
57
+ p_feature_id, p_phase, p_agent_name, p_claim_type,
58
+ p_description, p_evidence_refs, p_risk_level, p_invocation_id
59
+ )
60
+ RETURNING id, agent_claims.created_at INTO v_claim_id, v_created_at;
61
+
62
+ -- Log to audit
63
+ INSERT INTO audit_log (feature_id, operation, agent_name, details)
64
+ VALUES (p_feature_id, 'CLAIM_SUBMITTED', p_agent_name, jsonb_build_object(
65
+ 'table_name', 'agent_claims',
66
+ 'record_id', v_claim_id::text,
67
+ 'phase', p_phase,
68
+ 'claim_type', p_claim_type,
69
+ 'risk_level', p_risk_level
70
+ ));
71
+
72
+ RETURN QUERY SELECT v_claim_id, p_feature_id, p_phase, p_claim_type, p_risk_level, v_created_at;
73
+ END;
74
+ $$;
75
+
76
+ COMMENT ON FUNCTION submit_claim IS 'Submit a structured claim from an agent. Returns the created claim record.';
77
+
78
+ -- Get all claims for a feature
79
+ CREATE OR REPLACE FUNCTION get_feature_claims(p_feature_id TEXT)
80
+ RETURNS TABLE (
81
+ claim_id UUID,
82
+ phase phase,
83
+ agent_name TEXT,
84
+ claim_type claim_type,
85
+ claim_description TEXT,
86
+ evidence_refs JSONB,
87
+ risk_level TEXT,
88
+ policy_verdict verification_status,
89
+ watcher_verdict verification_status,
90
+ final_status TEXT,
91
+ created_at TIMESTAMPTZ
92
+ )
93
+ LANGUAGE plpgsql
94
+ SET search_path = public
95
+ AS $$
96
+ BEGIN
97
+ RETURN QUERY
98
+ SELECT
99
+ ac.id AS claim_id,
100
+ ac.phase,
101
+ ac.agent_name,
102
+ ac.claim_type,
103
+ ac.claim_description,
104
+ ac.evidence_refs,
105
+ ac.risk_level,
106
+ pv.verdict AS policy_verdict,
107
+ wr.verdict AS watcher_verdict,
108
+ CASE
109
+ WHEN wr.verdict IS NOT NULL THEN wr.verdict::text
110
+ WHEN pv.verdict IS NOT NULL THEN pv.verdict::text
111
+ ELSE 'PENDING'
112
+ END AS final_status,
113
+ ac.created_at
114
+ FROM agent_claims ac
115
+ LEFT JOIN policy_verdicts pv ON ac.id = pv.claim_id
116
+ LEFT JOIN watcher_reviews wr ON ac.id = wr.claim_id
117
+ WHERE ac.feature_id = p_feature_id
118
+ ORDER BY ac.created_at;
119
+ END;
120
+ $$;
121
+
122
+ COMMENT ON FUNCTION get_feature_claims IS 'Get all claims for a feature with their verification status';
123
+
124
+ -- ============================================================================
125
+ -- POLICY ENGINE FUNCTIONS
126
+ -- ============================================================================
127
+
128
+ -- Record a policy verdict
129
+ CREATE OR REPLACE FUNCTION record_policy_verdict(
130
+ p_claim_id UUID,
131
+ p_verdict verification_status,
132
+ p_rule_name TEXT,
133
+ p_reason TEXT DEFAULT NULL,
134
+ p_evidence_checked JSONB DEFAULT '{}'::jsonb
135
+ )
136
+ RETURNS TABLE (
137
+ verdict_id UUID,
138
+ claim_id UUID,
139
+ verdict verification_status,
140
+ rule_name TEXT
141
+ )
142
+ LANGUAGE plpgsql
143
+ SET search_path = public
144
+ AS $$
145
+ DECLARE
146
+ v_verdict_id UUID;
147
+ v_feature_id TEXT;
148
+ v_agent_name TEXT;
149
+ BEGIN
150
+ -- Get claim info for audit
151
+ SELECT ac.feature_id, ac.agent_name INTO v_feature_id, v_agent_name
152
+ FROM agent_claims ac WHERE ac.id = p_claim_id;
153
+
154
+ IF v_feature_id IS NULL THEN
155
+ RAISE EXCEPTION 'Claim not found: %', p_claim_id;
156
+ END IF;
157
+
158
+ -- Insert verdict
159
+ INSERT INTO policy_verdicts (
160
+ claim_id, verdict, rule_name, reason, evidence_checked
161
+ ) VALUES (
162
+ p_claim_id, p_verdict, p_rule_name, p_reason, p_evidence_checked
163
+ )
164
+ RETURNING id INTO v_verdict_id;
165
+
166
+ -- Log to audit
167
+ INSERT INTO audit_log (feature_id, operation, agent_name, details)
168
+ VALUES (v_feature_id, 'POLICY_VERDICT', 'policy-engine', jsonb_build_object(
169
+ 'table_name', 'policy_verdicts',
170
+ 'record_id', v_verdict_id::text,
171
+ 'claim_id', p_claim_id,
172
+ 'verdict', p_verdict,
173
+ 'rule_name', p_rule_name
174
+ ));
175
+
176
+ RETURN QUERY SELECT v_verdict_id, p_claim_id, p_verdict, p_rule_name;
177
+ END;
178
+ $$;
179
+
180
+ COMMENT ON FUNCTION record_policy_verdict IS 'Record the result of a deterministic policy check on a claim';
181
+
182
+ -- Policy engine: verify claim has evidence
183
+ -- Returns PASS if evidence present and not high-risk
184
+ -- Returns NEEDS_REVIEW if evidence missing or high-risk
185
+ CREATE OR REPLACE FUNCTION policy_verify_evidence(p_claim_id UUID)
186
+ RETURNS verification_status
187
+ LANGUAGE plpgsql
188
+ SET search_path = public
189
+ AS $$
190
+ DECLARE
191
+ v_claim agent_claims%ROWTYPE;
192
+ v_verdict verification_status;
193
+ v_reason TEXT;
194
+ BEGIN
195
+ SELECT * INTO v_claim FROM agent_claims WHERE id = p_claim_id;
196
+
197
+ IF v_claim IS NULL THEN
198
+ RAISE EXCEPTION 'Claim not found: %', p_claim_id;
199
+ END IF;
200
+
201
+ -- Determine verdict
202
+ IF v_claim.evidence_refs IS NULL OR v_claim.evidence_refs = '{}'::jsonb THEN
203
+ v_verdict := 'NEEDS_REVIEW';
204
+ v_reason := 'Missing evidence references - escalate to watcher';
205
+ ELSIF v_claim.risk_level = 'HIGH' THEN
206
+ v_verdict := 'NEEDS_REVIEW';
207
+ v_reason := 'High risk claim - requires watcher review';
208
+ ELSE
209
+ v_verdict := 'PASS';
210
+ v_reason := 'Evidence references present';
211
+ END IF;
212
+
213
+ -- Record the verdict
214
+ PERFORM record_policy_verdict(
215
+ p_claim_id,
216
+ v_verdict,
217
+ 'evidence_check',
218
+ v_reason,
219
+ v_claim.evidence_refs
220
+ );
221
+
222
+ RETURN v_verdict;
223
+ END;
224
+ $$;
225
+
226
+ COMMENT ON FUNCTION policy_verify_evidence IS 'Deterministic check: verify claim has evidence refs. Escalates to watcher if missing or high-risk.';
227
+
228
+ -- Run all policy checks on pending claims for a feature
229
+ CREATE OR REPLACE FUNCTION run_policy_checks(p_feature_id TEXT)
230
+ RETURNS TABLE (
231
+ claim_id UUID,
232
+ claim_type claim_type,
233
+ verdict verification_status,
234
+ needs_watcher BOOLEAN
235
+ )
236
+ LANGUAGE plpgsql
237
+ SET search_path = public
238
+ AS $$
239
+ DECLARE
240
+ v_claim RECORD;
241
+ v_verdict verification_status;
242
+ BEGIN
243
+ FOR v_claim IN
244
+ SELECT ac.id, ac.claim_type
245
+ FROM agent_claims ac
246
+ LEFT JOIN policy_verdicts pv ON ac.id = pv.claim_id
247
+ WHERE ac.feature_id = p_feature_id
248
+ AND pv.id IS NULL -- No verdict yet
249
+ ORDER BY ac.created_at
250
+ LOOP
251
+ -- Run evidence check
252
+ v_verdict := policy_verify_evidence(v_claim.id);
253
+
254
+ claim_id := v_claim.id;
255
+ claim_type := v_claim.claim_type;
256
+ verdict := v_verdict;
257
+ needs_watcher := (v_verdict = 'NEEDS_REVIEW');
258
+ RETURN NEXT;
259
+ END LOOP;
260
+ END;
261
+ $$;
262
+
263
+ COMMENT ON FUNCTION run_policy_checks IS 'Run all pending policy checks for a feature. Returns claims needing watcher review.';
264
+
265
+ -- ============================================================================
266
+ -- WATCHER FUNCTIONS
267
+ -- ============================================================================
268
+
269
+ -- Record a watcher review result
270
+ CREATE OR REPLACE FUNCTION record_watcher_review(
271
+ p_claim_id UUID,
272
+ p_verdict verification_status,
273
+ p_reasoning TEXT,
274
+ p_watcher_agent TEXT,
275
+ p_confidence DECIMAL DEFAULT 0.80
276
+ )
277
+ RETURNS TABLE (
278
+ review_id UUID,
279
+ claim_id UUID,
280
+ verdict verification_status,
281
+ confidence DECIMAL
282
+ )
283
+ LANGUAGE plpgsql
284
+ SET search_path = public
285
+ AS $$
286
+ DECLARE
287
+ v_review_id UUID;
288
+ v_feature_id TEXT;
289
+ BEGIN
290
+ -- Get claim info for audit
291
+ SELECT ac.feature_id INTO v_feature_id
292
+ FROM agent_claims ac WHERE ac.id = p_claim_id;
293
+
294
+ IF v_feature_id IS NULL THEN
295
+ RAISE EXCEPTION 'Claim not found: %', p_claim_id;
296
+ END IF;
297
+
298
+ -- Insert review
299
+ INSERT INTO watcher_reviews (
300
+ claim_id, verdict, confidence, reasoning, watcher_agent
301
+ ) VALUES (
302
+ p_claim_id, p_verdict, p_confidence, p_reasoning, p_watcher_agent
303
+ )
304
+ RETURNING id INTO v_review_id;
305
+
306
+ -- Log to audit
307
+ INSERT INTO audit_log (feature_id, operation, agent_name, details)
308
+ VALUES (v_feature_id, 'WATCHER_REVIEW', p_watcher_agent, jsonb_build_object(
309
+ 'table_name', 'watcher_reviews',
310
+ 'record_id', v_review_id::text,
311
+ 'claim_id', p_claim_id,
312
+ 'verdict', p_verdict,
313
+ 'confidence', p_confidence
314
+ ));
315
+
316
+ RETURN QUERY SELECT v_review_id, p_claim_id, p_verdict, p_confidence;
317
+ END;
318
+ $$;
319
+
320
+ COMMENT ON FUNCTION record_watcher_review IS 'Record the result of an LLM watcher review on an escalated claim';
321
+
322
+ -- Get claims needing watcher review
323
+ CREATE OR REPLACE FUNCTION get_claims_needing_review(p_feature_id TEXT DEFAULT NULL)
324
+ RETURNS TABLE (
325
+ claim_id UUID,
326
+ feature_id TEXT,
327
+ phase phase,
328
+ agent_name TEXT,
329
+ claim_type claim_type,
330
+ claim_description TEXT,
331
+ evidence_refs JSONB,
332
+ risk_level TEXT,
333
+ policy_verdict verification_status,
334
+ policy_reason TEXT,
335
+ created_at TIMESTAMPTZ
336
+ )
337
+ LANGUAGE plpgsql
338
+ SET search_path = public
339
+ AS $$
340
+ BEGIN
341
+ RETURN QUERY
342
+ SELECT
343
+ ac.id AS claim_id,
344
+ ac.feature_id,
345
+ ac.phase,
346
+ ac.agent_name,
347
+ ac.claim_type,
348
+ ac.claim_description,
349
+ ac.evidence_refs,
350
+ ac.risk_level,
351
+ pv.verdict AS policy_verdict,
352
+ pv.reason AS policy_reason,
353
+ ac.created_at
354
+ FROM agent_claims ac
355
+ LEFT JOIN policy_verdicts pv ON ac.id = pv.claim_id
356
+ LEFT JOIN watcher_reviews wr ON ac.id = wr.claim_id
357
+ WHERE (pv.verdict = 'NEEDS_REVIEW' OR ac.risk_level = 'HIGH')
358
+ AND wr.id IS NULL -- No watcher review yet
359
+ AND (p_feature_id IS NULL OR ac.feature_id = p_feature_id)
360
+ ORDER BY
361
+ CASE WHEN ac.risk_level = 'HIGH' THEN 0 ELSE 1 END,
362
+ ac.created_at;
363
+ END;
364
+ $$;
365
+
366
+ COMMENT ON FUNCTION get_claims_needing_review IS 'Get claims that need LLM watcher review (escalated or high-risk)';
367
+
368
+ -- Get verification summary for a feature
369
+ CREATE OR REPLACE FUNCTION get_verification_summary(p_feature_id TEXT)
370
+ RETURNS TABLE (
371
+ total_claims INTEGER,
372
+ policy_pass INTEGER,
373
+ policy_fail INTEGER,
374
+ policy_needs_review INTEGER,
375
+ watcher_pass INTEGER,
376
+ watcher_fail INTEGER,
377
+ pending_review INTEGER,
378
+ all_verified BOOLEAN
379
+ )
380
+ LANGUAGE plpgsql
381
+ SET search_path = public
382
+ AS $$
383
+ BEGIN
384
+ RETURN QUERY
385
+ WITH claim_stats AS (
386
+ SELECT
387
+ COUNT(*) AS total,
388
+ COUNT(*) FILTER (WHERE pv.verdict = 'PASS') AS p_pass,
389
+ COUNT(*) FILTER (WHERE pv.verdict = 'FAIL') AS p_fail,
390
+ COUNT(*) FILTER (WHERE pv.verdict = 'NEEDS_REVIEW') AS p_review,
391
+ COUNT(*) FILTER (WHERE wr.verdict = 'PASS') AS w_pass,
392
+ COUNT(*) FILTER (WHERE wr.verdict = 'FAIL') AS w_fail,
393
+ COUNT(*) FILTER (WHERE pv.verdict = 'NEEDS_REVIEW' AND wr.id IS NULL) AS pending
394
+ FROM agent_claims ac
395
+ LEFT JOIN policy_verdicts pv ON ac.id = pv.claim_id
396
+ LEFT JOIN watcher_reviews wr ON ac.id = wr.claim_id
397
+ WHERE ac.feature_id = p_feature_id
398
+ )
399
+ SELECT
400
+ cs.total::INTEGER,
401
+ cs.p_pass::INTEGER,
402
+ cs.p_fail::INTEGER,
403
+ cs.p_review::INTEGER,
404
+ cs.w_pass::INTEGER,
405
+ cs.w_fail::INTEGER,
406
+ cs.pending::INTEGER,
407
+ (cs.p_fail = 0 AND cs.w_fail = 0 AND cs.pending = 0)
408
+ FROM claim_stats cs;
409
+ END;
410
+ $$;
411
+
412
+ COMMENT ON FUNCTION get_verification_summary IS 'Get summary of claim verification status for a feature';
413
+
414
+ -- ============================================================================
415
+ -- SECURITY FINDINGS FUNCTIONS
416
+ -- ============================================================================
417
+
418
+ -- Record a security finding from SAST tool
419
+ CREATE OR REPLACE FUNCTION record_security_finding(
420
+ p_feature_id TEXT,
421
+ p_tool TEXT,
422
+ p_severity finding_severity,
423
+ p_message TEXT,
424
+ p_file_path TEXT DEFAULT NULL,
425
+ p_line_number INTEGER DEFAULT NULL,
426
+ p_rule_id TEXT DEFAULT NULL,
427
+ p_snippet TEXT DEFAULT NULL,
428
+ p_fix_suggestion TEXT DEFAULT NULL,
429
+ p_column_number INTEGER DEFAULT NULL,
430
+ p_end_line INTEGER DEFAULT NULL,
431
+ p_end_column INTEGER DEFAULT NULL
432
+ )
433
+ RETURNS TABLE (
434
+ finding_id UUID,
435
+ feature_id TEXT,
436
+ severity finding_severity,
437
+ is_blocking BOOLEAN
438
+ )
439
+ LANGUAGE plpgsql
440
+ SET search_path = public
441
+ AS $$
442
+ DECLARE
443
+ v_finding_id UUID;
444
+ BEGIN
445
+ INSERT INTO security_findings (
446
+ feature_id, tool, severity, message, file_path, line_number,
447
+ rule_id, snippet, fix_suggestion, column_number, end_line, end_column
448
+ ) VALUES (
449
+ p_feature_id, p_tool, p_severity, p_message, p_file_path, p_line_number,
450
+ p_rule_id, p_snippet, p_fix_suggestion, p_column_number, p_end_line, p_end_column
451
+ )
452
+ RETURNING id INTO v_finding_id;
453
+
454
+ -- Log to audit
455
+ INSERT INTO audit_log (feature_id, operation, agent_name, details)
456
+ VALUES (p_feature_id, 'SECURITY_FINDING', 'reviewer-agent', jsonb_build_object(
457
+ 'table_name', 'security_findings',
458
+ 'record_id', v_finding_id::text,
459
+ 'tool', p_tool,
460
+ 'severity', p_severity,
461
+ 'rule_id', p_rule_id,
462
+ 'file_path', p_file_path
463
+ ));
464
+
465
+ RETURN QUERY SELECT
466
+ v_finding_id,
467
+ p_feature_id,
468
+ p_severity,
469
+ (p_severity IN ('HIGH', 'CRITICAL'));
470
+ END;
471
+ $$;
472
+
473
+ COMMENT ON FUNCTION record_security_finding IS 'Record a security finding from a SAST tool. Returns whether the finding blocks progression.';
474
+
475
+ -- Resolve a security finding
476
+ CREATE OR REPLACE FUNCTION resolve_security_finding(
477
+ p_finding_id UUID,
478
+ p_resolved_by TEXT,
479
+ p_resolution_note TEXT DEFAULT NULL
480
+ )
481
+ RETURNS TABLE (
482
+ finding_id UUID,
483
+ resolved BOOLEAN,
484
+ resolved_at TIMESTAMPTZ
485
+ )
486
+ LANGUAGE plpgsql
487
+ SET search_path = public
488
+ AS $$
489
+ DECLARE
490
+ v_feature_id TEXT;
491
+ v_resolved_at TIMESTAMPTZ;
492
+ BEGIN
493
+ -- Get feature_id for audit
494
+ SELECT sf.feature_id INTO v_feature_id
495
+ FROM security_findings sf WHERE sf.id = p_finding_id;
496
+
497
+ IF v_feature_id IS NULL THEN
498
+ RAISE EXCEPTION 'Finding not found: %', p_finding_id;
499
+ END IF;
500
+
501
+ -- Update finding
502
+ UPDATE security_findings sf
503
+ SET resolved = true,
504
+ resolved_by = p_resolved_by,
505
+ resolved_at = now(),
506
+ resolution_note = p_resolution_note
507
+ WHERE sf.id = p_finding_id
508
+ RETURNING sf.resolved_at INTO v_resolved_at;
509
+
510
+ -- Log to audit
511
+ INSERT INTO audit_log (feature_id, operation, agent_name, details)
512
+ VALUES (v_feature_id, 'FINDING_RESOLVED', p_resolved_by, jsonb_build_object(
513
+ 'table_name', 'security_findings',
514
+ 'record_id', p_finding_id::text,
515
+ 'resolution_note', p_resolution_note
516
+ ));
517
+
518
+ RETURN QUERY SELECT p_finding_id, true, v_resolved_at;
519
+ END;
520
+ $$;
521
+
522
+ COMMENT ON FUNCTION resolve_security_finding IS 'Mark a security finding as resolved with optional note';
523
+
524
+ -- Get unresolved security findings for a feature
525
+ CREATE OR REPLACE FUNCTION get_unresolved_findings(p_feature_id TEXT)
526
+ RETURNS TABLE (
527
+ id UUID,
528
+ tool TEXT,
529
+ severity finding_severity,
530
+ rule_id TEXT,
531
+ file_path TEXT,
532
+ line_number INTEGER,
533
+ message TEXT,
534
+ snippet TEXT,
535
+ fix_suggestion TEXT,
536
+ is_blocking BOOLEAN,
537
+ created_at TIMESTAMPTZ
538
+ )
539
+ LANGUAGE plpgsql
540
+ SET search_path = public
541
+ AS $$
542
+ BEGIN
543
+ RETURN QUERY
544
+ SELECT
545
+ sf.id, sf.tool, sf.severity, sf.rule_id,
546
+ sf.file_path, sf.line_number, sf.message, sf.snippet, sf.fix_suggestion,
547
+ (sf.severity IN ('HIGH', 'CRITICAL')) AS is_blocking,
548
+ sf.created_at
549
+ FROM security_findings sf
550
+ WHERE sf.feature_id = p_feature_id
551
+ AND sf.resolved = false
552
+ ORDER BY
553
+ CASE sf.severity
554
+ WHEN 'CRITICAL' THEN 0
555
+ WHEN 'HIGH' THEN 1
556
+ WHEN 'MEDIUM' THEN 2
557
+ WHEN 'LOW' THEN 3
558
+ ELSE 4
559
+ END,
560
+ sf.created_at;
561
+ END;
562
+ $$;
563
+
564
+ COMMENT ON FUNCTION get_unresolved_findings IS 'Get all unresolved security findings for a feature, ordered by severity';
565
+
566
+ -- Check if feature can proceed past Reviewer phase
567
+ CREATE OR REPLACE FUNCTION can_proceed_past_reviewer(p_feature_id TEXT)
568
+ RETURNS TABLE (
569
+ can_proceed BOOLEAN,
570
+ blocking_count INTEGER,
571
+ total_findings INTEGER,
572
+ reason TEXT
573
+ )
574
+ LANGUAGE plpgsql
575
+ SET search_path = public
576
+ AS $$
577
+ DECLARE
578
+ v_blocking_count INTEGER;
579
+ v_total_count INTEGER;
580
+ BEGIN
581
+ SELECT
582
+ COUNT(*) FILTER (WHERE severity IN ('HIGH', 'CRITICAL') AND resolved = false),
583
+ COUNT(*) FILTER (WHERE resolved = false)
584
+ INTO v_blocking_count, v_total_count
585
+ FROM security_findings
586
+ WHERE feature_id = p_feature_id;
587
+
588
+ RETURN QUERY
589
+ SELECT
590
+ v_blocking_count = 0,
591
+ v_blocking_count,
592
+ v_total_count,
593
+ CASE
594
+ WHEN v_blocking_count = 0 AND v_total_count = 0 THEN 'No security findings'
595
+ WHEN v_blocking_count = 0 THEN v_total_count || ' non-blocking findings (can proceed)'
596
+ ELSE v_blocking_count || ' unresolved HIGH/CRITICAL findings (must resolve before proceeding)'
597
+ END;
598
+ END;
599
+ $$;
600
+
601
+ COMMENT ON FUNCTION can_proceed_past_reviewer IS 'Check if a feature can proceed past Reviewer phase (no unresolved HIGH/CRITICAL findings)';
602
+
603
+ -- Get security findings summary for a feature
604
+ CREATE OR REPLACE FUNCTION get_security_summary(p_feature_id TEXT)
605
+ RETURNS TABLE (
606
+ total_findings INTEGER,
607
+ critical_count INTEGER,
608
+ high_count INTEGER,
609
+ medium_count INTEGER,
610
+ low_count INTEGER,
611
+ info_count INTEGER,
612
+ resolved_count INTEGER,
613
+ unresolved_blocking INTEGER,
614
+ can_proceed BOOLEAN
615
+ )
616
+ LANGUAGE plpgsql
617
+ SET search_path = public
618
+ AS $$
619
+ BEGIN
620
+ RETURN QUERY
621
+ SELECT
622
+ COUNT(*)::INTEGER AS total_findings,
623
+ COUNT(*) FILTER (WHERE severity = 'CRITICAL')::INTEGER AS critical_count,
624
+ COUNT(*) FILTER (WHERE severity = 'HIGH')::INTEGER AS high_count,
625
+ COUNT(*) FILTER (WHERE severity = 'MEDIUM')::INTEGER AS medium_count,
626
+ COUNT(*) FILTER (WHERE severity = 'LOW')::INTEGER AS low_count,
627
+ COUNT(*) FILTER (WHERE severity = 'INFO')::INTEGER AS info_count,
628
+ COUNT(*) FILTER (WHERE resolved = true)::INTEGER AS resolved_count,
629
+ COUNT(*) FILTER (WHERE resolved = false AND severity IN ('HIGH', 'CRITICAL'))::INTEGER AS unresolved_blocking,
630
+ (COUNT(*) FILTER (WHERE resolved = false AND severity IN ('HIGH', 'CRITICAL')) = 0) AS can_proceed
631
+ FROM security_findings
632
+ WHERE feature_id = p_feature_id;
633
+ END;
634
+ $$;
635
+
636
+ COMMENT ON FUNCTION get_security_summary IS 'Get summary of security findings for a feature by severity';
637
+
638
+ -- ============================================================================
639
+ -- PHASE NAME HELPER (UPDATED FOR V2)
640
+ -- ============================================================================
641
+
642
+ -- Get phase name for v2 workflow
643
+ CREATE OR REPLACE FUNCTION get_phase_name_v2(p_phase phase)
644
+ RETURNS TEXT
645
+ LANGUAGE plpgsql
646
+ IMMUTABLE
647
+ SET search_path = public
648
+ AS $$
649
+ BEGIN
650
+ RETURN CASE p_phase
651
+ WHEN '0' THEN 'Planning'
652
+ WHEN '1' THEN 'Product'
653
+ WHEN '2' THEN 'Discovery'
654
+ WHEN '3' THEN 'Architect'
655
+ WHEN '4' THEN 'Guardian'
656
+ WHEN '5' THEN 'Builder'
657
+ WHEN '6' THEN 'Reviewer'
658
+ WHEN '7' THEN 'Integrator'
659
+ WHEN '8' THEN 'Documenter'
660
+ WHEN '9' THEN 'Release'
661
+ WHEN '10' THEN 'Complete'
662
+ ELSE 'Unknown'
663
+ END;
664
+ END;
665
+ $$;
666
+
667
+ COMMENT ON FUNCTION get_phase_name_v2 IS 'Get human-readable name for v2 workflow phase (11 phases)';
668
+
669
+ -- ============================================================================
670
+ -- END OF FUNCTIONS
671
+ -- ============================================================================