# Copyright 2025 DatologyAI
# SPDX-License-Identifier: Apache-2.0
"""Helpers for child/contributor-aware metadata construction."""
from collections.abc import Iterable
from typing import Any
from zephon.types import ContributorRef, SampleCursor, SampleMeta, SampleRecord
[docs]
def spawn_child(
parent: SampleMeta,
child_idx: int,
*,
is_last_child: bool = False,
tags: dict[str, Any] | None = None,
) -> SampleMeta:
"""Create metadata for a child derived from ``parent``.
- Assigns deterministic child lineage for identity/replay.
- Contributors:
* If the parent has no contributors (single-base record), emit one
contributor tied to the child cursor and honor ``is_last_child``.
* If the parent already has contributors (e.g., a child or packed input),
preserve their cursors. Only the final child inherits their closing
flags; every earlier child gets non-closing refs.
- ``is_last_child`` identifies the final child of this immediate parent.
An inherited closing flag is responsibility to complete a base offset,
not evidence that it has already been delivered and closed.
- Raises if you try to spawn a closing child from a tombstone.
"""
if is_last_child and parent.tombstone:
raise ValueError("Cannot spawn a closing child from tombstone metadata")
new_lineage = parent.lineage + (int(child_idx),)
if parent.contributors:
if is_last_child:
# Preserve the parent's contributors, including their closing flags.
refs = parent.contributors
else:
# Preserve every contributor's identity, but clear its closing flag.
refs = tuple(
ContributorRef(cursor=ref.cursor, is_last_child=False)
for ref in parent.contributors
)
else:
cursor = SampleCursor(
parent.chunk_id, parent.chunk_offset, parent.sample_id, new_lineage
)
refs = (ContributorRef(cursor=cursor, is_last_child=is_last_child),)
child_tags = dict(parent.tags) if tags is None else dict(tags)
child_tags.pop("_tombstone", None)
meta = SampleMeta(
sample_id=parent.sample_id,
lane_id=parent.lane_id,
chunk_id=parent.chunk_id,
chunk_offset=parent.chunk_offset,
# Preserve component contribution tracking from parent.
# Children inherit the same component membership as their parent.
component_sample_counts=dict(parent.component_sample_counts),
component_token_counts=(
dict(parent.component_token_counts)
if parent.component_token_counts is not None
else None
),
lineage=new_lineage,
tags=child_tags,
).with_contributors(refs)
return meta
[docs]
def tombstones_for_record(record: SampleRecord) -> list[SampleRecord]:
"""Emit tombstone records for every closing contributor in *record*.
Each record tracks which chunk offsets it contributes to via
``contribution_refs()``. Only refs with ``is_last_child=True`` need a
tombstone — intermediate children (from split/spawn) don't close the
base offset, so the engine doesn't need a signal for them.
"""
tombstones: list[SampleRecord] = []
for ref in record.meta.contribution_refs():
if ref.is_last_child:
tombstones.append(
SampleRecord(
meta=tombstone_meta(ref, record.meta.lane_id),
payload=None,
)
)
return tombstones
__all__ = [
"pack_meta",
"spawn_child",
"tombstone_meta",
"tombstones_for_record",
]