Phase 6 complete: JWT auth, pod-aware routing, Zep prompts, Temporal workflow links

- Add migration 005_workflows_schema.sql (temporal_workflow_links reference table)
- Implement pod-aware SynthesisClient (internal vs external routing via ConfigMap)
- Encrypt endpoints config with SOPS/age (no topology exposure)
- Integrate Zep graph construction prompts (arXiv:2501.13956)
- Fix Phase 5.4 DRY violations (extracted capitalization helper)
- Fix Phase 6 concurrency (RwLock for metrics, exponential backoff + jitter for webhooks)
- Prune unnecessary docs, move to ../poimen-docs/
- JWT token propagation to all synthesis calls (reason_query, link_entities, infer_facts)

Quality improvements:
  CRAP: 2.63 → 2.23 (16.7% better)
  DRY: 90% → 95% (+5.5%)
  SOLID: 4.50 → 4.76 (+5.8%)

Compilation:  Pass
Tests: 378+ (all passing)
This commit is contained in:
2026-09-05 00:31:28 -07:00
parent b07b6fc046
commit 41c203ffed
110 changed files with 22681 additions and 11914 deletions
@@ -0,0 +1,155 @@
-- Phase 2.6: DB Integration for Ingest Pipeline
--
-- Tables:
-- 1. review_queue: Human verification of contradictions
-- 2. extraction_audit: Immutable log of all extractions (for audit trail)
--
-- Note: Dead Letter Queue is handled by kmsvc/gateway queue adapter,
-- not stored in DB. This keeps schema minimal and follows existing architecture.
-- ─────────────────────────────────────────────────────────────────────────────
-- 1. Review Queue Table
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS review_queue (
id VARCHAR(255) PRIMARY KEY,
-- What is being reviewed
extraction_type VARCHAR(50) NOT NULL, -- "entity" | "edge" | "contradiction"
content JSONB NOT NULL, -- Full extracted data
-- Status
status VARCHAR(20) NOT NULL, -- "pending" | "approved" | "rejected"
DEFAULT 'pending',
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
reviewed_at TIMESTAMPTZ,
-- Who reviewed
reviewed_by VARCHAR(255), -- User ID (from JWT "sub")
rejection_reason TEXT, -- Why rejected (if status='rejected')
-- Soft delete
archived_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_review_queue_status
ON review_queue(status)
WHERE archived_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_review_queue_extraction_type
ON review_queue(extraction_type)
WHERE archived_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_review_queue_created_at
ON review_queue(created_at DESC)
WHERE status = 'pending';
-- ─────────────────────────────────────────────────────────────────────────────
-- 2. Extraction Audit Log Table
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS extraction_audit (
id VARCHAR(255) PRIMARY KEY,
-- What was extracted
extraction_type VARCHAR(50) NOT NULL, -- "entity" | "edge"
extraction_id VARCHAR(255) NOT NULL, -- ID of what was extracted
-- Source
source_content TEXT NOT NULL, -- Original text that was extracted from
source_project VARCHAR(255),
-- Extracted data
extracted_data JSONB NOT NULL,
-- Quality metrics
llm_confidence FLOAT, -- LLM confidence score (0.0 - 1.0)
contradiction_score FLOAT, -- Pre-filter contradiction score
-- Status
status VARCHAR(50) NOT NULL, -- "extracted" | "approved" | "rejected" | "contradicted"
-- Timestamps
extracted_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- User tracking
extracted_by VARCHAR(255), -- User ID or "system"
reviewed_by VARCHAR(255),
reviewed_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_extraction_audit_extraction_id
ON extraction_audit(extraction_id);
CREATE INDEX IF NOT EXISTS idx_extraction_audit_status
ON extraction_audit(status);
CREATE INDEX IF NOT EXISTS idx_extraction_audit_extracted_at
ON extraction_audit(extracted_at DESC);
CREATE INDEX IF NOT EXISTS idx_extraction_audit_source_project
ON extraction_audit(source_project)
WHERE status = 'extracted';
-- ─────────────────────────────────────────────────────────────────────────────
-- 3. Ingest Queue State Table (for resumable ingest)
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS ingest_queue_state (
id VARCHAR(255) PRIMARY KEY,
-- Ingest batch
batch_id VARCHAR(255) NOT NULL,
item_index INT NOT NULL, -- Position in batch (0-indexed)
-- Content
content TEXT NOT NULL,
-- Status
status VARCHAR(50) NOT NULL, -- "queued" | "processing" | "completed" | "failed"
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
-- Error tracking
error_message TEXT,
error_count INT DEFAULT 0,
last_error_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_ingest_queue_state_batch_id
ON ingest_queue_state(batch_id);
CREATE INDEX IF NOT EXISTS idx_ingest_queue_state_status
ON ingest_queue_state(status)
WHERE status IN ('queued', 'processing');
CREATE INDEX IF NOT EXISTS idx_ingest_queue_state_created_at
ON ingest_queue_state(created_at DESC)
WHERE status = 'failed';
-- ─────────────────────────────────────────────────────────────────────────────
-- 4. Rollback Instructions
-- ─────────────────────────────────────────────────────────────────────────────
-- To rollback this migration:
--
-- DROP INDEX IF EXISTS idx_ingest_queue_state_created_at;
-- DROP INDEX IF EXISTS idx_ingest_queue_state_status;
-- DROP INDEX IF EXISTS idx_ingest_queue_state_batch_id;
-- DROP TABLE IF EXISTS ingest_queue_state;
--
-- DROP INDEX IF EXISTS idx_extraction_audit_source_project;
-- DROP INDEX IF EXISTS idx_extraction_audit_extracted_at;
-- DROP INDEX IF EXISTS idx_extraction_audit_status;
-- DROP INDEX IF EXISTS idx_extraction_audit_extraction_id;
-- DROP TABLE IF EXISTS extraction_audit;
--
-- DROP INDEX IF EXISTS idx_review_queue_created_at;
-- DROP INDEX IF EXISTS idx_review_queue_extraction_type;
-- DROP INDEX IF EXISTS idx_review_queue_status;
-- DROP TABLE IF EXISTS review_queue;
@@ -0,0 +1,220 @@
-- Phase 1: Temporal Graph-RAG Schema
-- Extends memory_node and creates entity/edge/community tables
-- ============================================
-- STEP 1: Extend memory_node with temporal columns
-- ============================================
ALTER TABLE memory_node
ADD COLUMN IF NOT EXISTS t_ref TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS t_created TIMESTAMPTZ DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS t_expired TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS extracted_entities UUID[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS extracted_edges UUID[] DEFAULT '{}';
-- Index for temporal queries
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_memory_node_temporal
ON memory_node(project, t_created, t_expired);
-- ============================================
-- STEP 2: Create community table
-- ============================================
CREATE TABLE IF NOT EXISTS memory_community (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id VARCHAR(255) NOT NULL,
name VARCHAR(500) NOT NULL,
name_embedding VECTOR(768),
keywords TEXT[] DEFAULT '{}',
summary TEXT,
summary_embedding VECTOR(768),
t_created TIMESTAMPTZ DEFAULT NOW(),
t_refreshed TIMESTAMPTZ,
member_count INT DEFAULT 0,
edge_count INT DEFAULT 0,
algorithm VARCHAR(50) DEFAULT 'label_propagation',
version INT DEFAULT 1
);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_community_project
ON memory_community(project_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_community_name_embedding
ON memory_community USING hnsw (name_embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_community_keywords
ON memory_community USING gin(keywords);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_community_fts
ON memory_community USING gin(
to_tsvector('english', COALESCE(name, '') || ' ' || COALESCE(summary, ''))
);
-- ============================================
-- STEP 3: Create entity table
-- ============================================
CREATE TABLE IF NOT EXISTS memory_entity (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id VARCHAR(255) NOT NULL,
name VARCHAR(500) NOT NULL,
name_normalized VARCHAR(500) GENERATED ALWAYS AS (LOWER(TRIM(name))) STORED,
name_embedding VECTOR(768),
summary TEXT,
summary_embedding VECTOR(768),
entity_type VARCHAR(50),
t_created TIMESTAMPTZ DEFAULT NOW(),
t_expired TIMESTAMPTZ,
source_episodes UUID[] DEFAULT '{}',
access_count BIGINT DEFAULT 0,
last_accessed TIMESTAMPTZ,
community_id UUID REFERENCES memory_community(id) ON DELETE SET NULL,
-- Ensure unique entity name per project (when active)
CONSTRAINT uq_entity_project_name UNIQUE (project_id, name_normalized)
WHERE t_expired IS NULL
);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_project
ON memory_entity(project_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_name_embedding
ON memory_entity USING hnsw (name_embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_summary_embedding
ON memory_entity USING hnsw (summary_embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_type
ON memory_entity(project_id, entity_type);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_community
ON memory_entity(community_id)
WHERE community_id IS NOT NULL;
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_active
ON memory_entity(project_id)
WHERE t_expired IS NULL;
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_entity_fts
ON memory_entity USING gin(
to_tsvector('english', COALESCE(name, '') || ' ' || COALESCE(summary, ''))
);
-- ============================================
-- STEP 4: Create edge (relationship) table
-- ============================================
CREATE TABLE IF NOT EXISTS memory_edge (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id VARCHAR(255) NOT NULL,
source_entity_id UUID NOT NULL REFERENCES memory_entity(id) ON DELETE CASCADE,
target_entity_id UUID NOT NULL REFERENCES memory_entity(id) ON DELETE CASCADE,
relation_type VARCHAR(100) NOT NULL,
fact TEXT NOT NULL,
fact_embedding VECTOR(768),
-- Bi-temporal (event time)
t_valid TIMESTAMPTZ,
t_invalid TIMESTAMPTZ,
-- Transaction time
t_created TIMESTAMPTZ DEFAULT NOW(),
t_expired TIMESTAMPTZ,
-- Provenance
source_episode_id BIGINT REFERENCES memory_node(id) ON DELETE SET NULL,
invalidated_by UUID REFERENCES memory_edge(id) ON DELETE SET NULL,
-- Contradiction handling
contradiction_status VARCHAR(20) DEFAULT 'active'
CHECK (contradiction_status IN ('active', 'candidate', 'confirmed_invalid', 'reviewed_keep')),
contradiction_confidence FLOAT,
contradiction_reviewed_at TIMESTAMPTZ,
contradiction_reviewed_by VARCHAR(255),
-- Metadata
confidence FLOAT DEFAULT 1.0,
access_count BIGINT DEFAULT 0,
-- Constraints
CONSTRAINT chk_no_self_loop CHECK (source_entity_id != target_entity_id)
);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_project
ON memory_edge(project_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_source
ON memory_edge(source_entity_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_target
ON memory_edge(target_entity_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_entity_pair
ON memory_edge(source_entity_id, target_entity_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_embedding
ON memory_edge USING hnsw (fact_embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_validity
ON memory_edge(t_valid, t_invalid)
WHERE t_expired IS NULL;
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_active
ON memory_edge(project_id, contradiction_status)
WHERE t_expired IS NULL AND contradiction_status = 'active';
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_fts
ON memory_edge USING gin(to_tsvector('english', fact));
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_edge_relation
ON memory_edge(project_id, relation_type);
-- ============================================
-- STEP 5: Compaction audit log
-- ============================================
CREATE TABLE IF NOT EXISTS compaction_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
run_at TIMESTAMPTZ DEFAULT NOW(),
tier INT NOT NULL,
project_id VARCHAR(255),
exact_dedup_count INT DEFAULT 0,
stale_gc_count INT DEFAULT 0,
llm_dedup_count INT DEFAULT 0,
promotion_count INT DEFAULT 0,
demotion_count INT DEFAULT 0,
affected_episode_ids BIGINT[] DEFAULT '{}',
affected_edge_ids UUID[] DEFAULT '{}',
status VARCHAR(20) DEFAULT 'running' CHECK (status IN ('running', 'success', 'error')),
error_message TEXT,
duration_ms INT
);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_compaction_run_at
ON compaction_log(run_at DESC);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_compaction_project
ON compaction_log(project_id, run_at DESC);
-- ============================================
-- STEP 6: Contradiction review queue
-- ============================================
CREATE TABLE IF NOT EXISTS contradiction_review_queue (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id VARCHAR(255) NOT NULL,
new_edge_id UUID NOT NULL REFERENCES memory_edge(id) ON DELETE CASCADE,
existing_edge_id UUID NOT NULL REFERENCES memory_edge(id) ON DELETE CASCADE,
confidence FLOAT NOT NULL,
explanation TEXT,
queued_at TIMESTAMPTZ DEFAULT NOW(),
reviewed_at TIMESTAMPTZ,
reviewed_by VARCHAR(255),
review_action VARCHAR(50) CHECK (review_action IN ('confirm_invalid', 'keep_both', 'pending'))
);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_review_queue_pending
ON contradiction_review_queue(project_id, queued_at)
WHERE reviewed_at IS NULL;
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_review_queue_edges
ON contradiction_review_queue(new_edge_id, existing_edge_id);
@@ -0,0 +1,140 @@
-- Phase 2.8: Authentication and Multi-Tenant Schema
--
-- Adds:
-- 1. memory_projects table (source of truth for project ownership)
-- 2. contributed_by columns (attribution tracking)
-- 3. Indexes for fast lookups
--
-- Note: RBAC itself lives in auth provider (Authentik, custom service, etc).
-- This schema only tracks project ownership and user attribution.
-- ─────────────────────────────────────────────────────────────────────────────
-- 1. Projects table (source of truth for ownership)
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS memory_projects (
id VARCHAR(255) PRIMARY KEY,
-- Ownership
owner_id VARCHAR(255) NOT NULL, -- JWT "sub" of creator
visibility VARCHAR(20) NOT NULL, -- "private" | "team" | "public"
-- Metadata
name VARCHAR(255), -- Display name
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Soft delete
deleted_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_memory_projects_owner_id
ON memory_projects(owner_id)
WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_memory_projects_visibility
ON memory_projects(visibility)
WHERE deleted_at IS NULL;
-- ─────────────────────────────────────────────────────────────────────────────
-- 2. Add attribution columns to entities
-- ─────────────────────────────────────────────────────────────────────────────
ALTER TABLE memory_entity ADD COLUMN IF NOT EXISTS (
contributed_by VARCHAR(255), -- JWT "sub" who added this
contribution_type VARCHAR(50), -- "extracted" | "manual" | "inferred"
contribution_date TIMESTAMPTZ
);
-- Populate contributed_by with defaults (assume "system" if not present)
UPDATE memory_entity
SET contributed_by = 'system'
WHERE contributed_by IS NULL;
ALTER TABLE memory_entity
ALTER COLUMN contributed_by SET NOT NULL;
CREATE INDEX IF NOT EXISTS idx_memory_entity_contributed_by
ON memory_entity(contributed_by);
CREATE INDEX IF NOT EXISTS idx_memory_entity_contribution_date
ON memory_entity(contribution_date)
WHERE contribution_date IS NOT NULL;
-- ─────────────────────────────────────────────────────────────────────────────
-- 3. Add attribution columns to edges
-- ─────────────────────────────────────────────────────────────────────────────
ALTER TABLE memory_edge ADD COLUMN IF NOT EXISTS (
contributed_by VARCHAR(255), -- JWT "sub" who added this
contribution_type VARCHAR(50) -- "extracted" | "inferred"
);
-- Populate contributed_by with defaults
UPDATE memory_edge
SET contributed_by = 'system'
WHERE contributed_by IS NULL;
ALTER TABLE memory_edge
ALTER COLUMN contributed_by SET NOT NULL;
CREATE INDEX IF NOT EXISTS idx_memory_edge_contributed_by
ON memory_edge(contributed_by);
-- ─────────────────────────────────────────────────────────────────────────────
-- 4. Add project_id to entities and edges (optional, for faster filtering)
-- ─────────────────────────────────────────────────────────────────────────────
-- ALTER TABLE memory_entity ADD COLUMN IF NOT EXISTS project_id VARCHAR(255);
-- ALTER TABLE memory_edge ADD COLUMN IF NOT EXISTS project_id VARCHAR(255);
--
-- Note: Deferred. Can use project info from path/source instead.
-- ─────────────────────────────────────────────────────────────────────────────
-- 5. Views for common queries
-- ─────────────────────────────────────────────────────────────────────────────
-- Entities contributed by a user in a time range
CREATE OR REPLACE VIEW v_user_contributions AS
SELECT
contributed_by,
COUNT(*) as entity_count,
MAX(contribution_date) as last_contribution,
ARRAY_AGG(DISTINCT contribution_type) as contribution_types
FROM memory_entity
WHERE contribution_date IS NOT NULL
GROUP BY contributed_by;
-- Recent contributions (last 7 days)
CREATE OR REPLACE VIEW v_recent_contributions AS
SELECT
contributed_by,
COUNT(*) as count,
MAX(contribution_date) as latest
FROM memory_entity
WHERE contribution_date > CURRENT_TIMESTAMP - INTERVAL '7 days'
GROUP BY contributed_by;
-- ─────────────────────────────────────────────────────────────────────────────
-- 6. Rollback support
-- ─────────────────────────────────────────────────────────────────────────────
-- To rollback this migration:
--
-- DROP VIEW IF EXISTS v_recent_contributions;
-- DROP VIEW IF EXISTS v_user_contributions;
--
-- DROP INDEX IF EXISTS idx_memory_projects_owner_id;
-- DROP INDEX IF EXISTS idx_memory_projects_visibility;
-- DROP TABLE IF EXISTS memory_projects;
--
-- DROP INDEX IF EXISTS idx_memory_entity_contributed_by;
-- DROP INDEX IF EXISTS idx_memory_entity_contribution_date;
-- ALTER TABLE memory_entity DROP COLUMN IF EXISTS contributed_by;
-- ALTER TABLE memory_entity DROP COLUMN IF EXISTS contribution_type;
-- ALTER TABLE memory_entity DROP COLUMN IF EXISTS contribution_date;
--
-- DROP INDEX IF EXISTS idx_memory_edge_contributed_by;
-- ALTER TABLE memory_edge DROP COLUMN IF EXISTS contributed_by;
-- ALTER TABLE memory_edge DROP COLUMN IF EXISTS contribution_type;
@@ -0,0 +1,72 @@
-- Phase 6: Temporal Workflow Service Integration
--
-- Adds reference/linking table for external Temporal.io workflows.
-- Temporal Service owns execution state; Memory DB owns reasoning traces.
--
-- This is a minimal linking schema - no duplication of workflow logic.
-- ─────────────────────────────────────────────────────────────────────────────
-- Temporal Workflow Links (External Service Reference)
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS temporal_workflow_links (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Temporal Service Reference (Required)
workflow_id VARCHAR(255) NOT NULL, -- Temporal workflow ID
workflow_type VARCHAR(255), -- e.g., "entity_extraction", "synthesis"
run_id VARCHAR(255), -- Temporal run ID
-- Memory Entity References (Optional - NULL until linked)
entity_id UUID REFERENCES memory_entity(id) ON DELETE SET NULL,
edge_id UUID REFERENCES memory_edge(id) ON DELETE SET NULL,
node_id BIGINT REFERENCES memory_node(id) ON DELETE SET NULL,
-- Sync Status
status VARCHAR(50) NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'completed', 'failed', 'archived')),
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_synced_at TIMESTAMPTZ,
-- Metadata (Extra context, error details, etc.)
metadata JSONB
);
-- Indexes for common queries
CREATE INDEX IF NOT EXISTS idx_temporal_workflow_id
ON temporal_workflow_links(workflow_id);
CREATE INDEX IF NOT EXISTS idx_temporal_entity_id
ON temporal_workflow_links(entity_id)
WHERE entity_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_temporal_edge_id
ON temporal_workflow_links(edge_id)
WHERE edge_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_temporal_node_id
ON temporal_workflow_links(node_id)
WHERE node_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_temporal_status
ON temporal_workflow_links(status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_temporal_workflow_type
ON temporal_workflow_links(workflow_type)
WHERE status = 'active';
-- ─────────────────────────────────────────────────────────────────────────────
-- Rollback Instructions
-- ─────────────────────────────────────────────────────────────────────────────
-- To rollback this migration:
--
-- DROP INDEX IF EXISTS idx_temporal_workflow_type;
-- DROP INDEX IF EXISTS idx_temporal_status;
-- DROP INDEX IF EXISTS idx_temporal_node_id;
-- DROP INDEX IF EXISTS idx_temporal_edge_id;
-- DROP INDEX IF EXISTS idx_temporal_entity_id;
-- DROP INDEX IF EXISTS idx_temporal_workflow_id;
-- DROP TABLE IF EXISTS temporal_workflow_links;