Metadata
metadata is the node identity module of Softadastra Engine.
It describes a local node and, when needed, known remote nodes.
The core rule is:
Metadata describes nodes.
Store contains application state.2
Metadata does not sync values.
Metadata does not connect peers.
Metadata tells the runtime who a node is, what it can do, and where it is running.
Why metadata exists
Softadastra is local-first and peer-aware.
A node should be able to answer:
- who am I?
- what is my node id?
- what is my display name?
- what hostname am I running on?
- what operating system am I running on?
- what version am I running?
- what capabilities do I expose?
- how long have I been running?
This information is useful for:
- CLI status
- debug output
- dashboards
- peer inspection
- discovery enrichment
- transport diagnostics
- sync debugging
- runtime observability
Without metadata, peers are just raw node ids.
With metadata, nodes become understandable.
What metadata provides
The metadata module provides:
NodeMetadataNodeCapabilitiesMetadataOptionsMetadataServiceMetadataRegistryMetadataEncoderMetadataDecoderPlatformInfoHostnameVersionInfo- capabilities
- runtime info
It allows the engine to:
- create local node metadata
- refresh runtime information
- track node capabilities
- encode metadata
- decode metadata
- store known node metadata
- filter nodes by capability
- expose node identity to CLI and SDKs
What metadata does not do
metadata must not:
- store application data
- sync values
- connect peers
- discover peers by itself
- decide conflicts
- own WAL durability
- own transport delivery
- own CLI parsing
- format all terminal output
The rule is:
Metadata describes nodes.
Discovery finds nodes.
Transport connects nodes.
Sync propagates operations.
Store keeps local state.2
3
4
5
Include
Use the top-level include:
#include <softadastra/metadata/Metadata.hpp>Module location
The module lives in:
modules/metadata/Typical structure:
modules/metadata/
├── include/
│ └── softadastra/metadata/
│ ├── backend/
│ ├── core/
│ ├── encoding/
│ ├── registry/
│ ├── service/
│ ├── types/
│ ├── utils/
│ └── Metadata.hpp
├── src/
├── examples/
├── tests/
├── README.md
├── CMakeLists.txt
└── CHANGELOG.md2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
The exact structure can evolve, but the responsibility should stay stable:
describe nodes and expose runtime identityMain concepts
The metadata module is built around these concepts:
MetadataOptionsNodeMetadataNodeCapabilitiesMetadataRegistryMetadataServiceMetadataEncoderMetadataDecoderPlatformInfoHostnameVersionInfo
The normal local flow is:
MetadataOptions
↓
NodeMetadata
↓
refresh runtime
↓
local metadata snapshot
↓
CLI / SDK / diagnostics2
3
4
5
6
7
8
9
For remote nodes:
metadata received
↓
decode metadata
↓
MetadataRegistry
↓
query known nodes2
3
4
5
6
7
MetadataOptions
MetadataOptions configures metadata for the local node.
Example:
auto options =
metadata::MetadataOptions::local(
"node-a",
"0.1.0");2
3
4
This defines:
- node id
- version
- display name, when configured
- refresh interval, when supported
Metadata options example
#include <iostream>
#include <softadastra/metadata/Metadata.hpp>
using namespace softadastra;
int main()
{
std::cout << "== METADATA MINIMAL EXAMPLE ==\n";
auto options =
metadata::MetadataOptions::local(
"node-a",
"0.1.0");
if (!options.is_valid())
{
std::cerr << "invalid metadata options\n";
return 1;
}
auto config =
options.to_config();
std::cout << "node id: "
<< config.node_id
<< "\n";
std::cout << "display name: "
<< config.display_name
<< "\n";
std::cout << "version: "
<< config.version
<< "\n";
std::cout << "refresh interval ms: "
<< config.refresh_interval_ms()
<< "\n";
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Node id
The node id identifies the local node.
Good examples:
node-a
node-b
drive-client
drive-server
metadata-node
desktop-12
3
4
5
6
Avoid empty node ids.
A node id is used by:
- metadata
- sync operation origin
- transport messages
- discovery announcements
- CLI status
- SDK node info
The node id should be stable when sync, peer identity, or diagnostics depend on it.
Display name
The display name is a human-friendly label.
Example:
Softadastra SDK NodeA display name is useful for:
- dashboards
- CLI output
- debug tools
- peer lists
- logs
If no display name is available, the node id can be used as the fallback label.
Version
The version describes the runtime or application version.
Example:
0.1.0Version is useful when:
- nodes run different builds
- protocol compatibility matters
- debugging deployment issues
- inspecting peers
NodeMetadata
NodeMetadata is the main metadata object.
It can contain:
- node id
- display name
- runtime info
- capabilities
- created timestamp
- updated timestamp
Runtime info can include:
- hostname
- operating system
- version
- uptime
The exact fields depend on the implementation.
The important idea is:
NodeMetadata = node identity + runtime description + capabilitiesLocal metadata snapshot
A local metadata snapshot describes the current node.
Example:
auto metadata_snapshot =
metadata::core::NodeMetadata::foundation(
"node-a",
metadata::utils::Hostname::get(),
metadata::utils::PlatformInfo::os_name(),
metadata::utils::VersionInfo::current());2
3
4
5
6
Then refresh runtime fields:
metadata_snapshot.refresh_runtime();Local snapshot example
#include <iostream>
#include <softadastra/metadata/Metadata.hpp>
using namespace softadastra;
int main()
{
std::cout << "== METADATA LOCAL SNAPSHOT EXAMPLE ==\n";
auto metadata_snapshot =
metadata::core::NodeMetadata::foundation(
"node-a",
metadata::utils::Hostname::get(),
metadata::utils::PlatformInfo::os_name(),
metadata::utils::VersionInfo::current());
if (!metadata_snapshot.is_valid())
{
std::cerr << "invalid metadata snapshot\n";
return 1;
}
metadata_snapshot.refresh_runtime();
std::cout << "node id: "
<< metadata_snapshot.node_id()
<< "\n";
std::cout << "label: "
<< metadata_snapshot.label()
<< "\n";
std::cout << "hostname: "
<< metadata_snapshot.runtime.hostname
<< "\n";
std::cout << "os: "
<< metadata_snapshot.runtime.os_name
<< "\n";
std::cout << "version: "
<< metadata_snapshot.runtime.version
<< "\n";
std::cout << "uptime ms: "
<< metadata_snapshot.runtime.uptime_ms()
<< "\n";
std::cout << "capabilities: "
<< metadata_snapshot.capabilities.size()
<< "\n";
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
Minimal metadata
A minimal metadata object can describe a node with fewer capabilities.
Conceptual use:
- node id
- hostname
- OS
- version
- minimal capability set
Foundation metadata is richer and usually includes core runtime capabilities.
Label
A metadata label should return the most useful human-readable name.
Recommended behavior:
display name exists
↓
return display name
display name missing
↓
return node id2
3
4
5
6
7
This makes CLI and dashboards cleaner.
Runtime info
Runtime info describes where and how the node is running.
It can include:
- hostname
- operating system
- version
- start time
- uptime
This is useful for debugging distributed local-first systems.
Example output:
hostname : softadastra-dev
os : linux
version : 0.1.0
uptime : 18420 ms2
3
4
Hostname
Hostname reads the local machine name.
Example:
metadata::utils::Hostname::get()The hostname helps identify where a node is running.
This is especially useful when multiple nodes run on different machines.
PlatformInfo
PlatformInfo exposes platform data.
Example:
metadata::utils::PlatformInfo::os_name()It helps metadata answer:
- is this node running on Linux?
- is this node running on macOS?
- is this node running on Windows?
VersionInfo
VersionInfo exposes the current runtime version.
Example:
metadata::utils::VersionInfo::current()Version information helps with debugging and compatibility checks.
Uptime
Uptime describes how long the node runtime has been alive.
Example:
metadata_snapshot.runtime.uptime_ms()Uptime is useful for detecting:
- fresh starts
- unexpected restarts
- long-running nodes
- runtime lifecycle behavior
NodeCapabilities
NodeCapabilities describes what the node supports.
Capabilities can include:
CoreFSWALStoreSyncTransportDiscoveryMetadataCLIApp
The exact enum values depend on the implementation.
The important idea is:
capabilities tell what this node can doCapabilities example
#include <iostream>
#include <softadastra/metadata/Metadata.hpp>
using namespace softadastra;
int main()
{
std::cout << "== METADATA CAPABILITIES EXAMPLE ==\n";
metadata::core::NodeCapabilities capabilities;
capabilities.add(metadata::types::CapabilityType::Core);
capabilities.add(metadata::types::CapabilityType::Store);
capabilities.add(metadata::types::CapabilityType::Sync);
capabilities.add(metadata::types::CapabilityType::Transport);
capabilities.add(metadata::types::CapabilityType::Metadata);
std::cout << "capability count: "
<< capabilities.size()
<< "\n";
std::cout << "has sync: "
<< capabilities.has(metadata::types::CapabilityType::Sync)
<< "\n";
std::cout << "has foundation capability: "
<< capabilities.has_foundation_capability()
<< "\n";
std::cout << "has user-facing capability: "
<< capabilities.has_user_facing_capability()
<< "\n";
for (const auto capability : capabilities.values)
{
std::cout << "- "
<< metadata::types::to_string(capability)
<< "\n";
}
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Foundation capabilities
Foundation capabilities describe low-level runtime abilities.
Examples:
- core
- wal
- store
- sync
- transport
- discovery
- metadata
A foundation node may be a runtime node used for synchronization and storage.
User-facing capabilities
User-facing capabilities describe higher-level application abilities.
Examples:
- app
- cli
- dashboard
- sdk
The current implementation may only support a subset.
The useful rule is:
foundation capabilities describe the runtime
user-facing capabilities describe the interface2
Capability checks
Capability checks are useful for filtering.
Examples:
capabilities.has(metadata::types::CapabilityType::Sync)or:
capabilities.has_foundation_capability()or:
capabilities.has_user_facing_capability()This helps decide what kind of node is being inspected.
MetadataRegistry
MetadataRegistry stores metadata for known nodes.
It can:
- insert node metadata
- update node metadata
- find metadata by node id
- erase metadata
- list all metadata
- filter by capability
- list foundation nodes
- list user-facing nodes
The registry is useful when a node knows about multiple peers.
Registry example
#include <iostream>
#include <softadastra/metadata/Metadata.hpp>
using namespace softadastra;
int main()
{
std::cout << "== METADATA REGISTRY DEMO ==\n";
metadata::registry::MetadataRegistry registry;
auto node_a =
metadata::core::NodeMetadata::foundation(
"node-a",
"host-a",
"linux",
"1.0.0");
auto node_b =
metadata::core::NodeMetadata::minimal(
"node-b",
"host-b",
"linux",
"1.0.0");
registry.upsert(node_a);
registry.upsert(node_b);
std::cout << "registry size: "
<< registry.size()
<< "\n";
auto found =
registry.get("node-a");
if (found.has_value())
{
std::cout << "found node: "
<< found->node_id()
<< "\n";
}
registry.erase("node-b");
std::cout << "registry size after erase: "
<< registry.size()
<< "\n";
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Registry filter example
#include <iostream>
#include <softadastra/metadata/Metadata.hpp>
using namespace softadastra;
int main()
{
std::cout << "== METADATA REGISTRY FILTER EXAMPLE ==\n";
metadata::registry::MetadataRegistry registry;
auto foundation_node =
metadata::core::NodeMetadata::foundation(
"node-foundation",
"foundation-host",
"linux",
"1.0.0");
auto app_node =
metadata::core::NodeMetadata::minimal(
"node-app",
"app-host",
"linux",
"1.0.0");
app_node.capabilities.add(
metadata::types::CapabilityType::App);
registry.upsert(foundation_node);
registry.upsert(app_node);
const auto sync_nodes =
registry.with_capability(
metadata::types::CapabilityType::Sync);
const auto foundation_nodes =
registry.foundation_nodes();
const auto user_facing_nodes =
registry.user_facing_nodes();
std::cout << "registry size: "
<< registry.size()
<< "\n";
std::cout << "sync nodes: "
<< sync_nodes.size()
<< "\n";
std::cout << "foundation nodes: "
<< foundation_nodes.size()
<< "\n";
std::cout << "user-facing nodes: "
<< user_facing_nodes.size()
<< "\n";
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Metadata encoding
Metadata can be encoded for transport, storage, or diagnostics.
Encoding flow:
NodeMetadata
↓
MetadataEncoder
↓
payload bytes2
3
4
5
Decoding flow:
payload bytes
↓
MetadataDecoder
↓
NodeMetadata2
3
4
5
Invalid metadata payloads should fail clearly.
Encode decode example
#include <iostream>
#include <softadastra/metadata/Metadata.hpp>
using namespace softadastra;
int main()
{
std::cout << "== METADATA ENCODE DECODE DEMO ==\n";
auto original =
metadata::core::NodeMetadata::foundation(
"node-a",
metadata::utils::Hostname::get(),
metadata::utils::PlatformInfo::os_name(),
"1.0.0");
if (!original.is_valid())
{
std::cerr << "invalid original metadata\n";
return 1;
}
auto encoded =
metadata::encoding::MetadataEncoder::encode(original);
if (encoded.empty())
{
std::cerr << "failed to encode metadata\n";
return 1;
}
auto decoded =
metadata::encoding::MetadataDecoder::decode(encoded);
if (!decoded.has_value())
{
std::cerr << "failed to decode metadata\n";
return 1;
}
std::cout << "encoded size: "
<< encoded.size()
<< "\n";
std::cout << "decoded node id: "
<< decoded->node_id()
<< "\n";
std::cout << "decoded hostname: "
<< decoded->runtime.hostname
<< "\n";
std::cout << "decoded capabilities: "
<< decoded->capabilities.size()
<< "\n";
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
Encoding rule
Metadata encoding should be strict.
Bad:
invalid payload
↓
fake default metadata2
3
Better:
invalid payload
↓
decode failure
↓
registry unchanged2
3
4
5
Never register invalid metadata as a real node.
Custom metadata provider
A metadata provider can produce local metadata from a custom source.
This is useful when metadata must be generated by application logic or another runtime layer.
Custom provider example
#include <iostream>
#include <optional>
#include <softadastra/metadata/Metadata.hpp>
using namespace softadastra;
class DemoMetadataProvider final
: public metadata::backend::IMetadataProvider
{
public:
std::optional<metadata::core::NodeMetadata>
local_metadata() const override
{
return metadata_;
}
std::optional<metadata::core::NodeMetadata>
refresh_local_metadata() override
{
metadata_ =
metadata::core::NodeMetadata::foundation(
"provider-node",
"provider-host",
metadata::utils::PlatformInfo::os_name(),
"2.0.0");
metadata_->refresh_runtime();
return metadata_;
}
private:
std::optional<metadata::core::NodeMetadata> metadata_{};
};
int main()
{
std::cout << "== METADATA CUSTOM PROVIDER EXAMPLE ==\n";
DemoMetadataProvider provider;
auto metadata_snapshot =
provider.refresh_local_metadata();
if (!metadata_snapshot.has_value())
{
std::cerr << "provider failed to produce metadata\n";
return 1;
}
std::cout << "provider node id: "
<< metadata_snapshot->node_id()
<< "\n";
std::cout << "provider hostname: "
<< metadata_snapshot->runtime.hostname
<< "\n";
std::cout << "provider version: "
<< metadata_snapshot->runtime.version
<< "\n";
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
MetadataService
MetadataService is the higher-level metadata runtime service.
It can:
- start metadata service
- stop metadata service
- refresh local metadata
- return local metadata
- store known metadata
- return all known metadata
- integrate with discovery
The exact methods depend on the implementation.
Metadata service flow
A service flow can look like this:
MetadataOptions
↓
MetadataService
↓
start
↓
local_or_refresh
↓
NodeMetadata
↓
registry
↓
stop2
3
4
5
6
7
8
9
10
11
12
13
When integrated with discovery:
Discovery finds peer
↓
MetadataService can track or expose metadata2
3
The boundary should stay clear.
Discovery finds nodes.
Metadata describes nodes.
Metadata service example
#include <filesystem>
#include <iostream>
#include <softadastra/store/Store.hpp>
#include <softadastra/sync/Sync.hpp>
#include <softadastra/transport/Transport.hpp>
#include <softadastra/discovery/Discovery.hpp>
#include <softadastra/metadata/Metadata.hpp>
using namespace softadastra;
int main()
{
std::cout << "== METADATA SERVICE DEMO ==\n";
const std::string wal_path = "metadata_service_demo.wal";
std::filesystem::remove(wal_path);
store::engine::StoreEngine store{
store::core::StoreConfig::durable(wal_path)};
auto sync_config =
sync::core::SyncConfig::durable("metadata-node");
sync::core::SyncContext sync_context{
store,
sync_config};
sync::engine::SyncEngine sync_engine{
sync_context};
auto transport_config =
transport::core::TransportConfig::local(7300);
transport::core::TransportContext transport_context{
transport_config,
sync_engine};
transport::backend::TcpTransportBackend transport_backend{
transport_config};
transport::engine::TransportEngine transport_engine{
transport_context,
transport_backend};
if (!transport_engine.start())
{
std::cerr << "failed to start transport engine\n";
std::filesystem::remove(wal_path);
return 1;
}
auto discovery_config =
discovery::core::DiscoveryConfig::local(
"metadata-node",
9500,
7300);
discovery::core::DiscoveryContext discovery_context{
discovery_config,
transport_engine};
discovery::backend::UdpDiscoveryBackend discovery_backend{
discovery_config};
discovery::engine::DiscoveryEngine discovery_engine{
discovery_context,
discovery_backend};
if (!discovery_engine.start())
{
std::cerr << "failed to start discovery engine\n";
transport_engine.stop();
std::filesystem::remove(wal_path);
return 1;
}
auto metadata_options =
metadata::MetadataOptions::local(
"metadata-node",
"1.0.0");
metadata::MetadataService metadata_service{
metadata_options,
discovery_engine};
if (!metadata_service.start())
{
std::cerr << "failed to start metadata service\n";
discovery_engine.stop();
transport_engine.stop();
std::filesystem::remove(wal_path);
return 1;
}
auto local =
metadata_service.local_or_refresh();
if (local.has_value())
{
std::cout << "local metadata node id: "
<< local->node_id()
<< "\n";
std::cout << "hostname: "
<< local->runtime.hostname
<< "\n";
std::cout << "os: "
<< local->runtime.os_name
<< "\n";
std::cout << "capabilities: "
<< local->capabilities.size()
<< "\n";
}
std::cout << "registry entries: "
<< metadata_service.all().size()
<< "\n";
metadata_service.stop();
discovery_engine.stop();
transport_engine.stop();
std::filesystem::remove(wal_path);
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
Metadata and discovery
Discovery finds peers.
Metadata describes peers.
Relationship:
Discovery finds node-b
↓
Metadata can describe node-b
↓
CLI or SDK can show richer peer info2
3
4
5
Discovery should not own metadata behavior.
Metadata should not own discovery behavior.
They should integrate through explicit service boundaries.
Metadata and transport
Transport moves messages.
Metadata describes nodes.
A transport message may include node ids.
Metadata can later provide richer information about those node ids.
Relationship:
transport message from node-b
↓
metadata registry can describe node-b2
3
Transport should not depend on metadata to move basic messages.
Metadata and sync
Sync uses node identity.
Metadata describes node identity.
Example:
metadata node id = node-a
sync operation origin = node-a2
Sync can use metadata for diagnostics, but sync should not depend on metadata to apply basic local operations.
Metadata and store
Store contains application state.
Metadata describes the runtime node.
Example:
metadata.node_id = node-a
store["profile/name"] = Ada2
These are separate.
Do not store application values in metadata.
Do not store node identity as normal app data unless the application intentionally mirrors it.
Metadata and WAL
WAL records operation history.
Metadata describes nodes.
They are separate.
A diagnostic view may show:
- node id
- WAL path
- last sequence
But the generic metadata module should not own WAL writing.
Metadata and CLI
CLI can expose metadata through commands such as:
node infostatuspeers
Correct direction:
CLI command
↓
MetadataService
↓
NodeMetadata2
3
4
5
Wrong direction:
MetadataService
↓
CLI formatted output2
3
Metadata should return structured data.
CLI should format it.
Metadata and SDK
The SDK wraps metadata behind simpler methods.
C++ SDK:
client.refresh_node_info()
client.node_info()2
JavaScript SDK:
client.refreshNodeInfo()
client.nodeInfo()2
The SDK exposes a simplified NodeInfo object for application developers.
The engine metadata module remains lower-level.
Local-first behavior
Metadata should not be required for local store writes.
Correct behavior:
metadata unavailable
↓
store put can still work2
3
Metadata failure should affect diagnostics, node description, or peer visibility, not basic local state.
Offline behavior
Metadata is local.
It can work offline because it reads local runtime information:
- node id
- hostname
- OS
- version
- uptime
- capabilities
Remote metadata may be unavailable while offline.
That should not break local metadata.
Metadata failure behavior
Metadata can fail for normal reasons:
- invalid node id
- invalid version
- platform info unavailable
- hostname unavailable
- metadata decode failed
- provider failed
- service not started
Each failure should be visible.
Do not hide metadata failures behind generic messages.
Invalid node id
An empty node id should be rejected.
Bad:
""Good:
node-aThe error should be clear:
invalid metadata options: node id is emptyInvalid metadata payload
If decoding fails:
payload bytes
↓
decode failed
↓
return failure
↓
do not insert metadata into registry2
3
4
5
6
7
Invalid metadata should not become a fake node.
Provider failure
If a metadata provider cannot produce metadata:
refresh provider
↓
no metadata returned
↓
return failure or empty result2
3
4
5
The caller should decide how to handle it.
Service not started
If a service method requires a started service, the error should explain the lifecycle issue.
Example:
metadata service is not runningMetadata API reference
Main areas
| Area | Purpose |
|---|---|
core | Node metadata and capabilities |
types | Capability types and metadata types |
registry | Known node metadata registry |
encoding | Metadata encode and decode |
backend | Metadata providers |
utils | Hostname, platform, version utilities |
service | Metadata service |
Main types
| Type | Purpose |
|---|---|
MetadataOptions | User-facing metadata options |
NodeMetadata | Node identity and runtime info |
NodeCapabilities | Supported node capabilities |
CapabilityType | Capability enum |
MetadataRegistry | Stores known node metadata |
MetadataEncoder | Encodes metadata |
MetadataDecoder | Decodes metadata |
IMetadataProvider | Provider interface |
MetadataService | Higher-level metadata service |
Common methods
| Method | Purpose |
|---|---|
NodeMetadata::foundation(...) | Create foundation node metadata |
NodeMetadata::minimal(...) | Create minimal node metadata |
refresh_runtime() | Refresh runtime fields |
node_id() | Return node id |
label() | Return display label |
is_valid() | Validate metadata |
capabilities.add(...) | Add capability |
capabilities.has(...) | Check capability |
registry.upsert(...) | Insert or update metadata |
registry.get(...) | Get metadata by node id |
registry.with_capability(...) | Filter by capability |
MetadataEncoder::encode(...) | Encode metadata |
MetadataDecoder::decode(...) | Decode metadata |
Only document a method as stable when it exists in the current public API.
Examples
Current useful examples include:
metadata_minimal.cppmetadata_local_snapshot.cppmetadata_capabilities.cppmetadata_registry_demo.cppmetadata_registry_filter.cppmetadata_encode_decode_demo.cppmetadata_custom_provider.cppmetadata_service_demo.cpp
Recommended order:
metadata_minimal.cppmetadata_local_snapshot.cppmetadata_capabilities.cppmetadata_registry_demo.cppmetadata_registry_filter.cppmetadata_encode_decode_demo.cppmetadata_custom_provider.cppmetadata_service_demo.cpp
This order moves from local identity to registry, encoding, provider, and service integration.
Run examples
From the engine repository:
cd ~/softadastra/softadastraBuild:
vix buildOr with CMake:
cmake --preset dev-ninja
cmake --build --preset build-ninja2
Find binaries:
find build-ninja -type f -executableRun the relevant metadata example binary from the build output.
Testing metadata
Metadata tests should verify:
- valid metadata options
- invalid metadata options
- local metadata creation
- minimal metadata creation
- foundation metadata creation
- runtime refresh
- capability add
- capability has
- foundation capability check
- user-facing capability check
- registry upsert
- registry get
- registry erase
- registry filter by capability
- metadata encode
- metadata decode
- invalid payload decode failure
- custom provider refresh
- service start and stop
Good metadata test flow
Options test:
create local metadata options
validate options
convert to config
expect node id and version2
3
4
Capability test:
create NodeCapabilities
add Sync
expect has Sync
expect capability count = 12
3
4
Registry test:
create registry
upsert node-a metadata
get node-a
expect found
erase node-a
expect missing2
3
4
5
6
Codec test:
create NodeMetadata
encode metadata
decode payload
expect same node id2
3
4
Invalid decode test:
provide invalid bytes
decode
expect failure
registry unchanged2
3
4
Service test:
create service
start service
refresh local metadata
expect local node id
stop service2
3
4
5
Design rules
The metadata module should follow these rules:
- Describe nodes, not application data.
- Keep node identity explicit.
- Keep capabilities inspectable.
- Keep metadata encoding strict.
- Do not create fake metadata from invalid payloads.
- Do not make local writes depend on metadata.
- Keep discovery integration clean.
- Keep transport integration clean.
- Return explicit failures.
- Keep examples small and focused.
Common mistakes
Storing application data in metadata
Wrong:
metadata.profile_name = "Ada"Better:
store["profile/name"] = "Ada"Metadata describes the node.
Store contains app data.
Making metadata discover peers
Wrong:
MetadataService scans LAN for peersBetter:
DiscoveryService finds peers
MetadataService describes nodes2
Making metadata connect peers
Wrong:
MetadataService opens TCP connectionBetter:
TransportEngine connects peers
Metadata describes the peer2
Ignoring invalid metadata payloads
Wrong:
decode failed
↓
insert default node2
3
Better:
decode failed
↓
return failure
↓
registry unchanged2
3
4
5
Making metadata required for local writes
Wrong:
metadata refresh failed
↓
store put fails2
3
Better:
metadata refresh failed
↓
diagnostics unavailable
↓
store can still work2
3
4
5
Mixing CLI formatting into metadata
Wrong:
NodeMetadata prints terminal tablesBetter:
NodeMetadata returns data
CLI formats output2
Recommended usage pattern
Create local metadata options:
auto options =
metadata::MetadataOptions::local(
"node-a",
"0.1.0");
if (!options.is_valid())
{
return 1;
}2
3
4
5
6
7
8
9
Create a local metadata snapshot:
auto snapshot =
metadata::core::NodeMetadata::foundation(
"node-a",
metadata::utils::Hostname::get(),
metadata::utils::PlatformInfo::os_name(),
metadata::utils::VersionInfo::current());
if (!snapshot.is_valid())
{
return 1;
}
snapshot.refresh_runtime();2
3
4
5
6
7
8
9
10
11
12
13
Add capabilities:
snapshot.capabilities.add(
metadata::types::CapabilityType::Store);
snapshot.capabilities.add(
metadata::types::CapabilityType::Sync);2
3
4
5
Use a registry:
metadata::registry::MetadataRegistry registry;
registry.upsert(snapshot);
auto found = registry.get("node-a");
if (found.has_value())
{
std::cout << found->node_id() << "\n";
}2
3
4
5
6
7
8
9
10
Encode metadata:
auto encoded =
metadata::encoding::MetadataEncoder::encode(snapshot);
if (encoded.empty())
{
return 1;
}2
3
4
5
6
7
Decode metadata:
auto decoded =
metadata::encoding::MetadataDecoder::decode(encoded);
if (!decoded.has_value())
{
return 1;
}2
3
4
5
6
7
Summary
metadata is the node identity module of Softadastra Engine.
It provides:
NodeMetadataNodeCapabilitiesMetadataOptionsMetadataServiceMetadataRegistryMetadataEncoderMetadataDecoderPlatformInfoHostnameVersionInfo
The key idea is:
Metadata makes nodes understandable.It does not store application data, sync values, connect peers, or discover peers by itself.
Next step
Continue with CLI: