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()
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:The orphan reconciler added in #283 still identifies children only by Duroxide auto-generated naming:
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.instancesrow, soselect_orphans()includes it. The worker truncates candidates toRECLAIM_BATCHbefore callingdelete_instance_bulk(). Duroxide-pg correctly refuses to delete the child because its authoritativeparent_instance_idis 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
schedule_sub_orchestration(), so Duroxide generatedsub::-marked IDs.sub::classifier and tests using synthetic legacy IDs, without covering the post-Node state-transition model: execution_id stamping + derived statuses #263 ID shape.execute-subtree.Acceptance criteria
RECLAIM_BATCH.sub::child IDs remain excluded.RECLAIM_BATCHfailed children cannot prevent a genuine root orphan from being selected.{parent}::{generation}::{node}shape.Relevant code
src/orchestrations/execute_function_graph.rs:subtree_instance_id()and allschedule_sub_orchestration_with_id()call sitessrc/worker.rs:is_sub_orchestration(),select_orphans(), andreclaim_orphaned_instances()