Tagged union over the two node kinds the engine recognises. Both
variants are structurally { readonly id: NodeId } at runtime;
the _phantom brand provides type-level narrowing without leaking
a discriminator onto the value. Everything else previous drafts
called a "kind" (formula, selector, constraint, resource,
effect, conflict, collection, index, workflow) is a
role a node can play, not a kind it permanently is — a
formula is just a derived whose compute happens to interpret
an expression string, a constraint is a derived returning a
validation result, an effect is not a node at all but a post-
commit subscription.
Tagged union over the two node kinds the engine recognises. Both variants are structurally
{ readonly id: NodeId }at runtime; the_phantombrand provides type-level narrowing without leaking a discriminator onto the value. Everything else previous drafts called a "kind" (formula,selector,constraint,resource,effect,conflict,collection,index,workflow) is a role a node can play, not a kind it permanently is — aformulais just aderivedwhose compute happens to interpret an expression string, aconstraintis aderivedreturning a validation result, aneffectis not a node at all but a post- commit subscription.