Skip to content

symfonic.memory.layers.procedural.lifecycle

lifecycle

One fail-closed lifecycle projection for persisted procedures.

Procedure rows have existed in two shapes: native procedural writes store lifecycle keys at the top level, while capability records carry producer metadata under record_metadata. Readers and the janitor must resolve those shapes the same way. Missing lifecycle evidence is a draft, never approval.

normalise_procedure_lifecycle

normalise_procedure_lifecycle(properties: Mapping[str, Any]) -> dict[str, Any]

Return a producer-safe procedural property bag.

Generic capability writers persist producer vocabulary below record_metadata. A producer may describe a proposed status there, but it must not turn that description into an active procedure before the procedural review domain has made a transition. Preserve all unrelated semantic metadata and force the only safe admission state.

This intentionally does not touch rows which already carry a native review seal: it is a write-boundary admission function, not a migration rewrite for durable historical procedures.

Source code in src/symfonic/memory/layers/procedural/lifecycle.py
def normalise_procedure_lifecycle(properties: Mapping[str, Any]) -> dict[str, Any]:
    """Return a producer-safe procedural property bag.

    Generic capability writers persist producer vocabulary below
    ``record_metadata``.  A producer may describe a proposed status there, but
    it must not turn that description into an active procedure before the
    procedural review domain has made a transition.  Preserve all unrelated
    semantic metadata and force the only safe admission state.

    This intentionally does *not* touch rows which already carry a native
    review seal: it is a write-boundary admission function, not a migration
    rewrite for durable historical procedures.
    """
    result = dict(properties)
    nested = result.get("record_metadata")
    if isinstance(nested, Mapping):
        result["record_metadata"] = {
            key: value for key, value in nested.items() if key not in {"status", "active"}
        }
    result["status"] = "draft"
    result["active"] = False
    return result

procedure_authority_class

procedure_authority_class(properties: Mapping[str, Any]) -> str

Classify lifecycle provenance without upgrading old rows.

sealed_terminal is authority minted by the review domain. A terminal row marked by the compatibility writer is an automatic row and can be compacted with another such replay. An unmarked old terminal is historical_terminal: it remains evidence and is never silently reclassified. All other producer drafts are either explicitly automatic Phase-12 output or manual drafts, which must not be deleted under a terminal merely because their text happens to match.

Source code in src/symfonic/memory/layers/procedural/lifecycle.py
def procedure_authority_class(properties: Mapping[str, Any]) -> str:
    """Classify lifecycle provenance without upgrading old rows.

    ``sealed_terminal`` is authority minted by the review domain.  A terminal
    row marked by the compatibility writer is an ``automatic`` row and can be
    compacted with another such replay.  An unmarked old terminal is
    ``historical_terminal``: it remains evidence and is never silently
    reclassified.  All other producer drafts are either explicitly automatic
    Phase-12 output or manual drafts, which must not be deleted under a
    terminal merely because their text happens to match.
    """
    status, _ = procedure_lifecycle(properties)
    if status in _REVIEWED_STATUSES and procedure_has_review(properties):
        return "sealed_terminal"
    if properties.get("procedure_lifecycle_origin") == _LEGACY_AUTO_ORIGIN:
        return "automatic"
    if status in _REVIEWED_STATUSES:
        return "historical_terminal"
    if properties.get("procedure_lifecycle_origin") == _PHASE_12_ORIGIN:
        return "automatic"
    return "manual_draft"

procedure_drafts_have_mixed_authority

procedure_drafts_have_mixed_authority(left: Mapping[str, Any], right: Mapping[str, Any]) -> bool

Whether otherwise similar drafts must remain independent evidence.

Source code in src/symfonic/memory/layers/procedural/lifecycle.py
def procedure_drafts_have_mixed_authority(
    left: Mapping[str, Any], right: Mapping[str, Any]
) -> bool:
    """Whether otherwise similar drafts must remain independent evidence."""
    return (
        procedure_lifecycle(left)[0] == procedure_lifecycle(right)[0] == "draft"
        and (procedure_authority_class(left) == "automatic")
        != (procedure_authority_class(right) == "automatic")
    )

procedure_has_human_review

procedure_has_human_review(properties: Mapping[str, Any]) -> bool

Whether the review domain, rather than a producer, sealed this row.

Source code in src/symfonic/memory/layers/procedural/lifecycle.py
def procedure_has_human_review(properties: Mapping[str, Any]) -> bool:
    """Whether the review domain, rather than a producer, sealed this row."""
    return properties.get("procedure_review_authority") == _HUMAN_REVIEW_AUTHORITY

procedure_has_review

procedure_has_review(properties: Mapping[str, Any]) -> bool

Whether the review domain sealed a terminal transition.

Producer metadata is never trusted as review authority: store_skill strips this reserved field before persistence. The two values here are minted only by locked review transitions in ProceduralLayer.

Source code in src/symfonic/memory/layers/procedural/lifecycle.py
def procedure_has_review(properties: Mapping[str, Any]) -> bool:
    """Whether the review domain sealed a terminal transition.

    Producer metadata is never trusted as review authority: ``store_skill``
    strips this reserved field before persistence. The two values here are
    minted only by locked review transitions in ``ProceduralLayer``.
    """
    return properties.get("procedure_review_authority") in {
        _HUMAN_REVIEW_AUTHORITY,
        _POLICY_REVIEW_AUTHORITY,
    }

procedure_is_legacy_reviewed

procedure_is_legacy_reviewed(properties: Mapping[str, Any]) -> bool

Whether a historic terminal row lacks the native review seal.

Old rows can contain an approved or rejected lifecycle but cannot prove who wrote it. Deduplication retains those rows rather than inferring a seal and deleting or demoting history during a native cleanup pass.

Source code in src/symfonic/memory/layers/procedural/lifecycle.py
def procedure_is_legacy_reviewed(properties: Mapping[str, Any]) -> bool:
    """Whether a historic terminal row lacks the native review seal.

    Old rows can contain an approved or rejected lifecycle but cannot prove who
    wrote it. Deduplication retains those rows rather than inferring a seal and
    deleting or demoting history during a native cleanup pass.
    """
    status, _ = procedure_lifecycle(properties)
    return status in _REVIEWED_STATUSES and not procedure_has_review(properties)

procedure_is_sealed_terminal

procedure_is_sealed_terminal(properties: Mapping[str, Any]) -> bool

A terminal human/policy decision cannot be replaced by generic writes.

Source code in src/symfonic/memory/layers/procedural/lifecycle.py
def procedure_is_sealed_terminal(properties: Mapping[str, Any]) -> bool:
    """A terminal human/policy decision cannot be replaced by generic writes."""
    return procedure_lifecycle(properties)[0] in _REVIEWED_STATUSES and procedure_has_review(
        properties,
    )

procedure_lifecycle

procedure_lifecycle(properties: Mapping[str, Any]) -> tuple[str, bool]

Return (status, active) using top-level then nested metadata.

Only an explicitly approved procedure can be active. Malformed or partial rows therefore fail closed as well.

Source code in src/symfonic/memory/layers/procedural/lifecycle.py
def procedure_lifecycle(properties: Mapping[str, Any]) -> tuple[str, bool]:
    """Return ``(status, active)`` using top-level then nested metadata.

    Only an explicitly approved procedure can be active. Malformed or partial
    rows therefore fail closed as well.
    """
    nested = properties.get("record_metadata")
    metadata = nested if isinstance(nested, Mapping) else {}
    status = properties.get("status", metadata.get("status", "draft"))
    active = properties.get("active", metadata.get("active", False))
    resolved_status = status if isinstance(status, str) else "draft"
    return resolved_status, resolved_status == "approved" and active is True

procedure_lifecycle_declared

procedure_lifecycle_declared(properties: Mapping[str, Any]) -> bool

Whether a row supplies lifecycle evidence in either supported shape.

Source code in src/symfonic/memory/layers/procedural/lifecycle.py
def procedure_lifecycle_declared(properties: Mapping[str, Any]) -> bool:
    """Whether a row supplies lifecycle evidence in either supported shape."""
    nested = properties.get("record_metadata")
    metadata = nested if isinstance(nested, Mapping) else {}
    return any(key in properties or key in metadata for key in ("status", "active"))