ReactiveDag.Node.Lapse (reactive_dag v0.17.0-rc.40)

Copy Markdown View Source

What a machine recompute does to a HUMAN's mark: lapse :approved_at, when_changed: :any.

The default needs no declaration and is SURVIVAL. The payload write only sets what the computation emits, so a column the upsert action does not accept is never touched — a transcript correction is about the recording, and re-extracting it does not make "you misheard that name" any less true.

Survival is wrong for a sign-off. "I checked this" is a claim about content, and when the content moves the claim is stale. That is what this declares:

lapse :approved_at,   when_changed: :any
lapse :signed_off_by, when_changed: [:total, :vote_count]
lapse MyApp.Correction, key: :meeting_id, when_changed: [:speaker_ids]

Its own comparison, not the propagate verdict

when_changed: runs a SECOND content comparison, narrowed to the fields named. This is not a convenience — the two grains are genuinely independent. A recompute can be :changed overall (so it propagates) while the lapse fields sat still, and the mark must then survive: a spelling fix that moves :label leaves an approval of :total standing. Reusing the propagate verdict would clear every approval on every cosmetic edit, and an approval that lapses constantly stops being read as information.

Its own write, ordered after the payload

A lapse is a SEPARATE write, made after the payload create and only when it fires. That ordering is what keeps survival free. Folding the nulling into the payload upsert's attrs would require the payload action to accept the human column — and it would then null that column on EVERY pass, destroying the default the feature is built around. So lapse needs an action of its own (lapse_action:, default :lapse) accepting the lapsing attributes; for a child resource, a destroy action.

A failure to clear is LOGGED, never raised: the payload write runs inside the drain's per-cell savepoint, and a raise in a nested transaction aborts the outer one — a mark that could not be cleared must not cost the recompute that moved the content. Everything checkable without writing (a missing attribute, a child resource with no destroy action, an over: naming a unit this node does not declare) raises at compile time or assembly instead, which is off the hot path.

:created never lapses

No prior record means no mark. A lapse is a comparison against what was there, and on the pass that first creates the row there is nothing to compare and nothing to clear.