pub struct CapabilitySet {
pub tags: HashSet<Tag>,
pub metadata: BTreeMap<String, String>,
}Expand description
Complete capability set for a node.
Phase A.5.N.3 final shape: a typed tags: HashSet<Tag> plus
a metadata: BTreeMap for data that can’t safely round-trip
through the tag wire format. Hardware / Software / Model /
Tool / ResourceLimits are projections of these two fields,
computed on demand via views() / the From<&CapabilitySet>
impls. Typed-struct fields no longer exist on the storage
shape — every read goes through the projection layer; every
write goes through the typed setters which re-encode into the
canonical tag set.
Fields§
Canonical typed tag set. Holds:
Tag::AxisPresent/Tag::AxisValueaxis-prefixed tags (hardware.gpu,hardware.memory_gb=64,software.model.0.id=llama-3.1-70b, …) that encode the five projections.Tag::Reservedcross-axis tags (scope:tenant:foo,causal:<hex>,fork-of:<hex>,heat:*).Tag::Legacyuntyped tags (free-form strings, e.g.nat:full-cone/nrpc:<service>).
Wire format emits tags in sorted Tag::to_string() order so
every serialization is canonical. The HashSet keeps O(1)
membership for in-memory lookups; the serialize_with hook
flattens to a sorted Vec on the way out. Two sides of a
signed-announcement round-trip therefore produce identical
bytes regardless of HashSet iteration order (which is
process-local random and would otherwise cause spurious
signature-verification failures across processes).
metadata: BTreeMap<String, String>Free-form key-value metadata.
Phase A.5.N introduction. Carries data that doesn’t fit the typed-tag taxonomy:
- Tool schemas:
tool::<tool_id>::input_schemaandtool::<tool_id>::output_schemakeys hold JSON Schema strings (the=/:/,characters in JSON make these unsafe to round-trip through the tag wire format). - Intent:
intentkey carries the application-defined placement intent (Phase F). - Colocation hints:
colocate-withkey carries a chain origin hash for chain-aware placement. - Application-defined keys (subject to the metadata size cap in Phase C: 4 KB soft / 16 KB hard).
BTreeMap for deterministic iteration order over the wire.
Implementations§
Source§impl CapabilitySet
impl CapabilitySet
Sourcepub fn new() -> CapabilitySet
pub fn new() -> CapabilitySet
Create empty capability set
Sourcepub fn with_hardware(self, hardware: HardwareCapabilities) -> CapabilitySet
pub fn with_hardware(self, hardware: HardwareCapabilities) -> CapabilitySet
Set hardware capabilities
Sourcepub fn with_software(self, software: SoftwareCapabilities) -> CapabilitySet
pub fn with_software(self, software: SoftwareCapabilities) -> CapabilitySet
Set software capabilities
Sourcepub fn add_model(self, model: ModelCapability) -> CapabilitySet
pub fn add_model(self, model: ModelCapability) -> CapabilitySet
Add model capability. Read-modify-write through views()
since models live in the canonical tag set as
software.model.<i>.* indexed-encoding.
Sourcepub fn add_tool(self, tool: ToolCapability) -> CapabilitySet
pub fn add_tool(self, tool: ToolCapability) -> CapabilitySet
Add tool capability. Read-modify-write through views()
since tools live in the canonical tag set as
software.tool.<i>.* indexed-encoding; schemas are mirrored
into metadata by set_tools.
For adding more than one tool, prefer
Self::add_tools — the batch form invokes set_tools
exactly once instead of N times, dropping the announce-path
cost from O(N²) to O(N).
Sourcepub fn add_tools(
self,
tools: impl IntoIterator<Item = ToolCapability>,
) -> CapabilitySet
pub fn add_tools( self, tools: impl IntoIterator<Item = ToolCapability>, ) -> CapabilitySet
Batch counterpart to Self::add_tool — extends the current
tool list with every element of tools and invokes
set_tools exactly once. The single-set_tools call clears
stale tags + metadata once and re-encodes the final list, so
the cost is O(N) regardless of how many tools the iterator
yields.
Use this from announce paths that drain a tool_registry
(which can hold many tools); the per-call add_tool rebuilds
every previously-added tool’s tags + metadata, an O(N²)
pattern in the size of the registry.
Sourcepub fn add_tag(self, tag: impl Into<String>) -> CapabilitySet
pub fn add_tag(self, tag: impl Into<String>) -> CapabilitySet
Add a tag (parsed via the application-facing parser, which
rejects reserved cross-axis prefixes — use the dedicated
scope helpers for those). Untyped strings parse as
Tag::Legacy; axis-prefixed strings (hardware.gpu,
software.os=linux) parse as AxisPresent / AxisValue.
Empty tags and reserved-prefix tags are silently dropped
(the parser returns Err and we ignore it).
Sourcepub fn with_blob_capability(self, blob: BlobCapability) -> CapabilitySet
pub fn with_blob_capability(self, blob: BlobCapability) -> CapabilitySet
Add a typed BlobCapability projection. Emits the matching
dataforts.blob.* tags via the projection’s write_into.
Builder-style; producer-side counterpart to
BlobCapability::from_capability_set. Round-tripping
through both functions returns the original projection.
Sourcepub fn with_greedy_capability(self, greedy: GreedyCapability) -> CapabilitySet
pub fn with_greedy_capability(self, greedy: GreedyCapability) -> CapabilitySet
Add a typed GreedyCapability projection. Emits
dataforts.greedy.* tags.
Sourcepub fn with_gravity_capability(
self,
gravity: GravityCapability,
) -> CapabilitySet
pub fn with_gravity_capability( self, gravity: GravityCapability, ) -> CapabilitySet
Add a typed GravityCapability projection. Emits
dataforts.gravity.* tags.
Sourcepub fn with_tenant_scope(self, tenant_id: impl Into<String>) -> CapabilitySet
pub fn with_tenant_scope(self, tenant_id: impl Into<String>) -> CapabilitySet
Add a scope:tenant:<id> reserved tag, marking this
announcement as advertised under the given tenant. Idempotent
— repeated calls with the same id do not duplicate. Empty
tenant_id is silently dropped (matches the scope resolver,
which rejects empty ids).
Sourcepub fn with_region_scope(self, region: impl Into<String>) -> CapabilitySet
pub fn with_region_scope(self, region: impl Into<String>) -> CapabilitySet
Add a scope:region:<name> reserved tag, marking this
announcement as advertised under the given region.
Idempotent. Empty region is silently dropped.
Sourcepub fn with_subnet_local_scope(self) -> CapabilitySet
pub fn with_subnet_local_scope(self) -> CapabilitySet
Add the scope:subnet-local reserved tag, opting this
announcement out of cross-subnet discovery. The strictest
scope wins: any tenant / region tags also present on this
set are ignored by the scope resolver while
scope:subnet-local is set. Idempotent.
Sourcepub fn require_chain(self, chain_hash: impl AsRef<str>) -> CapabilitySet
pub fn require_chain(self, chain_hash: impl AsRef<str>) -> CapabilitySet
Declare this node holds the chain identified by chain_hash.
Emits the causal:<chain_hash> reserved tag. Idempotent —
repeated calls with the same hash do not duplicate.
Sourcepub fn require_chain_tip(
self,
chain_hash: impl AsRef<str>,
tip_seq: u64,
) -> CapabilitySet
pub fn require_chain_tip( self, chain_hash: impl AsRef<str>, tip_seq: u64, ) -> CapabilitySet
Declare this node holds the chain <chain_hash> up to the
named tip_seq.
Emits causal:<chain_hash>:<tip_seq>. Per
CAPABILITY_SYSTEM_PLAN.md §2: receivers downsample chains
shorter than they need, so a peer announcing a tip_seq is
implicitly also a holder for every prefix of that chain.
Sourcepub fn require_chain_range(
self,
chain_hash: impl AsRef<str>,
start_seq: u64,
end_seq: u64,
) -> CapabilitySet
pub fn require_chain_range( self, chain_hash: impl AsRef<str>, start_seq: u64, end_seq: u64, ) -> CapabilitySet
Declare this node holds the half-open range [start_seq..end_seq)
of the chain <chain_hash>.
Emits causal:<chain_hash>[<start>..<end>]. The validator
enforces start_seq < end_seq; equal or inverted ranges are
silently dropped.
Sourcepub fn require_any_chain<I, S>(self, chain_hashes: I) -> CapabilitySet
pub fn require_any_chain<I, S>(self, chain_hashes: I) -> CapabilitySet
Declare this node holds any of the named chains. One
causal:<hash> reserved tag emitted per non-empty hash.
Empty / blank hashes in the iterator are silently skipped.
Sourcepub fn from_fork(self, parent_chain_hash: impl AsRef<str>) -> CapabilitySet
pub fn from_fork(self, parent_chain_hash: impl AsRef<str>) -> CapabilitySet
Declare this chain forks from parent_chain_hash.
Emits the fork-of:<parent_chain_hash> reserved tag, used
by the chain-discovery layer for lineage walks.
Sourcepub fn heat_level(self, chain_hash: impl AsRef<str>, rate: f64) -> CapabilitySet
pub fn heat_level(self, chain_hash: impl AsRef<str>, rate: f64) -> CapabilitySet
Declare this node’s heat (read-rate / activity score) for the named chain.
rate is clamped to [0.0, 1.0] and emitted with two-decimal
precision (heat:<chain_hash>=0.85). Heat is per-chain, not
per-node; one call per chain.
Sourcepub fn with_limits(self, limits: ResourceLimits) -> CapabilitySet
pub fn with_limits(self, limits: ResourceLimits) -> CapabilitySet
Set resource limits
Sourcepub fn with_metadata(
self,
key: impl Into<String>,
value: impl Into<String>,
) -> CapabilitySet
pub fn with_metadata( self, key: impl Into<String>, value: impl Into<String>, ) -> CapabilitySet
Set or overwrite a metadata key-value entry.
CR-16: silently drops writes whose key matches a
substrate-reserved prefix (tool::). Those keys are
authored by the substrate’s own codecs (the tool codec
emits tool::<id>::input_schema etc.) and user code
must not collide with them — same shape as Tag::parse_user
rejecting reserved tag prefixes.
Note: the schema’s metadata_reserved exact-match list
(intent, colocate-with, priority, owner) is
intentionally NOT gated — those are well-known user-facing
scheduler hints; the substrate reads them to make placement
decisions, but user code is expected to set them. The
validator (validate_capabilities) does flag user writes
onto exact-match reserved keys as a MetadataReservedKey
warning so misconfiguration is visible without being fatal.
Substrate-internal callers that need to emit tool::* keys
use the with_metadata_unchecked sibling (crate-private).
Sourcepub fn set_hardware(&mut self, hardware: HardwareCapabilities)
pub fn set_hardware(&mut self, hardware: HardwareCapabilities)
Replace the hardware projection in-place.
Phase A.5.N.3: clears every hardware.* tag (excluding
hardware.limits.* which belongs to ResourceLimits) and
re-emits the new ones via hardware_to_tags.
Sourcepub fn set_software(&mut self, software: SoftwareCapabilities)
pub fn set_software(&mut self, software: SoftwareCapabilities)
Replace the software projection in-place.
Phase A.5.N.3: clears every software.* tag (excluding
software.model.* and software.tool.* which belong to
model/tool sub-collections) and re-emits the new ones.
Sourcepub fn set_limits(&mut self, limits: ResourceLimits)
pub fn set_limits(&mut self, limits: ResourceLimits)
Replace the resource-limits projection in-place.
Phase A.5.N.3: clears every hardware.limits.* tag and
re-emits the new ones.
Sourcepub fn set_models(&mut self, models: Vec<ModelCapability>)
pub fn set_models(&mut self, models: Vec<ModelCapability>)
Replace the loaded-model list in-place.
Phase A.5.N.3: clears every software.model.* tag and
re-emits the new indexed encoding via models_to_tags.
Sourcepub fn set_tools(&mut self, tools: Vec<ToolCapability>)
pub fn set_tools(&mut self, tools: Vec<ToolCapability>)
Replace the available-tool list in-place.
Phase A.5.N.3: clears every software.tool.* tag, prunes
stale tool::<id>::*_schema metadata, re-emits the indexed
tag encoding, and mirrors fresh schemas into metadata.
Sourcepub fn has_tag(&self, tag: &str) -> bool
pub fn has_tag(&self, tag: &str) -> bool
Check if has a specific tag.
The query string is parsed via the permissive parser
(Tag::parse) so reserved-prefix queries (scope:tenant:foo)
resolve correctly. Set membership is exact: a query for
hardware.gpu matches the AxisPresent tag, not an
AxisValue with a different value.
Sourcepub fn has_model(&self, model_id: &str) -> bool
pub fn has_model(&self, model_id: &str) -> bool
Check if has a specific model.
Phase A.5.N.3: scans for software.model.<i>.id=<model_id>
directly in the canonical tag set rather than reconstructing
the full Vec<ModelCapability> via views().
Sourcepub fn has_tool(&self, tool_id: &str) -> bool
pub fn has_tool(&self, tool_id: &str) -> bool
Check if has a specific tool.
Phase A.5.N.3: scans for software.tool.<i>.tool_id=<tool_id>
directly in the canonical tag set.
Sourcepub fn has_gpu(&self) -> bool
pub fn has_gpu(&self) -> bool
Check if has GPU.
Phase A.5.N.3: looks for the hardware.gpu AxisPresent
marker directly. Cheaper than reconstructing the full
HardwareCapabilities projection.
Sourcepub fn model_ids(&self) -> Vec<String>
pub fn model_ids(&self) -> Vec<String>
Get all model IDs.
Phase A.5.N.3: returns owned Strings (rather than borrowed
&str over a typed-struct field that no longer exists).
Sourcepub fn to_bytes(&self) -> Vec<u8> ⓘ
pub fn to_bytes(&self) -> Vec<u8> ⓘ
Serialize to bytes — JSON format, kept as the default for wire
compatibility with peers running pre-postcard code. New callers
that don’t need to interop with old peers should prefer
Self::to_bytes_compact (~10× faster, ~3× smaller).
Sourcepub fn to_bytes_compact(&self) -> Vec<u8> ⓘ
pub fn to_bytes_compact(&self) -> Vec<u8> ⓘ
Serialize to bytes using the compact postcard wire format —
a single leading 0x01 version byte followed by the postcard
payload. Self::from_bytes reads either format via
first-byte sniff, so receivers running this code accept both
compact and JSON inputs.
See docs/misc/PERF_AUDIT_2026_05_28_CAPABILITY.md fix #3 for
the rollout staging — flipping to_bytes itself to compact is
a separate, deliberate wire-format change.
Sourcepub fn from_bytes(data: &[u8]) -> Option<CapabilitySet>
pub fn from_bytes(data: &[u8]) -> Option<CapabilitySet>
Deserialize from bytes. Accepts both the legacy JSON wire
format (peers running pre-postcard code) and the compact
postcard format (peers using Self::to_bytes_compact).
Discriminates on the first byte: b'{' → JSON, 0x01 →
postcard, anything else → None.
Sourcepub fn diff(&self, prev: &CapabilitySet) -> CapabilitySetDiff
pub fn diff(&self, prev: &CapabilitySet) -> CapabilitySetDiff
Compute the structural change from prev to self.
Phase 1 of CAPABILITY_ENHANCEMENTS_PLAN.md: a cheap
before/after change detector that returns the raw set/map
difference — added tags, removed tags, and per-key
metadata changes (Added / Removed / Updated). Powers
event-driven placement updates, capability-aware dashboards,
and delta-based metadata propagation.
Cost: O(|tags| + |metadata|). Two HashSet::difference
scans + a BTreeMap walk; no allocation beyond the output
collections.
Composes with crate::adapter::net::behavior::diff::DiffEngine:
DiffEngine::diff produces structural DiffOps (used by
the propagation path); this method returns the raw set/map
diff (better for change-event consumers). Same input data;
pick the surface that matches the consumer’s shape.
Sourcepub fn views(&self) -> CapabilityViews<'_>
pub fn views(&self) -> CapabilityViews<'_>
All five view projections rolled into one struct, computed
once per call. Cheaper than calling each From<&CapabilitySet>
individually when the consumer reads more than one of them.
let caps = CapabilitySet::new();
let views = caps.views();
let _ = views.hardware();
let _ = views.software();
let _ = views.resource_limits();
let _ = views.models();
let _ = views.tools();Borrowing handle exposing the five typed projections
(HardwareCapabilities, SoftwareCapabilities,
ResourceLimits, Vec<ModelCapability>,
Vec<ToolCapability>).
Phase A.5.N.3 + Phase 1 of CAPABILITY_ENHANCEMENTS_PLAN.md:
each projection is decoded from the canonical tag set
(+ metadata, for tool schemas) on first access and cached
for the lifetime of the handle. Repeated reads of the same
projection hit the cache; reads of unrelated projections
don’t force the full set of decoders.
The handle borrows self. Mutations to self invalidate
the handle (compiler-enforced through the lifetime).
All capability data as a typed-tag set, including the
hardware / software / models / tools / limits structs
re-encoded as axis-prefixed tags AND the legacy tags
Vec<String> parsed via Tag::parse. The future wire
format (Phase A.5.2+) is exactly this HashSet<Tag>.
Round-trip-stable: Self::from_typed_tags(&caps.typed_tags())
produces a CapabilitySet semantically equal to caps,
modulo the documented order non-preservation for non-indexed
Vec fields (runtimes / frameworks / drivers).
Cost: linear in tag count. Currently computed on every call; downstream callers that read in a hot loop should cache the result.
Build a CapabilitySet from a typed-tag set. Inverse of
Self::typed_tags; uses the per-struct decoders to
reconstruct the typed fields plus a legacy-carrier scan for
reserved-prefix tags + unknown axis tags.
See Self::typed_tags for the round-trip contract.
Trait Implementations§
Source§impl Clone for CapabilitySet
impl Clone for CapabilitySet
Source§fn clone(&self) -> CapabilitySet
fn clone(&self) -> CapabilitySet
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for CapabilitySet
impl Debug for CapabilitySet
Source§impl Default for CapabilitySet
impl Default for CapabilitySet
Source§fn default() -> CapabilitySet
fn default() -> CapabilitySet
Source§impl<'de> Deserialize<'de> for CapabilitySet
impl<'de> Deserialize<'de> for CapabilitySet
Source§fn deserialize<__D>(
__deserializer: __D,
) -> Result<CapabilitySet, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(
__deserializer: __D,
) -> Result<CapabilitySet, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
Source§impl From<&CapabilitySet> for HardwareCapabilities
impl From<&CapabilitySet> for HardwareCapabilities
Source§fn from(caps: &CapabilitySet) -> HardwareCapabilities
fn from(caps: &CapabilitySet) -> HardwareCapabilities
Source§impl From<&CapabilitySet> for SoftwareCapabilities
impl From<&CapabilitySet> for SoftwareCapabilities
Source§fn from(caps: &CapabilitySet) -> SoftwareCapabilities
fn from(caps: &CapabilitySet) -> SoftwareCapabilities
Source§impl From<&CapabilitySet> for ResourceLimits
impl From<&CapabilitySet> for ResourceLimits
Source§fn from(caps: &CapabilitySet) -> ResourceLimits
fn from(caps: &CapabilitySet) -> ResourceLimits
Source§impl PartialEq for CapabilitySet
impl PartialEq for CapabilitySet
Source§fn eq(&self, other: &CapabilitySet) -> bool
fn eq(&self, other: &CapabilitySet) -> bool
self and other values to be equal, and is used by ==.