Skip to content

Reconciler misclassifies explicitly named sub-orchestrations as root orphans #312

Description

@pinodeca

Summary

Since #263, pg_durable schedules every JOIN, RACE, and non-root loop child with schedule_sub_orchestration_with_id() and controls the complete child ID:

{parent_instance_id}::{parent_execution_id}::{child_root_node_id}

The orphan reconciler added in #283 still identifies children only by Duroxide auto-generated naming:

fn is_sub_orchestration(id: &str) -> bool {
    id.starts_with("sub::") || id.contains("::sub::")
}

No current pg_durable scheduling path uses Duroxide’s no-ID API, so current children do not contain the sub:: marker and are misclassified as root-orphan candidates.

Impact

A failed explicitly named child has no df.instances row, so select_orphans() includes it. The worker truncates candidates to RECLAIM_BATCH before calling delete_instance_bulk(). Duroxide-pg correctly refuses to delete the child because its authoritative parent_instance_id is non-NULL.

Once enough failed composed children occupy the batch, they can be selected repeatedly and prevent genuine root orphans from being reached. This defeats orphan reclamation and permits engine storage to grow without bound. It does not currently delete live work or corrupt workflow results because Duroxide-pg’s root-only deletion predicate is the final guard.

Severity: Medium. The failure is operational/resource leakage rather than workflow corruption, but it can permanently stall an advertised maintenance safeguard.

History

Acceptance criteria

  • Explicit composed child IDs are excluded before applying RECLAIM_BATCH.
  • Legacy Duroxide-generated sub:: child IDs remain excluded.
  • More than RECLAIM_BATCH failed children cannot prevent a genuine root orphan from being selected.
  • Root-instance reclamation and cascading deletion of its children remain unchanged.
  • Tests use the actual current {parent}::{generation}::{node} shape.
  • Prefer authoritative parent metadata if the Duroxide management API exposes it; otherwise use the shared typed composed-ID parser planned for execution stamps and child IDs.

Relevant code

  • src/orchestrations/execute_function_graph.rs: subtree_instance_id() and all schedule_sub_orchestration_with_id() call sites
  • src/worker.rs: is_sub_orchestration(), select_orphans(), and reclaim_orphaned_instances()

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions