unmodifiable property

  1. @override
PulseBase<P> get unmodifiable
inherited

Returns a read‑only projection of this pulse, ensuring structural and payload-level finality for the current propagation hop.

Use this method to stabilize a signal before it crosses architectural boundaries, such as passing data to UI components, external logging systems, or downstream Receptor chains that should not modify the current state.

When to use

  • Receptor Protection: A Receptor can call .unmodifiable before returning a pulse to ensure that downstream consumers cannot mutate the payload or the context of this specific instance.
  • Cell Payload Safety: If the payload is a Cell, this prevents downstream logic from calling setters on that cell while still allowing them to observe its values.
  • Archiving: Ensuring a pulse stored in a history buffer or audit log cannot be retroactively altered.

How it works

  1. Privilege Attenuation: If the payload is a Cell, the returned pulse automatically projects it as an attenuated deputy (via Cell.unmodifiable). This strips mutation capabilities (Cell.modifiable) while preserving reactivity, ensuring downstream consumers can observe but not alter state.
  2. Recursive Provenance Locking: To maintain a tamper-proof Chain of Evidence, the entire causal lineage—including the parent, root, and source properties—is recursively projected as unmodifiable views.
  3. Structural Finality: The pulse envelope (metadata, context, and trace) is effectively sealed. This creates an Integrity Gate that prevents any modification to the current hop's record during the remainder of the propagation cycle.

Non‑obvious: Evolution is still permitted

Being "unmodifiable" does not terminate the causal chain. You can still call evolve or withStep on an unmodifiable pulse. Doing so creates a new, modifiable pulse instance that targets this unmodifiable pulse as its parent. This allows the signal to continue its journey while ensuring the historical record of this specific hop remains pristine.

Non‑obvious: Cells remain reactive

Converting a Cell payload to its unmodifiable form only prevents setting its value. The cell remains reactive; observers will still receive updates if the original source cell changes.

Example

// But you can still evolve the lineage
final next = protected.withStep('forwarded');
print(next.parent == protected); // true

Implementation

@override
PulseBase<P> get unmodifiable => UnmodifiablePulse<P>(this) as PulseBase<P>;