Skip to content

graflo.architecture.evolution.hashing

Content hashes over manifest blocks.

Lives at L4 rather than in migrate (L6) because the revision chain needs to verify a replayed manifest, and architecture.evolution may not import migrate. migrate.io re-exports these, which is where they have always been imported from.

Every hash is SHA-256 over :func:~graflo.architecture.evolution.canonicalize.canonical_payload, so two manifests that differ only in default-valued fields, key order, or the order of an order-insignificant list hash equal. CANON_VERSION is mixed into the hashed bytes: a future change to the canonicalization rules produces different hashes by construction rather than reinterpreting old ones.

Attributes

Functions:

full_hash(schema, ingestion_model, bindings)

Stable hash over a merged deployment object.

Source code in graflo/architecture/evolution/hashing.py
def full_hash(schema: Any, ingestion_model: Any, bindings: Any) -> str:
    """Stable hash over a merged deployment object."""
    payload = {
        "schema": _schema_payload(schema),
        "ingestion": canonical_payload(ingestion_model),
        "bindings": (
            canonical_payload(bindings)
            if hasattr(bindings, "to_minimal_canonical_dict")
            else (bindings.to_dict() if hasattr(bindings, "to_dict") else bindings)
        ),
    }
    return stable_hash(payload)

graph_hash(schema)

Stable hash over the logical graph model only.

Source code in graflo/architecture/evolution/hashing.py
def graph_hash(schema: Any) -> str:
    """Stable hash over the logical graph model only."""
    return stable_hash(canonical_payload(schema.core_schema))

ingestion_hash(ingestion_model)

Stable hash over the ingestion model (resources + transforms).

Source code in graflo/architecture/evolution/hashing.py
def ingestion_hash(ingestion_model: Any) -> str:
    """Stable hash over the ingestion model (resources + transforms)."""
    return stable_hash(canonical_payload(ingestion_model))

manifest_hash(manifest)

Stable hash over all three manifest blocks.

This is the identity a revision chain verifies against: replaying a chain from its base must reproduce the recorded hash at every step.

Source code in graflo/architecture/evolution/hashing.py
def manifest_hash(manifest: Any) -> str:
    """Stable hash over all three manifest blocks.

    This is the identity a revision chain verifies against: replaying a chain
    from its base must reproduce the recorded hash at every step.
    """
    payload = {
        "schema": _schema_payload(manifest.graph_schema),
        "ingestion_model": (
            canonical_payload(manifest.ingestion_model)
            if manifest.ingestion_model is not None
            else None
        ),
        "bindings": (
            canonical_payload(manifest.bindings)
            if manifest.bindings is not None
            else None
        ),
    }
    return stable_hash(payload)

schema_hash(schema)

Stable hash over the schema deployment contract (graph + DB profile).

metadata is deliberately excluded: name, semver and description are how a schema is labelled, not what it is, and a content address that moves when the version bumps cannot recognise that two versions carry identical content. :func:manifest_hash excludes it for the same reason.

Source code in graflo/architecture/evolution/hashing.py
def schema_hash(schema: Any) -> str:
    """Stable hash over the schema deployment contract (graph + DB profile).

    ``metadata`` is deliberately excluded: name, semver and description are how
    a schema is *labelled*, not what it *is*, and a content address that moves
    when the version bumps cannot recognise that two versions carry identical
    content. :func:`manifest_hash` excludes it for the same reason.
    """
    payload = {
        "core_schema": canonical_payload(schema.core_schema),
        "db_profile": canonical_payload(schema.db_profile),
    }
    return stable_hash(payload)

stable_hash(payload_obj)

SHA-256 over a canonical JSON rendering of payload_obj.

The canonicalization version is part of the hashed bytes, not a wrapper around them, so it cannot be stripped by a caller that re-serializes.

Source code in graflo/architecture/evolution/hashing.py
def stable_hash(payload_obj: Any) -> str:
    """SHA-256 over a canonical JSON rendering of *payload_obj*.

    The canonicalization version is part of the hashed bytes, not a wrapper
    around them, so it cannot be stripped by a caller that re-serializes.
    """
    return suthing.stable_hash({"canon": CANON_VERSION, "payload": payload_obj})