Form
A Form is a typed surface wrapped around a Nu node. It doesn't compute; it curates. Given a wrapped node, a Form offers the set of methods that would build observable compositions on top of it - and refuses the rest.
What a Form is
- wraps a single Nu node (a Ref or a value-node);
- exposes a typed surface (methods, operators, accessors) matched to the wrapped node's shape;
- each call constructs an Interaction on top of the wrapped node and returns a new Form.
A Form is itself a Nu - it participates in the tree. It doesn't do work; it chains through to the underlying Interactions.
Observability gate
The danger with a naive typed surface: a method builds a non-observable node and hands the user a validation failure after perfectly reasonable-looking code. So the surface a Form may offer is gated by the observability matrix.
The gate splits by what the Form wraps.
Value-node - a Literal, or any yielding non-Ref node. Only the yielding face is reachable:
- Query methods - yield, always observable. Offered.
- Action methods - offered, but addressless: they degrade to a Query (the mutate half evaporates, the yield survives).
- Command methods - never offered. A Command over a value-node voids -> non-observable -> invalid.
So Literal(5) + 10 and Literal([1,2,3]).pop() (degrades to a Query yielding 3) are fine; Literal([1,2,3]).append(4) is not offered - it would void.
Ref-node - the full face: Query + Command + Action. A Ref is an address, so a mutation lands.
This is the observability matrix from foundations resurfacing one layer up: the Form gate is the same value-node-versus-Ref split, applied to which methods a wrapper may expose.
Immutability as a policy
A Form's surface is fixed by what it wraps - value-node or Ref-node - not by a separate mutable / immutable axis. Immutability, when useful, is a policy on top: narrow a Ref's full face to the value-node's yield-only face. An immutable Ref voluntarily wears the value-node's mask - the same partition, applied by choice instead of by necessity.
Unknown surface
When a value-node's type is genuinely opaque - a dynamically-typed source, an unresolvable annotation - the Form degrades to an unknown surface. Every operation on it returns another unknown-surface Form. The chain never explodes; the user keeps composing. Mutation methods remain gated by the value-node rule: only yielding operations reach through.
Why a layer, not core
The model works without Forms - a program can always be written as raw Interactions. Forms make it ergonomic. They are the typed-surface discipline over Refs and value-nodes; the algebra underneath doesn't change.