Skip to content

Federated update metadata

Validate the declared structure of a dense adapter update before aggregation without opening tensors, computing gradients, or accepting client identity. The envelope accepts only model and update SHA-256 references, an adapter format, parameter names, shapes, dtypes, an exact total element count, a clipping declaration, and the schema version.

Coordinator policy

The coordinator supplies FederatedUpdatePolicy independently of the update. Its parameter specifications are the complete allowlist of names, shapes, and dtypes expected for the selected model. Do not construct this policy from incoming update metadata: doing so would let a submitter authorize its own parameters. This module does not infer model structure or read a model file.

from openmed.training import (
    FEDERATED_UPDATE_METADATA_SCHEMA_VERSION,
    FederatedParameterMetadata,
    FederatedUpdateMetadata,
    FederatedUpdatePolicy,
)

# Synthetic digest references and adapter shapes for this example only.
model_digest = "sha256:" + "a" * 64
policy = FederatedUpdatePolicy(
    model_digest=model_digest,
    parameters=(
        FederatedParameterMetadata("adapter.lora_A.weight", (2, 3), "float32"),
        FederatedParameterMetadata("adapter.lora_B.weight", (4, 2), "float32"),
    ),
    require_clipped=True,
    max_total_elements=14,
)

payload = {
    "schema_version": FEDERATED_UPDATE_METADATA_SCHEMA_VERSION,
    "model_digest": model_digest,
    "adapter_format": "dense",
    "parameters": [
        {"name": "adapter.lora_B.weight", "shape": [4, 2], "dtype": "float32"},
        {"name": "adapter.lora_A.weight", "shape": [2, 3], "dtype": "float32"},
    ],
    "total_elements": 14,  # (4 * 2) + (2 * 3)
    "update_digest": "sha256:" + "b" * 64,
    "clipped": True,
}

metadata = FederatedUpdateMetadata.from_dict(payload, policy=policy)
canonical_json = metadata.to_json()
assert FederatedUpdateMetadata.from_json(canonical_json, policy=policy) == metadata

Use from_json() at a JSON boundary so duplicate object keys are rejected before they can be silently overwritten by a generic JSON decoder. from_dict() accepts built-in dictionaries and lists containing JSON-style values. Direct construction also requires a policy and enforces the same metadata invariants.

Version and validation rules

The schema identifier is openmed.training.federated_update_metadata.v1. Every envelope and parameter field shown above is required. Unknown fields are rejected; there is no extensions or free-text field.

Field or limit Contract
adapter_format Exactly dense; no sparse, packed, or container-specific formats
Model digest sha256: followed by 64 lowercase hexadecimal digits; must match policy
Update digest Same digest syntax; a declared content reference only
Parameter names Unique dotted ASCII identifiers, at most 256 characters; numeric path components such as layers.0.weight are allowed
Parameter set Exactly the coordinator's set, including each expected shape and dtype
Parameter count Between 1 and 1,024
Shape Between 1 and 8 positive integer dimensions, each at most 2**31 - 1
Dtype float16, bfloat16, float32, or float64, matching that parameter's policy
Element arithmetic Checked integer products and sum; at most 2**63 - 1
Total elements Must equal the sum of shape products and respect the policy budget, default 100,000,000
clipped Strict boolean; must be true unless trusted policy permits an unclipped declaration
JSON size At most 1 MiB of UTF-8 text, including whitespace

Booleans, strings, floating-point numbers, NaN, and infinity are not integer dimensions or counts. Scalars (zero-rank shapes), zero-sized dimensions, unknown parameters, missing parameters, duplicate parameters, unsupported versions, and overflows fail closed. Reordering dimensions is a shape mismatch even when the product remains the same.

Deterministic output and privacy boundary

Validated metadata is immutable. Parameter records are sorted by name; to_json() sorts object keys and emits indented JSON with a trailing newline. Changing the input parameter or key order does not change the canonical bytes. to_dict() returns fresh dictionaries and shape lists, so later caller mutations cannot change the validated envelope.

Tensor arrays, gradients, examples, client IDs, site names, paths, endpoints, and local metrics are rejected as unknown fields at every record level. Parameter-name syntax excludes paths, URLs, whitespace, and arbitrary free text. Known fields also reject invalid types instead of coercing them. Failures raise FederatedUpdateMetadataError with fixed messages that omit submitted keys and values; JSON parser excerpts are suppressed. The module performs no logging, filesystem access, network calls, tensor allocation, or training.

The accepted metadata is still a declaration. Valid digest syntax and a matching model reference do not verify tensor bytes, signatures, provenance, or the truth of a clipping claim. This schema does not certify that digests or coordinator-approved names are non-identifying. Those meanings depend on the trusted caller's policy and handling. Do not log arbitrary rejected payloads.

Round manifest verification, actual tensor allowlisting, and numerical clipping remain separate work in issues #2822, #2824, and #2825. This module has no imports from those pending implementations. See also federated round lifecycle and federated round status.