Files
meshcore-analyzer/proto/node.proto
Kpa-clawbot cdcaa476f2 rename: MeshCore Analyzer → CoreScope (Phase 1 — backend + infra)
Rename product branding, binary names, Docker images, container names,
Go modules, proto go_package, CI, manage.sh, and documentation.

Preserved (backward compat):
- meshcore.db database filename
- meshcore-data / meshcore-staging-data directory paths
- MQTT topics (meshcore/#, meshcore/+/+/packets, etc.)
- proto package namespace (meshcore.v1)
- localStorage keys

Changes by category:
- Go modules: github.com/corescope/{server,ingestor}
- Binaries: corescope-server, corescope-ingestor
- Docker images: corescope:latest, corescope-go:latest
- Containers: corescope-prod, corescope-staging, corescope-staging-go
- Supervisord programs: corescope, corescope-server, corescope-ingestor
- Branding: siteName, heroTitle, startup logs, fallback HTML
- Proto go_package: github.com/corescope/proto/v1
- CI: container refs, deploy path
- Docs: 8 markdown files updated

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-28 14:08:15 -07:00

369 lines
15 KiB
Protocol Buffer
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
syntax = "proto3";
package meshcore.v1;
option go_package = "github.com/corescope/proto/v1";
import "common.proto";
import "packet.proto";
// ─── Core Node Type ────────────────────────────────────────────────────────────
// A mesh network node. Defined ONCE, reused across all node-related endpoints.
// Fields that are absent/inapplicable remain at proto3 default (zero) or unset (optional).
message Node {
// 64-character hex public key — unique node identifier.
string public_key = 1 [json_name = "public_key"];
// Display name (null if never advertised).
optional string name = 2;
// Node role: "repeater", "room", "companion", "sensor".
string role = 3;
// GPS latitude (null if no fix or unknown).
optional double lat = 4;
// GPS longitude (null if no fix or unknown).
optional double lon = 5;
// Last advert/upsert timestamp from DB (ISO 8601).
string last_seen = 6 [json_name = "last_seen"];
// When this node was first observed (ISO 8601).
string first_seen = 7 [json_name = "first_seen"];
// Total advertisement packets received.
int32 advert_count = 8 [json_name = "advert_count"];
// Latest observed hash size (13 bytes). Null if unknown.
optional int32 hash_size = 9 [json_name = "hash_size"];
// True if the node has been seen with different hash sizes.
bool hash_size_inconsistent = 10 [json_name = "hash_size_inconsistent"];
// All unique hash sizes seen (empty if only one or unknown).
repeated int32 hash_sizes_seen = 11 [json_name = "hash_sizes_seen"];
// Most recent timestamp from in-memory packets (ISO 8601).
// More current than last_seen; absent if no in-memory data.
optional string last_heard = 12 [json_name = "last_heard"];
}
// ─── Observer Stats (per-node context) ─────────────────────────────────────────
// How a specific observer sees a specific node.
// Used in bulk-health, node health, and node analytics observer coverage.
message NodeObserverStats {
// Observer device identifier.
string observer_id = 1 [json_name = "observer_id"];
// Observer display name.
optional string observer_name = 2 [json_name = "observer_name"];
// Packets from this node seen by this observer.
int32 packet_count = 3 [json_name = "packetCount"];
// Average SNR for packets from this node.
optional double avg_snr = 4 [json_name = "avgSnr"];
// Average RSSI for packets from this node.
optional double avg_rssi = 5 [json_name = "avgRssi"];
// IATA region code of observer (present in node-health, absent in bulk-health).
optional string iata = 6;
// First time this observer saw this node (ISO 8601, present in analytics).
optional string first_seen = 7 [json_name = "firstSeen"];
// Last time this observer saw this node (ISO 8601, present in analytics).
optional string last_seen = 8 [json_name = "lastSeen"];
}
// ─── Node Stats ────────────────────────────────────────────────────────────────
// Aggregate packet statistics for a node.
// Used in bulk-health entries and node health responses.
message NodeStats {
// Unique transmissions involving this node.
int32 total_transmissions = 1 [json_name = "totalTransmissions"];
// Total observations (may exceed transmissions due to multi-observer).
int32 total_observations = 2 [json_name = "totalObservations"];
// Same as totalTransmissions (backward compat alias).
int32 total_packets = 3 [json_name = "totalPackets"];
// Transmissions in the last 24 hours.
int32 packets_today = 4 [json_name = "packetsToday"];
// Average SNR across all observations.
optional double avg_snr = 5 [json_name = "avgSnr"];
// Most recent packet timestamp (ISO 8601).
optional string last_heard = 6 [json_name = "lastHeard"];
// Average hop count (rounded integer, present in node health).
optional double avg_hops = 7 [json_name = "avgHops"];
}
// ─── API Responses ─────────────────────────────────────────────────────────────
// GET /api/nodes — paginated node list.
message NodeListResponse {
repeated Node nodes = 1;
// Total matching count before pagination.
int32 total = 2;
// Global role counts (not filtered by current query).
RoleCounts counts = 3;
}
// GET /api/nodes/search — quick search for autocomplete.
message NodeSearchResponse {
// Matching nodes (subset of Node fields populated).
repeated Node nodes = 1;
}
// GET /api/nodes/bulk-health — bulk health summary for analytics dashboard.
// NOTE: The API returns a bare JSON array with flat node fields at top level.
message BulkHealthEntry {
// Node public key (flat, not nested in a sub-message).
string public_key = 1 [json_name = "public_key"];
// Node display name.
optional string name = 2;
// Node role: "repeater", "room", "companion", "sensor".
optional string role = 3;
// GPS latitude (null if unknown).
optional double lat = 4;
// GPS longitude (null if unknown).
optional double lon = 5;
// Aggregate packet stats.
NodeStats stats = 6;
// Per-observer signal quality.
repeated NodeObserverStats observers = 7;
}
// Wrapper for the bulk-health array response.
message BulkHealthResponse {
repeated BulkHealthEntry entries = 1;
}
// GET /api/nodes/network-status — aggregate health status counts.
message NetworkStatusResponse {
// Total nodes considered.
int32 total = 1;
// Nodes within degradedMs threshold.
int32 active = 2;
// Nodes between degradedMs and silentMs.
int32 degraded = 3;
// Nodes beyond silentMs.
int32 silent = 4;
// Per-role counts (may include "unknown" key).
map<string, int32> role_counts = 5 [json_name = "roleCounts"];
}
// GET /api/nodes/:pubkey — node detail page.
message NodeDetailResponse {
// Full node record.
Node node = 1;
// Last 20 packets involving this node, newest first.
repeated Transmission recent_adverts = 2 [json_name = "recentAdverts"];
}
// GET /api/nodes/:pubkey/health — detailed health for one node.
message NodeHealthResponse {
// Full node record.
Node node = 1;
// Per-observer signal stats.
repeated NodeObserverStats observers = 2;
// Aggregate packet statistics.
NodeStats stats = 3;
// Last 20 packets (observations stripped, observation_count added).
repeated Transmission recent_packets = 4 [json_name = "recentPackets"];
}
// ─── Path Analysis ─────────────────────────────────────────────────────────────
// Single hop in a resolved path.
message PathHop {
// Raw hex hop prefix.
string prefix = 1;
// Resolved node name.
string name = 2;
// Full public key (null if unresolved).
optional string pubkey = 3;
// GPS latitude (null if unknown).
optional double lat = 4;
// GPS longitude (null if unknown).
optional double lon = 5;
}
// A unique path signature with usage stats.
message PathEntry {
// Ordered hops in this path.
repeated PathHop hops = 1;
// Number of times this path was seen.
int32 count = 2;
// Most recent usage (ISO 8601).
optional string last_seen = 3 [json_name = "lastSeen"];
// Hash of a sample packet using this path.
string sample_hash = 4 [json_name = "sampleHash"];
}
// GET /api/nodes/:pubkey/paths — path analysis for a node.
message NodePathsResponse {
// Lightweight node identification.
Node node = 1;
// All unique paths containing this node.
repeated PathEntry paths = 2;
// Number of unique path signatures.
int32 total_paths = 3 [json_name = "totalPaths"];
// Total transmissions with this node in path.
int32 total_transmissions = 4 [json_name = "totalTransmissions"];
}
// ─── Node Analytics ────────────────────────────────────────────────────────────
// Time range for analytics query.
message TimeRange {
// Start of range (ISO 8601).
string from = 1;
// End of range (ISO 8601).
string to = 2;
// Number of days in range.
int32 days = 3;
}
// SNR trend data point.
message SnrTrendEntry {
// Observation timestamp (ISO 8601).
string timestamp = 1;
// Signal-to-noise ratio (dB).
double snr = 2;
// Received signal strength (dBm).
optional double rssi = 3;
// Observer that recorded this data point.
optional string observer_id = 4 [json_name = "observer_id"];
optional string observer_name = 5 [json_name = "observer_name"];
}
// Payload type breakdown entry.
message PayloadTypeCount {
// Payload type number.
int32 payload_type = 1 [json_name = "payload_type"];
// Number of packets of this type.
int32 count = 2;
}
// Hop distribution entry (e.g. "0", "1", "4+").
message HopDistEntry {
// Hop count label.
string hops = 1;
// Number of packets at this hop distance.
int32 count = 2;
}
// Peer interaction summary.
message PeerInteraction {
// Peer node public key.
string peer_key = 1 [json_name = "peer_key"];
// Peer node display name.
string peer_name = 2 [json_name = "peer_name"];
// Number of messages exchanged.
int32 message_count = 3 [json_name = "messageCount"];
// Most recent interaction (ISO 8601).
string last_contact = 4 [json_name = "lastContact"];
}
// Uptime heatmap cell (day-of-week × hour-of-day).
message HeatmapCell {
// Day of week (0=Sunday).
int32 day_of_week = 1 [json_name = "dayOfWeek"];
// Hour of day (023 UTC).
int32 hour = 2;
// Packet count in this cell.
int32 count = 3;
}
// Computed analytics statistics for a node.
message ComputedNodeStats {
// Availability percentage (0100).
double availability_pct = 1 [json_name = "availabilityPct"];
// Longest silence gap in milliseconds.
double longest_silence_ms = 2 [json_name = "longestSilenceMs"];
// When the longest silence started (ISO 8601).
optional string longest_silence_start = 3 [json_name = "longestSilenceStart"];
// Overall signal grade: "A", "A-", "B+", "B", "C", "D".
string signal_grade = 4 [json_name = "signalGrade"];
// Mean SNR across all observations.
double snr_mean = 5 [json_name = "snrMean"];
// SNR standard deviation.
double snr_std_dev = 6 [json_name = "snrStdDev"];
// Percentage of packets with >1 hop.
double relay_pct = 7 [json_name = "relayPct"];
// Total packets in analytics window.
int32 total_packets = 8 [json_name = "totalPackets"];
// Unique observers that saw this node.
int32 unique_observers = 9 [json_name = "uniqueObservers"];
// Unique peer nodes interacted with.
int32 unique_peers = 10 [json_name = "uniquePeers"];
// Average packets per day.
double avg_packets_per_day = 11 [json_name = "avgPacketsPerDay"];
}
// GET /api/nodes/:pubkey/analytics — per-node analytics.
message NodeAnalyticsResponse {
// Full node record.
Node node = 1;
// Query time range.
TimeRange time_range = 2 [json_name = "timeRange"];
// Hourly activity buckets.
repeated TimeBucket activity_timeline = 3 [json_name = "activityTimeline"];
// SNR over time with observer attribution.
repeated SnrTrendEntry snr_trend = 4 [json_name = "snrTrend"];
// Packet count by payload type.
repeated PayloadTypeCount packet_type_breakdown = 5 [json_name = "packetTypeBreakdown"];
// Per-observer coverage with signal stats and time range.
repeated NodeObserverStats observer_coverage = 6 [json_name = "observerCoverage"];
// Distribution of hop counts.
repeated HopDistEntry hop_distribution = 7 [json_name = "hopDistribution"];
// Peer interaction summaries.
repeated PeerInteraction peer_interactions = 8 [json_name = "peerInteractions"];
// Day×hour activity heatmap.
repeated HeatmapCell uptime_heatmap = 9 [json_name = "uptimeHeatmap"];
// Derived statistics.
ComputedNodeStats computed_stats = 10 [json_name = "computedStats"];
}
// ─── Hop Resolution ────────────────────────────────────────────────────────────
// Candidate node for an ambiguous hop prefix.
message HopCandidate {
// Node display name.
string name = 1;
// Node public key.
string pubkey = 2;
// GPS latitude (null if unknown).
optional double lat = 3;
// GPS longitude (null if unknown).
optional double lon = 4;
// Whether this candidate is in the regional filter set.
bool regional = 5;
// How this candidate was selected: "geo", "observer", etc.
string filter_method = 6 [json_name = "filterMethod"];
// Distance in km from origin (null if no coordinates).
optional double dist_km = 7 [json_name = "distKm"];
}
// Resolution result for a single hop prefix.
message HopResolution {
// Resolved node name (null if unresolvable).
optional string name = 1;
// Resolved node public key (null if unresolvable).
optional string pubkey = 2;
// True if multiple candidates matched.
optional bool ambiguous = 3;
// True if resolution failed sanity checks.
optional bool unreliable = 4;
// Candidate nodes that matched.
repeated HopCandidate candidates = 5;
// Conflicting candidates (different from chosen).
repeated HopCandidate conflicts = 6;
// True if fell back to global (non-regional) resolution.
optional bool global_fallback = 7 [json_name = "globalFallback"];
// Method used for filtering: "geo", "observer".
optional string filter_method = 8 [json_name = "filterMethod"];
// Hop hash size in bytes (for ambiguous entries).
optional int32 hop_bytes = 9 [json_name = "hopBytes"];
// Total global candidates before filtering.
optional int32 total_global = 10 [json_name = "totalGlobal"];
// Total regional candidates after filtering.
optional int32 total_regional = 11 [json_name = "totalRegional"];
// All filter methods attempted.
repeated string filter_methods = 12 [json_name = "filterMethods"];
}
// GET /api/resolve-hops — resolve hop prefixes to node identities.
message ResolveHopsResponse {
// Map of hop prefix → resolution result.
map<string, HopResolution> resolved = 1;
// Regional context used for resolution (null if no region).
optional string region = 2;
}