// Edge-control-plane schema, authored by ace-ng-edge team, reviewed
// + accepted by Aerodrome 2026-04-22.
//
// Corresponds to ace_edge.models.ReachabilityEvent (v0.32.0 shape).
// Published to: ace.edge.events.ot.reachability.{sanitized_device_ref}
//
// Cloud consumer guide: docs/aerodrome-ot-reachability-consumer.md.

syntax = "proto3";

package aerodrome.v1;

import "google/protobuf/timestamp.proto";

// Probe outcome. String values here match the edge-emitted JSON today
// (v0.23.0+); keep them stable so Aerodrome consumers parsing either
// wire format see the same set.
enum ReachabilityStatus {
  REACHABILITY_STATUS_UNSPECIFIED = 0;
  REACHABILITY_STATUS_OK = 1;             // device responded successfully
  REACHABILITY_STATUS_DEGRADED = 2;       // responded with partial/inconsistent results
  REACHABILITY_STATUS_DOWN = 3;           // timeout or protocol error
  REACHABILITY_STATUS_SUPPRESSED = 4;     // upstream parent failure masks this one
}

// Where an edge-emitted observation originated. Shared envelope reused
// by every ot.* / driver.* event payload so Aerodrome can correlate
// across feeds without per-event field drift.
//
// Not every field is populated on every event — they default to
// proto3 unset. Specifically:
//   - network_attachment_id / network_port_ref / network_number are
//     only populated when the driver is bound to a BacnetNetworkConfig
//     (v0.28.0+ deployments). Earlier edges emit unset — fall back to
//     transport-URI matching.
//   - canonical_device_ref is reserved for Aerodrome-minted logical
//     device IDs. The edge never mints; it echoes back whatever the
//     cloud sends down via registry config.
message ObservationIdentity {
  // Deployment provenance. Populated from ACE_EDGE_DEPLOYMENT_ID env.
  optional string edge_deployment_id = 1;
  // Owning site (business/location boundary). Populated from ACE_SITE_ID.
  optional string site_id = 2;
  // Physical/virtual host running the edge. Populated from
  // ACE_EDGE_HOST_ID. Edges <=v0.28.0 used the env var
  // ACE_GATEWAY_HOST_ID and emitted the field as gateway_host_id;
  // Aerodrome parsers should tolerate the rename.
  optional string edge_host_id = 3;

  // Field-bus protocol. "bacnet" today; reserved for future Modbus /
  // EtherNet-IP reachability emission.
  optional string protocol = 10;

  // Protocol-local device identifier (BACnet device_id). Also encoded
  // in device_ref — exposed as a first-class int so consumers don't
  // have to parse the URI.
  optional int32 device_id = 11;

  // Durable logical-attachment identity — the cloud correlation key.
  // Survives DHCP, operator renames of the network id, and hardware
  // replacement. UUID serialized as string (matches TsIntervalMessage).
  optional string network_attachment_id = 12;

  // Runtime provenance: the concrete BACnet NetworkPortObject UUID.
  // 1:1 with network_attachment_id in v1; may diverge when a single
  // attachment spans multiple runtime ports.
  optional string network_port_ref = 13;

  // BACnet internetwork number. 0 means "local network" per spec —
  // NOT "unknown". Consumers that need to distinguish must also check
  // whether the field is set.
  optional int32 network_number = 14;

  // Cloud-assigned logical device id. Edge never mints; it echoes back
  // whatever Aerodrome pushes down via registry config. Unset on
  // edge-initiated events — included here for shape stability.
  optional string canonical_device_ref = 20;
}

// Reachability probe event published on every successful or failed
// check. Both ace-driver (piggybacking on real reads) and
// ace-ot-monitor (synthetic probes) emit the same shape — the source
// field distinguishes. Cloud consumers deduplicate / order by
// check_ts, not by arrival order.
message ReachabilityEvent {
  // Transport locator — URI like "bacnet://10.1.0.50:47808/device/100".
  // Ephemeral on DHCP networks; Aerodrome correlates on the fields in
  // identity, not this string.
  string device_ref = 1;

  // UTC moment the probe completed. Ordering authority.
  google.protobuf.Timestamp check_ts = 2;

  // Which probe mechanism produced this evidence. Current values:
  //   driver_poll, driver_property_read (ace-driver)
  //   monitor_read_point, monitor_read_identity (ace-ot-monitor)
  //   bacnet_bbmd_health, bacnet_router_health (infrastructure probes, v0.42.0+)
  string method = 3;

  // Which agent wrote the record. "ace-driver" | "ace-ot-monitor".
  string source = 4;

  ReachabilityStatus status = 5;

  // Round-trip time in milliseconds. Populated even on timeouts
  // (value is the timeout duration). Unset only when no request was
  // actually attempted.
  optional double rtt_ms = 6;

  // Incremented on every non-OK result; reset to 0 on OK.
  int32 consecutive_failures = 7;

  // Most recent OK timestamp. Carried through failures so consumers
  // can render "last seen" ages.
  optional google.protobuf.Timestamp last_success_ts = 8;

  // Upstream router/BBMD of this device, if known. URI shape like
  // "bacnet://10.1.0.1:47808/router".
  optional string parent_ref = 9;

  // Parent device_ref whose failure masks this one. Set only when
  // status == SUPPRESSED.
  optional string suppressed_by = 10;

  // Free-form context — error message, "rpc_timeout", "success=3 fail=5".
  string detail = 11;

  // Observation-identity envelope (see message above).
  ObservationIdentity identity = 20;
}

// Canonical current-state record — the shape in edge.ot.reachability
// KV. Wraps ReachabilityEvent so the 11 shared fields don't drift
// between the two messages; record-only aggregate fields land
// alongside `event` as they're added.
//
// Per Aerodrome review (2026-04-22): the wrapper pattern gives the
// same schema-evolution independence the original two-message shape
// aimed for, without the maintenance burden of keeping every field
// in sync by hand.
message ReachabilityRecord {
  ReachabilityEvent event = 1;
  // Record-only aggregate fields land here. Reserved 2-19 for future
  // additions without touching event-shape numbering:
  //   - rolling success_rate over a window
  //   - avg/p95 RTT aggregates
  //   - parent-subtree health summary
}
