Skip to content

symfonic.capabilities.memory.reserved_keys

reserved_keys

The property names this capability owns, and why a producer may not set them.

Its own module because this is an authorisation boundary rather than a naming convention, and a boundary that lives inside a compatibility shim is one a reader has to go looking for.

A memory's properties bag is shared: the capability writes what it decides about a record -- which scope it belongs to, whether it is published, what its identity is, where it came from -- and a producer writes whatever its own vocabulary needs. Those two must not be able to collide, because every one of the capability's names grants something.

producer_metadata

producer_metadata(metadata: Mapping[str, Any] | None) -> dict[str, Any]

A producer's metadata with the capability's own names removed.

Dropped and logged rather than raised. A producer using a reserved name is a bug in the producer, and the two worse answers are honouring it -- which lets a record claim a scope or a durability it was not granted -- and failing the turn, which costs a user their answer because an extractor named a key badly. The memory is written correctly and the log says exactly what was ignored.

Source code in src/symfonic/capabilities/memory/reserved_keys.py
def producer_metadata(metadata: Mapping[str, Any] | None) -> dict[str, Any]:
    """A producer's metadata with the capability's own names removed.

    Dropped and logged rather than raised. A producer using a reserved name
    is a bug in the producer, and the two worse answers are honouring it --
    which lets a record claim a scope or a durability it was not granted --
    and failing the turn, which costs a user their answer because an
    extractor named a key badly. The memory is written correctly and the log
    says exactly what was ignored.
    """
    if not metadata:
        return {}
    reserved = reserved_property_keys()
    kept = {k: v for k, v in metadata.items() if k not in reserved}
    refused = sorted(set(metadata) - set(kept))
    if refused:
        logger.warning(
            "record metadata tried to set reserved propert%s %s; ignored -- "
            "these are the capability's own and decide scope, identity, "
            "provenance, durability and on whose authority a memory was "
            "written",
            "y" if len(refused) == 1 else "ies",
            refused,
        )
    return kept

reserved_property_keys

reserved_property_keys() -> frozenset[str]

The property names this capability owns, and a producer may not set.

Every one of them is something the capability decides: which scope a memory belongs to, whether it is published or still pending, what its identity is, where it came from, and on whose authority it was last written. A producer that could write them could file its memory into another scope, mark it durable without passing the gate that publishes, or claim a person typed it -- so these are not a namespace convention, they are an authorisation boundary.

Each has exactly one home, at the top of the properties bag. A producer's own vocabulary lives nested under :data:METADATA_KEY, so the same name can never appear in both places and a reader never has to decide which copy is authoritative.

Source code in src/symfonic/capabilities/memory/reserved_keys.py
def reserved_property_keys() -> frozenset[str]:
    """The property names this capability owns, and a producer may not set.

    Every one of them is something the capability *decides*: which scope a
    memory belongs to, whether it is published or still pending, what its
    identity is, where it came from, and on whose authority it was last
    written. A producer that could write them could file its memory into
    another scope, mark it durable without passing the gate that publishes,
    or claim a person typed it -- so these are not a namespace convention,
    they are an authorisation boundary.

    Each has exactly one home, at the top of the properties bag. A producer's
    own vocabulary lives nested under :data:`METADATA_KEY`, so the same name
    can never appear in both places and a reader never has to decide which
    copy is authoritative.
    """
    return frozenset({
        SCOPE_PATH_KEY,
        DURABILITY_KEY,
        PROVENANCE_KEY,
        RECORD_ID_KEY,
        ORIGIN_KEY,
        METADATA_KEY,
        EDITED_BY_KEY,
    })

validate_edit_authority

validate_edit_authority(value: str) -> str

Refuse an authority nobody has decided how to authorise.

Raised rather than dropped, unlike a reserved name in a producer's metadata bag. The two cases differ: a producer that named a reserved key was reaching for the capability's vocabulary by accident, and the memory is still worth keeping without it. A record that declares an authority is making a claim on purpose, and a claim nobody recognises is one no reader could act on -- so it is a construction error, where the caller can see it.

Source code in src/symfonic/capabilities/memory/reserved_keys.py
def validate_edit_authority(value: str) -> str:
    """Refuse an authority nobody has decided how to authorise.

    Raised rather than dropped, unlike a reserved name in a producer's
    metadata bag. The two cases differ: a producer that *named* a reserved key
    was reaching for the capability's vocabulary by accident, and the memory is
    still worth keeping without it. A record that *declares* an authority is
    making a claim on purpose, and a claim nobody recognises is one no reader
    could act on -- so it is a construction error, where the caller can see it.
    """
    if value and value not in EDIT_AUTHORITIES:
        raise MemoryContractError(
            f"{value!r} is not an edit authority. The vocabulary is closed "
            f"({', '.join(sorted(EDIT_AUTHORITIES))}) because this field is a "
            "claim about who wrote a memory, and 'user_manual_edit' is what "
            "makes a memory eligible for promotion onto somebody's profile. An "
            "unrecognised value is an authorisation nobody has granted."
        )
    return value