View Source ProcessHub.Service.DeclaredChildren (ProcessHub v0.7.0)

The declared list: a versioned, durable, leader-written list of the children that SHALL exist on a hub.

Experimental

The declared-children feature is experimental and may change in future releases. Use in production at your own discretion.

start_child/3 with durable: true adds the child's spec, a deliberate stop removes it, and nothing else writes it — list absence is the stop record and never expires. Mutations serialize through the hub's leader (:elector, with the lowest hub member as deterministic fallback), which bumps one version per mutation; adoption replaces the whole list, higher version wins. The list commits before the process action, so the orphan reconcile heals a crashed command in the next round.

The list persists in its own DETS-backed store beside the registry, is cached in misc storage for reads, and optionally ships to an off-cluster ProcessHub.Storage.RemoteManifest. A missing or corrupt list with durable evidence behind it parks the reconcile instead of opening empty; clear/1 is the operator override. See guides/Persistence.md for the full model.

Summary

Types

The declared list with its version lineage, as persisted and shipped.

Functions

Adopts an incoming manifest when it wins: a higher version wholesale, a tie with differing content by lowest mutating node (WARN + tiebreak hook). MUST run inside the coordinator process.

Announces the local list version to hub peers; a lower-version peer pulls the full list. A no-op while the gate is off, parked, or nothing declared.

Applies a mutation as the leader; MUST run inside the coordinator process so writes serialize. A mutation that changes nothing does not bump the version.

Resolves the list on coordinator boot; see ProcessHub.Service.DeclaredChildren.Boot.

Operator call: clears the hub's declared list. Destructive — nothing remains declared and the reconcile stops running declared children. Written above every known version so it wins adoption everywhere; lifts the park state.

Returns the hub's declared children and the list version from local storage. A hub without the feature in use returns %{version: 0, children: []}.

Parks continuation behind the write of manifest, the batch's working copy. One :flush_declared is queued per batch (Batch.add/3) and lands behind every request already in the coordinator's mailbox, so concurrent durable commands share one write and sync — and none of them runs before the write that covers its entry. MUST run inside the coordinator process.

Starts elector participation. Elector is node-global; the strategy module is set only when unset so another user of it keeps its configuration.

Writes and syncs the batch's working manifest once, publishes it (peers adopt the newest version, so the batch's intermediate versions need no broadcast of their own), then runs every parked command in arrival order and replies to its caller. A write that fails answers every parked caller with the error and runs none of them — no child starts on an intent that did not persist. MUST run inside the coordinator process; the coordinator flushes before it considers a peer's copy, so an adoption never overwrites a batch in flight.

The manifest wire/storage format this release reads and writes.

Resolves the hub's current leader: the elector leader when it is a hub member, otherwise the lexicographically lowest hub member — deterministic within a connected component, so exactly one node accepts writes.

Returns the cached manifest read directly from the hub's misc storage.

Handles a peer's version announce: when the local copy is older, fetches the peer's manifest in a supervised task and casts it back for adoption.

Builds a manifest at version with entries, mutated by this node.

Opens the list's durable store for the initializer; see Store.open/2.

Returns whether the hub's reconcile is parked over a lost declared list.

Commits the list additions a durable: true start requires, before any process starts. Refuses when the gate is off, the list is parked, a spec is :temporary, or no leader is reachable. :ok for non-durable starts.

Commits the list removals a stop requires, before any child terminates. The leader's copy is authoritative; with no leader reachable the stop is refused only when the local copy shows a declared child among child_ids.

Re-runs the boot-time remote comparison; MUST run inside the coordinator.

Returns the full cached manifest, or nil when none exists.

Types

@type manifest() :: %{
  format: pos_integer(),
  version: non_neg_integer(),
  mutated_by: node(),
  entries: %{required(ProcessHub.child_id()) => ProcessHub.child_spec()}
}

The declared list with its version lineage, as persisted and shipped.

Functions

@spec adopt(ProcessHub.Hub.t(), manifest()) :: :ok

Adopts an incoming manifest when it wins: a higher version wholesale, a tie with differing content by lowest mutating node (WARN + tiebreak hook). MUST run inside the coordinator process.

@spec announce_version(ProcessHub.Hub.t()) :: :ok

Announces the local list version to hub peers; a lower-version peer pulls the full list. A no-op while the gate is off, parked, or nothing declared.

Link to this function

apply_mutation(hub, mutation)

View Source
@spec apply_mutation(
  ProcessHub.Hub.t(),
  {:add, [ProcessHub.child_spec()]} | {:remove, [term()]}
) ::
  :ok | {:pending, manifest()} | {:error, term()}

Applies a mutation as the leader; MUST run inside the coordinator process so writes serialize. A mutation that changes nothing does not bump the version.

A mutation that changes the list answers {:pending, manifest}: the new manifest is the batch's working copy, not yet written. The coordinator parks the command behind defer/4 and runs it from flush/1, after the one write and sync that persists the whole batch — so N commands cost one manifest write, not N rewrites of a list that grows with every child.

@spec boot(ProcessHub.Hub.t()) ::
  {:ok, :ready | :parked | {:remote_error, term()}} | {:error, term()}

Resolves the list on coordinator boot; see ProcessHub.Service.DeclaredChildren.Boot.

@spec clear(ProcessHub.hub_id()) :: :ok | {:error, term()}

Operator call: clears the hub's declared list. Destructive — nothing remains declared and the reconcile stops running declared children. Written above every known version so it wins adoption everywhere; lifts the park state.

Link to this function

declared_children(hub_id)

View Source
@spec declared_children(ProcessHub.hub_id()) :: %{
  version: non_neg_integer(),
  children: [ProcessHub.child_spec()]
}

Returns the hub's declared children and the list version from local storage. A hub without the feature in use returns %{version: 0, children: []}.

Link to this function

defer(hub, manifest, from, continuation)

View Source

Parks continuation behind the write of manifest, the batch's working copy. One :flush_declared is queued per batch (Batch.add/3) and lands behind every request already in the coordinator's mailbox, so concurrent durable commands share one write and sync — and none of them runs before the write that covers its entry. MUST run inside the coordinator process.

@spec ensure_election() :: :ok

Starts elector participation. Elector is node-global; the strategy module is set only when unset so another user of it keeps its configuration.

@spec flush(ProcessHub.Hub.t()) :: ProcessHub.Hub.t()

Writes and syncs the batch's working manifest once, publishes it (peers adopt the newest version, so the batch's intermediate versions need no broadcast of their own), then runs every parked command in arrival order and replies to its caller. A write that fails answers every parked caller with the error and runs none of them — no child starts on an intent that did not persist. MUST run inside the coordinator process; the coordinator flushes before it considers a peer's copy, so an adoption never overwrites a batch in flight.

@spec format() :: pos_integer()

The manifest wire/storage format this release reads and writes.

@spec leader(ProcessHub.Hub.t()) :: node()

Resolves the hub's current leader: the elector leader when it is a hub member, otherwise the lexicographically lowest hub member — deterministic within a connected component, so exactly one node accepts writes.

@spec manifest(ProcessHub.Hub.t()) :: manifest() | nil

Returns the cached manifest read directly from the hub's misc storage.

Link to this function

maybe_pull(hub, from_node, version)

View Source
@spec maybe_pull(ProcessHub.Hub.t(), node(), non_neg_integer()) :: :ok

Handles a peer's version announce: when the local copy is older, fetches the peer's manifest in a supervised task and casts it back for adoption.

Link to this function

new_manifest(version, entries)

View Source
@spec new_manifest(non_neg_integer(), %{
  required(ProcessHub.child_id()) => ProcessHub.child_spec()
}) ::
  manifest()

Builds a manifest at version with entries, mutated by this node.

Link to this function

open_storage(hub_id, registry_backend)

View Source
@spec open_storage(ProcessHub.hub_id(), term()) :: %{
  declared_backend: {module(), term()},
  declared_path: String.t()
}

Opens the list's durable store for the initializer; see Store.open/2.

@spec parked?(ProcessHub.Hub.t()) :: boolean()

Returns whether the hub's reconcile is parked over a lost declared list.

Link to this function

precommit_start(hub, child_specs, opts)

View Source
@spec precommit_start(ProcessHub.Hub.t(), [ProcessHub.child_spec()], keyword()) ::
  :ok | {:pending, manifest()} | {:error, term()}

Commits the list additions a durable: true start requires, before any process starts. Refuses when the gate is off, the list is parked, a spec is :temporary, or no leader is reachable. :ok for non-durable starts.

Link to this function

precommit_stop(hub, child_ids)

View Source
@spec precommit_stop(ProcessHub.Hub.t(), [ProcessHub.child_id()]) ::
  :ok | {:pending, manifest()} | {:error, term()}

Commits the list removals a stop requires, before any child terminates. The leader's copy is authoritative; with no leader reachable the stop is refused only when the local copy shows a declared child among child_ids.

@spec remote_recompare(ProcessHub.Hub.t()) :: :ok | {:error, term()}

Re-runs the boot-time remote comparison; MUST run inside the coordinator.

@spec snapshot(ProcessHub.hub_id()) :: manifest() | nil

Returns the full cached manifest, or nil when none exists.