Overview
Implement a Policy Evaluation Engine that periodically scans nodes and executes lifecycle/retention policy transitions. This is the runtime component that makes policies actionable.
Architecture Context
This issue is part of the Universal Graph Architecture Transition (Epic #783).
Policies are declarative rules stored as nodes. The Policy Evaluation Engine is the runtime that:
- Reads enabled policies
- Finds nodes matching policy triggers
- Executes the specified actions (archive, delete, purge)
- Optionally triggers workflows for side effects
Problem Statement
With policy nodes defined (#755), we need a system to actually evaluate and execute them. Without this:
- Lifecycle policies exist but do nothing
- Users must manually archive/delete content
- Retention policies can't auto-purge old deleted content
Proposed Solution
1. Engine Architecture
┌─────────────────────────────────────────────────────────┐
│ Policy Evaluation Engine │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Loader │ │ Evaluator │ │ Executor │ │
│ │ │ │ │ │ │ │
│ │ Read active │ │ Find nodes │ │ Transition │ │
│ │ policies │ │ matching │ │ lifecycle │ │
│ │ │ │ triggers │ │ states │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Scheduler (Background Job) │ │
│ │ - Runs every N minutes (configurable) │ │
│ │ - Respects app idle state │ │
│ │ - Batches operations to avoid blocking │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
2. Policy Types Supported
| policy_type |
Trigger Types |
Actions |
lifecycle |
last_modified, created_at, last_accessed |
archive, delete |
retention |
deleted_at, archived_at |
purge (hard delete) |
3. Trigger Conditions
{
"trigger": {
"type": "last_modified",
"days": 90,
"condition": "properties.status == 'done'"
}
}
type: Which timestamp to check
days: How old before triggering
condition: Optional SurrealQL/expression filter
4. Evaluation Query Example
-- Find tasks to archive (done + not modified in 90 days)
SELECT id FROM node
WHERE node_type IN $applies_to
AND lifecycleStatus = 'active'
AND properties.status == 'done'
AND modified_at < time::now() - 90d
LIMIT 100; -- Batch size
5. Execution Flow
- Load enabled policies ordered by priority
- Evaluate each policy, collecting matching node IDs
- Execute state transitions in batches
- Emit events for workflow triggers (if configured)
- Log actions for audit trail
6. Safety Constraints
- Batch size: Max 100 nodes per execution cycle
- Idle requirement: Only run when app idle > 30 seconds
- Rate limit: Max 1 evaluation cycle per minute
- Dry run mode: Preview what would change without executing
Acceptance Criteria
Phase 1: Core Engine
Phase 2: Lifecycle Policies
Phase 3: Retention Policies
Phase 4: Safety & Observability
Phase 5: Workflow Integration
Phase 6: Quality
Technical Specifications
Reference Files
Scheduling Options
| Trigger |
Behavior |
| Interval |
Every 5 minutes (default) |
| App idle |
After 30 seconds of no user activity |
| Manual |
User triggers "Run policies now" |
| Startup |
Light evaluation on app launch |
Condition Expression Syntax
Use a subset of SurrealQL or a simple expression language:
properties.status == 'done'
properties.priority IN ['low', 'medium']
content CONTAINS 'draft'
Dependencies
Related Issues
Overview
Implement a Policy Evaluation Engine that periodically scans nodes and executes lifecycle/retention policy transitions. This is the runtime component that makes policies actionable.
Architecture Context
This issue is part of the Universal Graph Architecture Transition (Epic #783).
Policies are declarative rules stored as nodes. The Policy Evaluation Engine is the runtime that:
Problem Statement
With policy nodes defined (#755), we need a system to actually evaluate and execute them. Without this:
Proposed Solution
1. Engine Architecture
2. Policy Types Supported
lifecyclelast_modified,created_at,last_accessedarchive,deleteretentiondeleted_at,archived_atpurge(hard delete)3. Trigger Conditions
{ "trigger": { "type": "last_modified", "days": 90, "condition": "properties.status == 'done'" } }type: Which timestamp to checkdays: How old before triggeringcondition: Optional SurrealQL/expression filter4. Evaluation Query Example
5. Execution Flow
6. Safety Constraints
Acceptance Criteria
Phase 1: Core Engine
Phase 2: Lifecycle Policies
last_modifiedtrigger type workscreated_attrigger type worksconditionexpressions evaluated correctlyarchiveaction transitions tolifecycleStatus: 'archived'deleteaction transitions tolifecycleStatus: 'deleted'Phase 3: Retention Policies
deleted_attrigger type works (time since deleted)purgeaction hard-deletes nodesPhase 4: Safety & Observability
Phase 5: Workflow Integration
Phase 6: Quality
bun run quality:fixbun run test:all)Technical Specifications
Reference Files
Scheduling Options
Condition Expression Syntax
Use a subset of SurrealQL or a simple expression language:
Dependencies
lifecycleStatusfield and policy schema)Related Issues