// strands-robots mesh ACL - operator template (role-scoped subscribe).
//
// Drop this at $STRANDS_MESH_ACL_FILE and edit the ENUMERATED CNs
// below to match the cert Common Names of every robot and operator
// in your fleet.
//
// SUBSCRIBE SCOPING
// -----------------
// This template is deny-by-default and scopes the subscribe surface by
// ROLE rather than handing every authenticated peer a blanket
// `declare_subscriber` on `**`:
//
//   * robot_peer  - may subscribe only to operationally low-impact,
//                   fleet-shared topic CLASSES (presence, broadcast,
//                   safety) plus its OWN command/response. It cannot
//                   declare a subscriber on another robot's cmd / state
//                   / camera / input topics. This mirrors the IoT
//                   transport's per-Thing subscribe isolation
//                   (provision.py AllowOwnSubscriptions / AllowReceiveScoped).
//   * operator_peer - retains broad observe privilege (`**`), because
//                   operators legitimately monitor the whole fleet. This
//                   is an INTENTIONAL operator-role grant, called out
//                   explicitly below so it is never mistaken for a robot
//                   grant.
//
// RESIDUAL LIMITATION (read before relying on this for hard isolation)
// --------------------------------------------------------------------
// Zenoh 1.x `key_exprs` cannot interpolate the connecting peer's cert
// CN, so a single `robot_peer` subject scoped by topic-CLASS still lets
// one robot subscribe to another robot's OWN-class topics if they share
// the topic shape (there is no `${cn}` substitution like IoT's
// `${iot:Connection.Thing.ThingName}`). For STRICT per-peer isolation
// equivalent to the IoT policy, enumerate one rule + one subject per
// robot CN - see examples/mesh/mesh_acl_strict_per_peer.json5. When in doubt,
// prefer the IoT transport for fleets that require hard cross-peer
// subscribe denial.
//
// Zenoh 1.x quirks (verified against live sessions in
// tests/mesh/test_zenoh_transport_security.py):
//
//  1. `enabled: true` is required. Without it the entire access_control
//     block is a silent no-op.
//
//  2. `cert_common_names` matches LITERAL CNs only - `"robot-*"` and
//     `"robot-.*"` both match nothing. Enumerate every peer by exact
//     CN below; there is no glob fallback.
//
//  3. Subject `interfaces` is OPTIONAL per Zenoh's schema
//     (Option<NEVec<...>>; an absent field maps to
//     SubjectProperty::Wildcard, i.e. matches every link). We omit
//     it below so this template works on any host regardless of NIC
//     name (`eth0` / `enp0s3` / `cni0` / `wg0` / ...). Add an
//     `interfaces: ["lo", "eth0", ...]` line under a subject if you
//     want to additionally scope it to a NIC subset.
//
//  4. `key_exprs` see the user-side key (the Zenoh `namespace`
//     prefix is stripped before matching). Use `**/cmd` style
//     globs, NOT `<namespace>/*/cmd`.
//
//  5. `put` rules live in `ingress` (where the publishing peer's
//     cert CN is known to the receiver). `declare_subscriber` lives
//     in `egress` (the declare flows from subscriber to publisher).
{
  enabled: true,
  default_permission: "deny",

  rules: [
    {
      id: "robot_publish_telemetry",
      messages: ["put"],
      flows: ["ingress"],
      permission: "allow",
      key_exprs: [
        "**/presence",
        "**/state/**",
        "**/health",
        "**/pose",
        "**/imu",
        "**/odom",
        "**/lidar/**",
        "**/hand/**",
        "**/camera/**",
        "**/response/**",
      ],
    },
    {
      id: "operator_publish_cmds",
      messages: ["put"],
      flows: ["ingress"],
      permission: "allow",
      key_exprs: [
        "**/cmd",
        "**/broadcast",
        "**/safety/**",
      ],
    },
    {
      // Robot subscribe is intentionally NARROW: only fleet-shared,
      // low-impact topic classes plus the robot's own cmd/response.
      // It deliberately does NOT include `**`, `**/state/**`,
      // `**/camera/**`, or another peer's `**/cmd` - a robot has no
      // operational need to observe another robot's control or sensor
      // streams. Operators that need the robot to read a specific peer
      // should route through `tell`/`send` RPC, not a raw subscribe.
      id: "robot_subscribe_scoped",
      messages: ["declare_subscriber"],
      flows: ["egress"],
      permission: "allow",
      key_exprs: [
        "**/presence",
        "**/broadcast",
        "**/safety/**",
        "**/cmd",
        "**/response/**",
      ],
    },
    {
      // INTENTIONAL operator-role privilege: operators monitor the whole
      // fleet, so they keep a broad subscribe. Scoped to the
      // operator_peer subject only (see policies below) - robots never
      // reference this rule.
      id: "operator_subscribe_all",
      messages: ["declare_subscriber"],
      flows: ["egress"],
      permission: "allow",
      key_exprs: ["**"],
    },
    {
      // Dashboard subscribe is OBSERVE-ONLY: presence, health, safety
      // events, camera frames, state and rollout streams - everything a
      // read-only fleet dashboard (examples/fleet/dashboard.py, epic
      // #2179) renders. It deliberately does NOT include `**/cmd`,
      // `**/broadcast` or `**/response/**`: a dashboard has no
      // operational need to observe the command/RPC plane, and this
      // role must never be a quiet path to it.
      id: "dashboard_subscribe_observe",
      messages: ["declare_subscriber"],
      flows: ["egress"],
      permission: "allow",
      key_exprs: [
        "**/presence",
        "**/health",
        "**/safety/**",
        "**/camera/**",
        "**/state/**",
        "**/stream",
        "**/pose",
      ],
    },
    {
      // The dashboard's ONLY publish grant: announcing its own presence
      // so operators can see it on the mesh. Zenoh 1.x key_exprs cannot
      // interpolate the peer's cert CN (see the header), so this is the
      // presence topic CLASS - the role still cannot publish commands,
      // broadcasts or safety traffic, which is the boundary that makes
      // it read-only where it matters.
      id: "dashboard_publish_presence",
      messages: ["put"],
      flows: ["ingress"],
      permission: "allow",
      key_exprs: [
        "**/presence",
      ],
    },
  ],

  subjects: [
    {
      id: "robot_peer",
      // ENUMERATE every robot's exact cert CN here.
      cert_common_names: [
        "robot-a",
        "robot-b",
        // Add more as you provision...
      ],
    },
    {
      id: "operator_peer",
      cert_common_names: [
        "op-1",
        "op-2",
        // Add more operators...
      ],
    },
    {
      id: "dashboard_peer",
      // Read-only observers: fleet dashboards, wall displays, replay
      // recorders. ENUMERATE each one's exact cert CN.
      cert_common_names: [
        "dashboard-1",
      ],
    },
  ],

  policies: [
    {
      // Robots: publish telemetry + scoped subscribe. NO blanket `**`.
      rules: ["robot_publish_telemetry", "robot_subscribe_scoped"],
      subjects: ["robot_peer"],
    },
    {
      // Operators: publish commands + broad observe (intentional).
      rules: ["operator_publish_cmds", "operator_subscribe_all"],
      subjects: ["operator_peer"],
    },
    {
      // Dashboards: observe-only. May watch the fleet and announce their
      // own presence; may NOT publish commands, broadcasts or safety
      // traffic - a compromised dashboard cert can watch, not actuate.
      rules: ["dashboard_subscribe_observe", "dashboard_publish_presence"],
      subjects: ["dashboard_peer"],
    },
  ],
}
