Skip to content

symfonic.core.prompt.blocks.capabilities

capabilities

What a block source can do, read structurally rather than self-reported.

:mod:symfonic.core.prompt.blocks.validation decides whether a declared block is allowed; this module answers the prior question it asks -- what the configured source actually offers. The two are separated because the answers are reused: the resolver, the renderer and the config-level checks all ask "is this source scope-aware?" and none of them should re-derive it from hasattr by hand.

Every answer here comes from the object, never from a claim it makes about itself in prose: a flag is coerced through :func:_coerced_flag (so a property that raises, or returns a non-bool, is treated as absent rather than trusted), and a capability is confirmed by callable() or hasattr() per member -- isinstance against a runtime_checkable Protocol checks member presence only, which a source that set load = True passes.

WRITE_CAPABILITIES module-attribute

WRITE_CAPABILITIES: tuple[str, ...] = ('list_revisions', 'load_revision', 'append_revision')

The write-specific members WritableBlockSource adds on top of BlockSource -- the two history methods plus the append verb.

Named here so the rejection message can say which capability is missing rather than only that the Protocol was not satisfied. This is not the full member set isinstance(source, WritableBlockSource) requires: BlockSource's own members (load, offline_safe, scope_aware) are required too, and are checked separately by :func:missing_write_capabilities -- see :data:_BASE_SOURCE_MEMBERS.

is_offline_safe

is_offline_safe(source: Any) -> bool

Return whether source can still be read during an outage.

The memory lane is not offline-safe: it reads the datastore, and that is precisely the dependency §2.7 says the authored spine must not have. An adapter that declares nothing is treated as unsafe, so the quieter outcome is never the accidental one.

Raises:

Type Description
TypeError

source.offline_safe is present and not a bool.

Source code in src/symfonic/core/prompt/blocks/capabilities.py
def is_offline_safe(source: Any) -> bool:
    """Return whether ``source`` can still be read during an outage.

    The memory lane is **not** offline-safe: it reads the datastore, and
    that is precisely the dependency §2.7 says the authored spine must
    not have. An adapter that declares nothing is treated as unsafe, so
    the quieter outcome is never the accidental one.

    Raises:
        TypeError: ``source.offline_safe`` is present and not a ``bool``.
    """
    if isinstance(source, str):
        return False
    return _coerced_flag(source, "offline_safe")

is_scope_aware

is_scope_aware(source: Any) -> bool

Return whether source can serve different content per scope.

The memory lane is scope-aware by construction -- it reads the tenant's own graph, so it cannot serve another tenant's content. Any other source is asked for its declared scope_aware member, and a source that does not declare one is treated as not scope-aware: an undeclared isolation property is not an isolation guarantee.

Raises:

Type Description
TypeError

source.scope_aware is present and not a bool.

Source code in src/symfonic/core/prompt/blocks/capabilities.py
def is_scope_aware(source: Any) -> bool:
    """Return whether ``source`` can serve different content per scope.

    The memory lane is scope-aware by construction -- it reads the
    tenant's own graph, so it cannot serve another tenant's content. Any
    other source is asked for its declared ``scope_aware`` member, and a
    source that does not declare one is treated as **not** scope-aware:
    an undeclared isolation property is not an isolation guarantee.

    Raises:
        TypeError: ``source.scope_aware`` is present and not a ``bool``.
    """
    if isinstance(source, str) and source == MEMORY_SOURCE:
        return True
    return _coerced_flag(source, "scope_aware")

missing_write_capabilities

missing_write_capabilities(source: Any) -> list[str]

Return the WritableBlockSource members source does not present.

Empty only for a source that satisfies WritableBlockSource. Covers the full member set, split by how "present" is checked:

  • :data:WRITE_CAPABILITIES and :data:_BASE_CALLABLE_MEMBERS (load) are methods, checked by callable() -- a source that sets one of these to a non-callable value (e.g. load = True) is reported as missing it, even though a bare isinstance(source, WritableBlockSource) would not catch that: a runtime_checkable Protocol's isinstance verifies member presence, not callability, for method-shaped members.
  • :data:_BASE_FLAG_MEMBERS (offline_safe, scope_aware) are data members, checked by hasattr().

Used to name the gap in the rejection message: "not writable" sends the adopter reading Protocol source, "missing append_revision" sends them to the one method they have to add -- and a source presenting all three write verbs but missing a capability flag now names that gap instead of reporting none.

Source code in src/symfonic/core/prompt/blocks/capabilities.py
def missing_write_capabilities(source: Any) -> list[str]:
    """Return the ``WritableBlockSource`` members ``source`` does not present.

    Empty **only** for a source that satisfies ``WritableBlockSource``.
    Covers the full member set, split by how "present" is checked:

    * :data:`WRITE_CAPABILITIES` and :data:`_BASE_CALLABLE_MEMBERS`
      (``load``) are methods, checked by ``callable()`` -- a source that
      sets one of these to a non-callable value (e.g. ``load = True``)
      is reported as missing it, even though a bare
      ``isinstance(source, WritableBlockSource)`` would not catch that:
      a ``runtime_checkable`` Protocol's ``isinstance`` verifies member
      *presence*, not callability, for method-shaped members.
    * :data:`_BASE_FLAG_MEMBERS` (``offline_safe``, ``scope_aware``) are
      data members, checked by ``hasattr()``.

    Used to name the gap in the rejection message: "not writable" sends
    the adopter reading Protocol source, "missing ``append_revision``"
    sends them to the one method they have to add -- and a source
    presenting all three write verbs but missing a capability flag now
    names *that* gap instead of reporting none.
    """
    if isinstance(source, str):
        return [*_BASE_SOURCE_MEMBERS, *WRITE_CAPABILITIES]
    callable_members = (*WRITE_CAPABILITIES, *_BASE_CALLABLE_MEMBERS)
    missing = [verb for verb in callable_members if not callable(getattr(source, verb, None))]
    missing += [member for member in _BASE_FLAG_MEMBERS if not hasattr(source, member)]
    return missing

source_label

source_label(source: Any) -> str

Return a short, human-meaningful name for source.

The adopter reading the error needs to recognise the thing they configured, which is a class for an adapter and the literal sentinel for the memory lane.

Source code in src/symfonic/core/prompt/blocks/capabilities.py
def source_label(source: Any) -> str:
    """Return a short, human-meaningful name for ``source``.

    The adopter reading the error needs to recognise the thing they
    configured, which is a class for an adapter and the literal sentinel
    for the memory lane.
    """
    if isinstance(source, str):
        return f"source={source!r}"
    return type(source).__name__