Files
lcbp3/specs/1-rfa-approval-refactor/research.md
T
2026-05-12 15:37:56 +07:00

7.1 KiB
Raw Blame History

Phase 0 Research: RFA Approval System Refactor

Date: 2026-05-11
Purpose: Research technical patterns and validate design decisions


Research Topics

1. Parallel Review in Workflow Engine

Research Task: Can Unified Workflow Engine (ADR-001) support parallel tasks with consensus rules?

Decision: Yes, with DSL Extension

Rationale:

  • Current DSL supports sequential states and transitions
  • Parallel review requires: (a) Task splitting on state entry, (b) Task aggregation before transition, (c) Consensus rule evaluation
  • Pattern: ParallelGateState - enters sub-workflows for each Discipline, aggregates on completion

Implementation Pattern:

// DSL Extension: Parallel Gateway
{
  type: 'parallel_gateway',
  config: {
    discriminator: 'discipline',  // Split by discipline
    minCompletion: 'majority',    // Consensus rule
    vetoConditions: [{ field: 'responseCode', value: '3' }] // Veto triggers
  }
}

Alternatives Considered:

  • Option A: Multiple Workflow Instances per RFA (rejected - too complex, hard to aggregate)
  • Option B: Sequential with fast-forward (rejected - doesn't truly parallelize)
  • Option C: Parallel Gateway in DSL (selected - clean abstraction, reusable pattern)

References:

  • BPMN 2.0 Parallel Gateway pattern
  • Existing workflow-dsl.schema.ts in codebase

2. Response Code Matrix Storage

Research Task: Best structure for Master Approval Matrix with 5 categories × 11 codes?

Decision: Normalized Relational Model with JSON for flexibility

Rationale:

  • Core codes (1A-1G, 2, 3, 4) are stable relational data
  • Category mappings (which codes apply to which doc types) need flexibility
  • Project overrides need inheritance tracking

Schema Design:

-- Core Response Codes (stable)
response_codes (id, code, sub_status, description_th, description_en, category)

-- Matrix Rules (project-specific overrides)
response_code_rules (
  id, 
  project_id NULLABLE,  -- NULL = global default
  document_type_id,
  response_code_id,
  is_enabled,
  requires_comments,
  triggers_notification,
  parent_rule_id  -- For inheritance tracking
)

Alternatives Considered:

  • Single JSON column for entire matrix (rejected - hard to query, validate, index)
  • Full EAV (Entity-Attribute-Value) (rejected - too complex for this use case)

3. Delegation Pattern & Circular Detection

Research Task: Best approach for delegation with chain depth limit and circular detection?

Decision: Adjacency List with Path Enumeration, Max Depth = 3

Rationale:

  • Adjacency List: Simple, fast for immediate lookup (delegator_id → delegatee_id)
  • Path Enumeration: Store full chain as array for circular detection
  • Max Depth 3: Prevents runaway chains while supporting realistic use cases

Circular Detection Algorithm:

function detectCircularDelegation(delegatorId: string, proposedDelegateeId: string): boolean {
  // BFS/DFS from proposedDelegatee, check if can reach delegatorId
  const visited = new Set<string>();
  const queue = [proposedDelegateeId];
  
  while (queue.length > 0) {
    const current = queue.shift()!;
    if (current === delegatorId) return true; // Circular!
    if (visited.has(current)) continue;
    visited.add(current);
    
    // Add all delegatees of current
    const delegatees = getActiveDelegations(current);
    queue.push(...delegatees.map(d => d.delegateeId));
  }
  return false;
}

Alternatives Considered:

  • Nested Set Model (rejected - overkill for simple chains)
  • Closure Table (rejected - requires maintenance on delegation expiry)

4. BullMQ Pattern for Reminders & Distribution

Research Task: Best BullMQ patterns for scheduled reminders and async distribution?

Decision: Delayed Jobs + Repeatable Jobs + Flows

Pattern Breakdown:

Reminders:

  • Delayed Jobs: Schedule individual reminder at due date
  • Repeatable Jobs: Daily reminder for overdue items (cron pattern)
  • Job Data: { rfaId, reviewerId, reminderType, escalationLevel }

Distribution:

  • Job Flow: Parent (distribution coordinator) → Children (individual deliveries)
  • Retry: 3 attempts with exponential backoff
  • Dead Letter: Failed distributions logged for manual intervention
// Reminder Queue Pattern
await reminderQueue.add('rfa-reminder', {
  rfaRevisionId,
  reviewerId,
  reminderType: 'DUE_SOON'
}, {
  delay: calculateDelay(dueDate, reminderDaysBefore)
});

// Distribution Flow Pattern
await distributionFlow.add({
  name: 'rfa-distribution',
  data: { rfaId, responseCode, recipients: [...] },
  children: recipients.map(r => ({
    name: 'deliver-document',
    data: { recipientId: r.id, method: r.deliveryMethod }
  }))
});

Alternatives Considered:

  • node-cron for scheduling (rejected - no persistence, no retry)
  • Custom scheduler service (rejected - BullMQ already provides this)

5. Review Task Status Aggregation

Research Task: How to efficiently calculate aggregate status for parallel reviews?

Decision: Materialized View + Real-time Counter

Rationale:

  • Materialized View: Fast reads for list views ("2 of 3 approved")
  • Real-time Counter: Immediate update on each review action
  • Trigger: Update counter on ReviewTask status change

Aggregation Logic:

-- Materialized view for fast reads
CREATE VIEW review_task_summary AS
SELECT 
  rfa_revision_id,
  COUNT(*) as total_disciplines,
  SUM(CASE WHEN status = 'COMPLETED' THEN 1 ELSE 0 END) as completed,
  SUM(CASE WHEN response_code = '3' THEN 1 ELSE 0 END) as rejected_count
FROM review_tasks
GROUP BY rfa_revision_id;

-- Real-time consensus check
SELECT CASE 
  WHEN rejected_count > 0 THEN 'REJECTED'  -- Veto triggered
  WHEN completed = total_disciplines THEN 
    CASE 
      WHEN approved_count / total_disciplines >= 0.5 THEN 'APPROVED'
      ELSE 'NEEDS_REVIEW'
    END
  ELSE 'IN_PROGRESS'
END as consensus_status
FROM review_task_summary;

Alternatives Considered:

  • Calculate on-demand (rejected - slow with many disciplines)
  • Application-level cache (rejected - stale data risk)

Summary of Decisions

Topic Decision Key Rationale
Parallel Review DSL Parallel Gateway Clean abstraction, reusable
Response Code Storage Normalized + JSON Balance of structure and flexibility
Delegation Adjacency List + Path Enum Simple, sufficient for depth ≤3
Queue Pattern BullMQ Delayed + Flows Industry standard, reliable
Status Aggregation Materialized View + Counter Fast reads, real-time updates

Risk Assessment

Risk Probability Mitigation
DSL Parallel Gateway complexity Medium Prototype with simple 2-discipline case first
Response Code migration from existing Low New tables, existing data untouched
Performance on large Review Teams Low Pagination on aggregation, Redis caching
Circular delegation algorithm Low Unit test with 3-level chains

Next Phase

Phase 1: Design data model and API contracts based on these research decisions.