// Edge-control-plane schema, authored by ace-ng-edge team, reviewed
// + accepted by Aerodrome 2026-04-22.
//
// Three edge-emitted event families that aren't reachability:
//   - DriverConfigError (v0.33.0)   — ace.edge.events.driver.config_error
//   - TopologyIntegrityIssue (v0.41.0) — ace.edge.events.ot.integrity.{type}
//   - ImpactTransitionEvent (v0.40.0)  — ace.edge.events.ot.impact.{suppressed,cleared}.{device_id}

syntax = "proto3";

package aerodrome.v1;

import "google/protobuf/timestamp.proto";
// Re-use ObservationIdentity from reachability.proto. In the final
// schema the Aerodrome team can choose to factor it into its own file
// (e.g. identity.proto) — left inline here to keep the draft legible.
import "aerodrome/v1/reachability.proto";

// Operator-visible config error surfaced by ace-driver. Emitted when a
// driver config can't be loaded — the device is skipped, the edge keeps
// running, and Aerodrome / operator UIs render the error so the
// reference can be fixed. Per multi-network-bacnet.md §3.2, silent
// fallback is not acceptable — the event carries enough context to
// pinpoint the broken config.
//
// Current reason codes (extend as new validation paths land):
//   "unknown_network" — driver config references a network_id that has
//                       no matching BacnetNetworkConfig.
message DriverConfigError {
  // KV key that failed validation. Shape: "driver.bacnet.{device_id}".
  string driver_key = 1;

  // Protocol-local id if the config parsed far enough to extract one.
  optional int32 device_id = 2;

  // Field-bus protocol — "bacnet" today.
  string protocol = 3;

  // Short machine-readable code. Stable across releases; operators /
  // dashboards can group by this.
  string reason = 4;

  // Human-readable context.
  string detail = 5;

  // Populated for unknown-network errors; unset for other reason types.
  optional string network_id = 6;

  // When the error was produced.
  google.protobuf.Timestamp ts = 7;

  // Provenance envelope. Reuses the same shape as ReachabilityEvent
  // so correlators can key on edge_deployment_id identically across
  // feeds.
  ObservationIdentity identity = 20;
}

// Topology-integrity finding emitted by the scanner after every
// topology probe. Pure-analysis output over the BDT / router data
// already collected — no new probes, no new protocol traffic. See
// docs/plans/ot-network-monitoring.md Phase 3.
//
// Current type codes (open set — new checks can add new types
// additively):
//   "bdt_missing_entry"      — one BBMD's BDT doesn't list a peer BBMD
//                              that was discovered in the topology,
//                              implying a one-way broadcast gap.
//   "duplicate_router_claim" — two or more routers advertise the same
//                              non-zero network_number.
message TopologyIntegrityIssue {
  // Finding category. See type codes above.
  string type = 1;

  // "info" | "warn" | "error". All currently-emitted findings are warn.
  string severity = 2;

  // When the check ran.
  google.protobuf.Timestamp check_ts = 3;

  // Human-readable summary. Aerodrome / TUI can render this verbatim.
  string detail = 4;

  // --- Per-type context. Unset on types that don't use them. ---

  // Set on bdt_missing_entry: the BBMD whose BDT is incomplete.
  // Format: "ip:port".
  optional string bbmd = 5;

  // Set on bdt_missing_entry: the peer BBMD that's missing from `bbmd`'s BDT.
  // Format: "ip:port".
  optional string peer = 6;

  // Set on duplicate_router_claim: the BACnet internetwork number
  // with conflicting claims.
  optional int32 network_number = 7;

  // Set on duplicate_router_claim: the router addresses that all
  // advertise `network_number`. Sorted lexicographically on the edge
  // for deterministic output.
  repeated string routers = 8;

  ObservationIdentity identity = 20;
}

// Topology-attributed suppression transition. Emitted exactly once per
// state flip (down ↔ suppressed) for a given child device, separate
// from the reachability stream so consumers can subscribe to root-cause
// events without wading through every child's full history. The
// reachability stream still emits a `suppressed`-status record on the
// same probe cycle — the two feeds complement.
//
// Subject encodes the transition:
//   ace.edge.events.ot.impact.suppressed.{device_id}
//   ace.edge.events.ot.impact.cleared.{device_id}
message ImpactTransitionEvent {
  // Kind of transition.
  enum Transition {
    TRANSITION_UNSPECIFIED = 0;
    TRANSITION_SUPPRESSED = 1;  // status flipped to suppressed
    TRANSITION_CLEARED = 2;     // device is no longer suppressed
  }

  Transition transition = 1;

  // Transport locator of the child whose state flipped.
  string device_ref = 2;

  // Protocol-local device_id, for subject-matching convenience.
  int32 device_id = 3;

  // Upstream router/BBMD device_ref. Always set on suppressed
  // transitions (suppression requires a parent); may be set on cleared
  // transitions for continuity but the parent may by then be OK.
  optional string parent_ref = 4;

  // device_ref of the parent that caused the suppression. On cleared
  // transitions this is unset — the parent is no longer a cause.
  optional string suppressed_by = 5;

  // When the transition was observed.
  google.protobuf.Timestamp check_ts = 6;

  // Why the child was suppressed. Unset on cleared transitions.
  // Current values: "parent_down", "parent_degraded".
  optional string reason = 7;

  // Current status at the moment of the transition. For suppressed →
  // SUPPRESSED; for cleared → whatever the next probe found (usually
  // OK, possibly DOWN if the child is still failing for a reason
  // unrelated to the parent).
  ReachabilityStatus status = 8;

  ObservationIdentity identity = 20;
}
