@@ -58,19 +58,33 @@ dependency in the read path.
5858 1 . ** Drop the duroxide dependency** — read solely from ` df.nodes ` (one
5959 ` SELECT ` of the instance's rows). Remove ` list_executions ` / the fabricated
6060 ` execution_id ` cross join.
61- 2 . For each node, ** override** the returned ` status ` (and ` status_details ` ) to
62- the * effective* value at read time, derived from ancestors:
63- - ** Leaf→root climb:** find the first ancestor whose ` execution_id ` is not a
64- prefix of the node's. Ancestor ` failed ` ⇒ ` skipped ` ; ` running ` /newer
65- execution ⇒ ` pending ` ; ` completed ` but branch/iteration not taken ⇒
66- ` skipped ` ; RACE loser ⇒ ` cancelled ` .
67- - Use the IF decision and RACE winner recorded in ` status_details `
68- (Step 6) to know which branch/iteration is "taken".
69- 3 . Children are reachable via ` left_node ` /` right_node ` ; the tree is immutable
61+ 2 . For each node, ** leave ` status ` and the stored ` execution_id ` untouched**
62+ and instead ** add inferred fields to the returned ` status_details ` ** :
63+ - ` inferred_status ` — the * effective* state at read time (` skipped ` ,
64+ ` cancelled ` , ` pending ` , or the node's own physical status when no
65+ ancestor overrides it).
66+ - ` inferred_status_from_ancestor_id ` — the node whose state * determined*
67+ ` inferred_status ` (the first ancestor whose ` execution_id ` is not a
68+ prefix of this node's). Makes the derivation explainable per row.
69+ - We deliberately do NOT add ` inferred_status_execution_id ` : it is just the
70+ ` execution_id ` of ` inferred_status_from_ancestor_id ` 's row, so a reader
71+ can join to that node if needed.
72+ - Rationale: the read path ** must not compete with the write path** . By
73+ augmenting (not overwriting) ` status_details ` , the helper never races the
74+ orchestration's fenced writes. (If we later materialize this at the DB
75+ level it would compete — defer that decision.)
76+ 3 . ** Derivation (leaf→root climb):** find the first ancestor whose
77+ ` execution_id ` is not a prefix of the node's; that ancestor's state sets
78+ ` inferred_status ` . Ancestor ` failed ` ⇒ ` skipped ` ; ` running ` /newer execution
79+ ⇒ ` pending ` ; ` completed ` but branch/iteration not taken ⇒ ` skipped ` ; RACE
80+ loser ⇒ ` cancelled ` . Use the IF decision and RACE winner recorded in
81+ ` status_details ` (Step 6) to know which branch/iteration is "taken".
82+ 4 . Children are reachable via ` left_node ` /` right_node ` ; the tree is immutable
7083 after ` df.start() ` , so no parent column is required (optional safe
7184 denormalization only).
72- - Output columns: surface ` status_details ` alongside ` status ` ; the implicit
73- states appear only here, never in the base table.
85+ - Output columns: ` status ` and ` status_details ` keep their stored values; the
86+ inferred fields live inside the returned ` status_details ` JSONB only, never in
87+ the base table.
7488
7589## Step 4 — Document the ` execution_id `
7690
@@ -88,7 +102,9 @@ dependency in the read path.
88102 execution) drawn dashed, with a clear note that the dashed ones exist only in
89103 ` df.instance_nodes ` output.
90104- Document the inference helper used by ` instance_nodes ` (the ancestor-climb /
91- derivation function): inputs, the prefix rule, and each case.
105+ derivation function): inputs, the prefix rule, each case, and that it emits
106+ ` inferred_status ` + ` inferred_status_from_ancestor_id ` into ` status_details `
107+ rather than mutating the stored ` status ` /` execution_id ` .
92108
93109## Step 6 — RACE winner recording (small behavioral add)
94110
0 commit comments