﻿<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="rfc2629.xslt" ?>
<!DOCTYPE rfc [
  <!ENTITY nbsp    "&#160;">
  <!ENTITY zwsp   "&#8203;">
  <!ENTITY nbhy   "&#8209;">
  <!ENTITY wj     "&#8288;">
]>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude"
     version="3"
     ipr="trust200902"
     docName="draft-melegassi-ippm-mvps-bundle-00"
     category="info"
     submissionType="independent"
     xml:lang="en"
     tocInclude="true"
     tocDepth="3"
     symRefs="true"
     sortRefs="true">

  <front>
    <title abbrev="MVPS Bundle">
      Multi-Vantage Path Snapshot (MVPS): A Canonical Bundle
      Format for Coordinated Traceroute Measurements
    </title>
    <seriesInfo name="Internet-Draft"
                value="draft-melegassi-ippm-mvps-bundle-00"/>

    <author fullname="Leonardo Melegassi" initials="L." surname="Melegassi">
      <organization>Catellix</organization>
      <address>
        <postal>
          <country>BR</country>
        </postal>
        <email>melegassi@catellix.com</email>
        <uri>https://www.catellix.com</uri>
      </address>
    </author>

    <date year="2026" month="May" day="17"/>

    <!--
      No <area> or <workgroup> element: this is an individual
      submission and has not been adopted by any IETF working group.
      The author's intended target for adoption discussion is IPPM,
      but that is an intent, not a status.
    -->

    <keyword>multi-vantage</keyword>
    <keyword>traceroute</keyword>
    <keyword>path measurement</keyword>
    <keyword>bundle</keyword>
    <keyword>YANG</keyword>
    <keyword>JSON</keyword>

    <abstract>
      <t>
        This document specifies the Multi-Vantage Path Snapshot
        (MVPS) bundle format, a focused envelope for traceroute
        observations collected in coordination from two or more
        network vantages towards a common destination.  MVPS defines
        a JSON serialization, a YANG 1.1 module, and a deterministic
        path-fingerprint algorithm enabling bit-reproducible auditing
        and cross-implementation interoperability.
      </t>
      <t>
        MVPS is intentionally minimal in scope: it specifies a wire
        format and the algorithms required to produce it
        deterministically.  Analytical metrics derived from MVPS
        bundles are out of scope.
      </t>
      <t>
        MVPS complements the AURA architecture defined by
        RFC 9198, which specifies a measurement architecture but
        does not normatively specify the result format. MVPS is
        intended as one possible result format for AURA-style
        coordinated measurements.  MVPS is also positioned
        relative to existing single-operator reporting formats
        (RIPE Atlas measurement JSON, CAIDA warts), which are not
        standardised and which do not provide a deterministic
        cross-implementation path identity.
      </t>
    </abstract>
  </front>

  <middle>

    <section anchor="intro">
      <name>Introduction</name>

      <t>
        Several existing systems coordinate traceroute measurements
        from distributed vantages towards a common target.  Examples
        include RIPE Atlas, CAIDA Ark, ThousandEyes, and various
        operator-internal collection frameworks.  These systems
        produce per-vantage observations whose joint analysis enables
        cross-vantage consistency checks (for example, applying a
        speed-of-light feasibility bound to pairs of observations of
        the same hop) and topology comparison (for example,
        quantifying path divergence between vantages).
      </t>

      <t>
        At the time of writing, no widely deployed envelope provides
        all of the following properties simultaneously:
      </t>

      <ul spacing="normal">
        <li>
          A bit-reproducible canonical serialization that allows two
          independent implementations to produce identical artefacts
          from identical inputs.
        </li>
        <li>
          A deterministic path-fingerprint allowing inexpensive path
          identity comparison and change detection across rounds.
        </li>
        <li>
          An explicit coordination-window declaration with a bounded
          clock-skew uncertainty.
        </li>
        <li>
          A YANG module suitable for use in network management
          contexts, with a sibling JSON serialization aligned via
          <xref target="RFC7951"/>.
        </li>
      </ul>

      <t>
        This document specifies such an envelope: the Multi-Vantage
        Path Snapshot (MVPS) bundle format.
      </t>

      <section anchor="scope">
        <name>Scope and Non-Goals</name>

        <t>In scope:</t>
        <ul spacing="normal">
          <li>Bundle serialization (JSON and YANG, aligned via RFC 7951).</li>
          <li>Deterministic path-fingerprint algorithm.</li>
          <li>Coordination-window semantics with explicit uncertainty.</li>
          <li>Conformance test vectors.</li>
          <li>Privacy and security considerations for the envelope.</li>
        </ul>

        <t>Out of scope (see also <xref target="appendix-non-goals"/>):</t>
        <ul spacing="normal">
          <li>
            Definition of analytical metrics or anomaly detection.
          </li>
          <li>
            Failure classification, regime detection, or operational
            dashboards.
          </li>
          <li>
            Probing protocol details (this document describes a
            reporting envelope, not a measurement protocol).
          </li>
          <li>
            Comparison frameworks against external observability
            platforms.
          </li>
        </ul>
      </section>

      <section anchor="rel-9198">
        <name>Relationship to RFC 9198 (AURA)</name>

        <t>
          <xref target="RFC9198"/> defines AURA, an architecture for
          large-scale active network measurement using cooperating
          agents.  AURA addresses agent registration, capability
          advertisement, measurement task distribution, and result
          collection, but does not mandate a specific on-the-wire
          format for the per-vantage results of a coordinated
          measurement.
        </t>
        <t>
          MVPS does not replace AURA.  An AURA-conforming collector
          MAY use MVPS as its bundle serialization format when
          coordinating traceroute measurements.  Conversely, an MVPS
          producer is not required to operate within an AURA-managed
          deployment; standalone collectors are equally supported.
        </t>
      </section>

      <section anchor="rel-existing">
        <name>Relationship to Existing Reporting Formats</name>

        <t>
          Several measurement platforms already publish results from
          coordinated traceroute campaigns in formats that overlap
          with MVPS in intent.  This section briefly distinguishes
          MVPS from the most widely deployed of these.
        </t>

        <section>
          <name>RIPE Atlas Measurement Results</name>
          <t>
            RIPE Atlas <xref target="RIPE-Atlas-Measurements"/>
            publishes per-probe traceroute results as JSON, with a
            stable per-platform schema.  The format is operationally
            mature, widely used, and well-documented, but it is
            (a) operator-specific, with no normative reference
            outside RIPE NCC documentation; (b) not aligned with a
            YANG model; (c) does not define a deterministic
            cross-implementation path identity; and (d) does not
            declare a coordination window or a clock-skew uncertainty
            at the level of a bundle aggregating multiple probes.
          </t>
          <t>
            MVPS aims to provide the same kind of operational utility
            in a vendor-neutral, RFC-style envelope with explicit
            coordination semantics and a fingerprint-level identity
            primitive.
          </t>
        </section>

        <section>
          <name>CAIDA scamper / warts</name>
          <t>
            CAIDA's scamper tool <xref target="CAIDA-Warts"/> emits
            measurement results in the binary "warts" format.  Warts
            is rich, efficient, and well-supported by the CAIDA
            tooling ecosystem, but it is binary, tool-specific, and
            not designed as a vendor-neutral interchange format.
            MVPS is text-based JSON (with an aligned YANG model) and
            is intended for cases where interchange and audit
            readability are primary concerns; warts and MVPS are
            therefore complementary rather than competing.
          </t>
        </section>

        <section>
          <name>Other Platforms</name>
          <t>
            Other coordinated-measurement platforms (commercial and
            research) typically use proprietary formats.  MVPS is
            intended to be a candidate common denominator for
            cross-platform publication of bundle-level results.
          </t>
        </section>
      </section>

    </section>

    <section anchor="terminology">
      <name>Terminology and Conventions</name>

      <t>
        This document uses the JSON data format
        <xref target="RFC8259"/>, the YANG 1.1 data modelling
        language <xref target="RFC7950"/>, common YANG data types
        from <xref target="RFC6991"/>, RTT definitions consistent
        with <xref target="RFC2681"/>, and UUIDs as defined in
        <xref target="RFC4122"/>.
      </t>

      <t>
        The key words "<bcp14>MUST</bcp14>", "<bcp14>MUST NOT</bcp14>",
        "<bcp14>REQUIRED</bcp14>", "<bcp14>SHALL</bcp14>",
        "<bcp14>SHALL NOT</bcp14>", "<bcp14>SHOULD</bcp14>",
        "<bcp14>SHOULD NOT</bcp14>", "<bcp14>RECOMMENDED</bcp14>",
        "<bcp14>NOT RECOMMENDED</bcp14>", "<bcp14>MAY</bcp14>", and
        "<bcp14>OPTIONAL</bcp14>" in this document are to be
        interpreted as described in BCP 14
        <xref target="RFC2119"/><xref target="RFC8174"/> when, and
        only when, they appear in all capitals, as shown here.
      </t>

      <dl>
        <dt>Vantage:</dt>
        <dd>
          A network position from which a traceroute measurement is
          initiated.  Identified by a vantage-id (string) and
          OPTIONALLY by declared geolocation.
        </dd>

        <dt>Snapshot:</dt>
        <dd>
          A single per-vantage observation within a bundle.  Contains
          the ordered hop list, RTT samples, timestamps, and metadata.
        </dd>

        <dt>Bundle:</dt>
        <dd>
          A collection of snapshots of a common destination, gathered
          within a single coordination window.
        </dd>

        <dt>Path Fingerprint:</dt>
        <dd>
          A deterministic cryptographic hash (SHA-256) of the
          canonicalized hop sequence of a snapshot, as defined in
          <xref target="fingerprint"/>.
        </dd>

        <dt>Coordination Window:</dt>
        <dd>
          The time interval [start, end] within which all snapshots
          of a bundle MUST have been initiated.  The width
          (end - start) declares the coordination tolerance.
        </dd>

        <dt>Hop:</dt>
        <dd>
          An intermediate router observed in a traceroute,
          identified by its responding IP address (when available)
          or by an opaque-marker.
        </dd>

        <dt>Opaque Hop:</dt>
        <dd>
          A hop for which the responding IP is not observable (for
          example, MPLS opaque LSRs, ICMP-disabled routers, or hops
          redacted per <xref target="RFC5837"/>).
        </dd>
      </dl>
    </section>

    <section anchor="known-limitations">
      <name>Known Limitations of This Revision</name>

      <t>
        This revision (-00) makes a number of explicit simplifying
        choices.  They are listed here so that reviewers and
        implementers can evaluate them up front; future revisions
        MAY relax some of these.
      </t>

      <section>
        <name>Load-Balanced Paths</name>
        <t>
          Standard traceroute can return different responding
          addresses at the same TTL when an intermediate router or
          load-balancer dispatches probes across multiple equal-cost
          next-hops.  Paris traceroute
          <xref target="ParisTraceroute"/> and scamper avoid this by
          constraining the flow identifier across probes.  In this
          revision, MVPS represents a hop as a single
          <tt>address</tt> (or <tt>opaque_marker</tt>); producers
          observing load-balanced behaviour
          <bcp14>SHOULD</bcp14> either (a) use a Paris-style probing
          discipline so that a single canonical hop is observed per
          TTL, or (b) emit several distinct bundles, one per flow
          identifier.  A multi-address-per-hop encoding is left to
          a future revision.
        </t>
      </section>

      <section>
        <name>Router Identity vs. Interface Identity</name>
        <t>
          A responding IP address identifies an interface, not a
          router.  Two interfaces of the same router can produce
          different path-fingerprints even though the path
          traversed is operationally identical.  This document
          treats path-fingerprints as a check on observational
          identity (same probed bytes produce same fingerprint), not
          on physical-router identity.  Consumers requiring router-
          level identity SHOULD pair MVPS with an external alias
          resolution dataset.
        </t>
      </section>

      <section>
        <name>ICMP Extensions and MPLS Labels</name>
        <t>
          ICMP extensions per <xref target="RFC4884"/> and the MPLS
          label information that can be carried in them
          (<xref target="RFC4950"/>) are not modelled in this
          revision.  Implementations that capture such extensions
          MAY publish them in a vendor-specific reverse-DNS extension
          field, but interoperability of such fields is out of scope.
        </t>
      </section>

      <section>
        <name>Anycast Destinations</name>
        <t>
          When the destination is an anycast address, different
          vantages can legitimately reach different sites, producing
          legitimately different path-fingerprints.  The
          <tt>is_anycast</tt> flag carries this information so that
          consumers do not interpret divergent fingerprints as
          evidence of inconsistency in such cases.
        </t>
      </section>
    </section>

    <section anchor="requirements">
      <name>Requirements</name>

      <t>
        The following requirements apply to producers of MVPS bundles.
      </t>

      <ul spacing="normal">
        <li>
          <strong>REQ-1.</strong>  An MVPS bundle <bcp14>MUST</bcp14>
          contain at least one snapshot.
        </li>
        <li>
          <strong>REQ-2.</strong>  All snapshots in a bundle
          <bcp14>MUST</bcp14> share the same destination.
        </li>
        <li>
          <strong>REQ-3.</strong>  Each snapshot
          <bcp14>MUST</bcp14> declare a vantage-id unique within the
          bundle.
        </li>
        <li>
          <strong>REQ-4.</strong>  Each snapshot
          <bcp14>MUST</bcp14> declare a start-timestamp.
        </li>
        <li>
          <strong>REQ-5.</strong>  The bundle
          <bcp14>MUST</bcp14> declare its coordination-window (start,
          end) as the envelope of its snapshots' start and end
          timestamps.
        </li>
        <li>
          <strong>REQ-6.</strong>  Each snapshot's path-fingerprint
          <bcp14>MUST</bcp14> be computed using the algorithm in
          <xref target="fingerprint"/>.
        </li>
        <li>
          <strong>REQ-7.</strong>  Implementations
          <bcp14>MUST</bcp14> produce bit-identical bundles when
          given the same input traceroute data, by following the
          canonicalization rules in <xref target="format"/>.
        </li>
        <li>
          <strong>REQ-8.</strong>  Implementations
          <bcp14>SHOULD</bcp14> support hop redaction per
          <xref target="RFC5837"/>.
        </li>
        <li>
          <strong>REQ-9.</strong>  Implementations
          <bcp14>MUST NOT</bcp14> include vantage credentials,
          operator-internal hostnames, or personally identifiable
          information in the bundle.
        </li>
        <li>
          <strong>REQ-10.</strong>  Implementations
          <bcp14>SHOULD</bcp14> declare a skew-bound-ms estimate
          (<xref target="window"/>).
        </li>
        <li>
          <strong>REQ-11.</strong>  In a hop, the fields
          <tt>address</tt> and <tt>opaque-marker</tt>
          <bcp14>MUST</bcp14> be mutually exclusive: exactly one of
          them is present in a well-formed hop.
        </li>
      </ul>
    </section>

    <section anchor="format">
      <name>MVPS Bundle Format</name>

      <section anchor="canonicalization">
        <name>Canonicalization</name>

        <t>
          To ensure bit-reproducibility (REQ-7), the JSON
          serialization of an MVPS bundle <bcp14>MUST</bcp14> apply
          the following canonicalization rules:
        </t>
        <ul spacing="normal">
          <li>UTF-8 encoding, without byte-order mark.</li>
          <li>
            Object keys sorted lexicographically (UTF-16 code unit
            order), at every nesting level.
          </li>
          <li>No insignificant whitespace.</li>
          <li>
            Numbers serialized in their shortest decimal
            representation; trailing zeroes after the decimal point
            are omitted, except as required to preserve declared
            precision (for example, <tt>declared_lat</tt> with
            6 fraction digits).
          </li>
          <li>
            IPv4 addresses in dotted decimal.
          </li>
          <li>
            IPv6 addresses in the fully-expanded lowercase form
            (eight groups of four lowercase hexadecimal digits
            separated by ":"; no zero compression; no "::"
            shorthand).  This is a <strong>declared deviation</strong>
            from <xref target="RFC5952"/>, which mandates zero
            compression: see <xref target="canonicalization-ipv6"/>
            for rationale.
          </li>
          <li>
            Timestamps in RFC 3339 format with timezone "Z" (UTC).
          </li>
        </ul>
        <t>
          This document references the canonicalization rules of
          <xref target="RFC8785"/> (JSON Canonicalization Scheme) and
          deviates only where required for IP-address and
          timestamp normalization above.
        </t>
      </section>

      <section anchor="canonicalization-ipv6">
        <name>IPv6 Canonicalization: Deviation from RFC 5952</name>
        <t>
          <xref target="RFC5952"/> specifies a recommended textual
          form for IPv6 addresses that includes lowercase hexadecimal
          digits, suppression of leading zeros within each 16-bit
          group, and compression of consecutive all-zero groups using
          the "::" shorthand (with several disambiguation rules).
          Implementations of RFC 5952 differ in corner cases
          (selection of which all-zero run to compress when several
          are tied, handling of embedded IPv4, etc.), and these
          differences propagate directly into the SHA-256
          path-fingerprint.
        </t>
        <t>
          To eliminate this source of cross-implementation drift,
          implementations of this document <bcp14>MUST</bcp14> use
          the fully-expanded lowercase form for IPv6 in both the
          on-the-wire representation and the canonical hop string
          used to compute the path-fingerprint
          (<xref target="fingerprint"/>). The fully-expanded form
          is unambiguous: each address has exactly one textual
          representation, derivable trivially from the 128-bit
          integer.
        </t>
        <t>
          This deviation is intentional and is the only departure
          from RFC 5952 in this document.  Implementations that
          render addresses for human display
          <bcp14>MAY</bcp14> apply RFC 5952 compression at
          presentation time, but <bcp14>MUST NOT</bcp14> use the
          compressed form in bundle output or in fingerprint input.
        </t>
      </section>

      <section anchor="yang-module">
        <name>YANG Module</name>

        <t>
          The canonical YANG 1.1 module <tt>catellix-mvps-bundle</tt> is
          provided in <xref target="appendix-yang"/>.  The JSON
          serialization (<xref target="json-schema"/>) is the
          RFC 7951 encoding of this module, with the
          canonicalization rules in <xref target="canonicalization"/>
          applied.
        </t>
      </section>

      <section anchor="json-schema">
        <name>JSON Schema</name>

        <t>
          A JSON Schema 2020-12 document expressing the same model
          is provided in <xref target="appendix-json"/>.  Where the
          YANG module and the JSON Schema disagree, the YANG module
          is normative.
        </t>
      </section>

      <section anchor="examples">
        <name>Examples</name>

        <t>
          Two examples are provided in
          <xref target="appendix-examples"/>: a minimal one-vantage
          bundle and a four-vantage bundle with opaque hops and an
          anycast destination.
        </t>
      </section>
    </section>

    <section anchor="fingerprint">
      <name>Path Fingerprint Algorithm</name>

      <section anchor="construction">
        <name>Construction</name>

        <t>
          For each snapshot, the path-fingerprint is computed as
          follows:
        </t>

        <ol spacing="normal" type="1">
          <li><t>Build the canonical hop string CANON:</t></li>
        </ol>

        <sourcecode type="abnf"><![CDATA[
CANON = "v1|"
      || destination_canonical
      || "|"
      || join("|", [hop_token(h) for h in hops_in_index_order])
        ]]></sourcecode>

        <ol spacing="normal" type="1" start="2">
          <li>
            <t>
              For each hop <tt>h</tt>, <tt>hop_token(h)</tt> is
              defined as follows.
            </t>
            <ul spacing="compact">
              <li>
                <t>
                  If <tt>h.address</tt> is present:
                  <tt>"ip:" || canonical_ip(h.address)</tt>.
                </t>
              </li>
              <li>
                <t>
                  If <tt>h.opaque_marker</tt> is present:
                  <tt>"op:" || h.opaque_marker</tt>.
                </t>
              </li>
              <li>
                <t>
                  Otherwise (well-formed bundles cannot reach this
                  branch, per REQ-11):
                  <tt>"*"</tt>.
                </t>
              </li>
            </ul>
          </li>
          <li>
            <t>
              <tt>canonical_ip(addr)</tt> is the lowercase, dotted-
              decimal representation for IPv4, and the fully-expanded
              lowercase form for IPv6 as defined in
              <xref target="canonicalization-ipv6"/> (a declared
              deviation from <xref target="RFC5952"/>).
            </t>
          </li>
          <li>
            <t>
              <tt>destination_canonical</tt> equals
              <tt>canonical_ip(bundle.destination.address)</tt>.
            </t>
          </li>
          <li>
            <t>
              <tt>path_fingerprint = lowercase_hex(SHA-256(UTF-8(CANON)))</tt>.
              SHA-256 is specified in <xref target="RFC6234"/>.
            </t>
          </li>
        </ol>

        <t>
          The leading <tt>"v1|"</tt> identifies the fingerprint
          version.  Future revisions of MVPS that change the
          fingerprint algorithm <bcp14>MUST</bcp14> use a different
          prefix.
        </t>
      </section>

      <section anchor="opaque-hops">
        <name>Opaque Hops</name>

        <t>
          Hops that respond but do not reveal an IP address (for
          example, MPLS opaque LSRs that do not generate ICMP
          Time Exceeded, or hops redacted per <xref target="RFC5837"/>)
          <bcp14>MUST</bcp14> be represented with an opaque-marker
          rather than a synthetic IP address.  Implementations
          <bcp14>MUST NOT</bcp14> invent IP addresses for opaque hops.
        </t>
        <t>
          Hops that did not respond at all
          <bcp14>SHOULD</bcp14> be represented with
          <tt>opaque_marker = "noresp"</tt>.
        </t>
      </section>

      <section anchor="test-vectors-section">
        <name>Test Vectors</name>

        <t>
          A set of conformance test vectors is provided in
          <xref target="appendix-vectors"/>.  Conformant
          implementations <bcp14>MUST</bcp14> reproduce all listed
          fingerprints bit-identically.
        </t>
      </section>
    </section>

    <section anchor="window">
      <name>Coordination Window Semantics</name>

      <section anchor="simultaneity">
        <name>Window Width and Consumer Use</name>

        <t>
          A bundle's snapshots are considered coordinated if all of
          them have start-timestamp values within the bundle's
          declared coordination-window <tt>[start, end]</tt>.  The
          width <tt>(end - start)</tt> is the maximum coordination
          tolerance the producer is asserting.  Smaller widths
          permit causality-sensitive analyses; larger widths only
          support coarser topology comparison.
        </t>

        <t>
          Consumers <bcp14>MAY</bcp14> filter or reject bundles
          whose window width exceeds an analysis-specific budget.
          This document does not normatively prescribe specific
          numeric thresholds; the appropriate budget depends on
          the consumer's intended use of the bundle.
        </t>

        <t>
          An optional informational hint
          (<tt>tolerance</tt> in the JSON serialization and the
          corresponding YANG leaf) <bcp14>MAY</bcp14> be carried
          to express the producer's intent at three reference
          orders of magnitude (sub-second, sub-minute, sub-five-
          minutes).  The hint is non-normative; the only
          normative time bound on a bundle is its
          <tt>(end - start)</tt> value.
        </t>
      </section>

      <section anchor="skew">
        <name>Clock Skew</name>

        <t>
          Vantage clocks are assumed to be synchronized via NTPv4
          <xref target="RFC5905"/> or PTP.  A bundle MAY declare
          <tt>skew_bound_ms</tt> as the producer's best estimate
          (upper bound) of the maximum pairwise clock skew across
          its vantages at the time of bundle creation.
        </t>
        <t>
          Bundles for which clock synchronization cannot be
          asserted <bcp14>MUST NOT</bcp14> declare a numeric
          <tt>skew_bound_ms</tt> value; consumers
          <bcp14>MUST NOT</bcp14> use such bundles for
          timing-sensitive cross-vantage analysis.
        </t>
        <t>
          The <tt>skew_bound_ms</tt> field is a declaration of
          uncertainty, not a measurement guarantee.
        </t>
      </section>

      <section anchor="temporal-uncertainty">
        <name>Temporal Uncertainty</name>

        <t>
          When publishing bundles for use by third parties,
          implementations <bcp14>SHOULD</bcp14> document how
          <tt>skew_bound_ms</tt> was estimated (for example, via
          chronyc tracking offsets, NTP root-distance, or
          out-of-band PTP statistics).  This document does not
          prescribe a single estimation method.
        </t>
      </section>
    </section>

    <section anchor="operational">
      <name>Operational Considerations</name>

      <section>
        <name>Parallel Collection</name>
        <t>
          A typical implementation initiates traceroute probes from
          all vantages within a short interval and aggregates the
          per-vantage results into a single bundle.  The bundle's
          coordination-window <bcp14>MUST</bcp14> reflect the
          earliest start-timestamp and the latest end-timestamp
          across snapshots.
        </t>
      </section>
      <section>
        <name>Missing Hops</name>
        <t>
          Hops that did not respond <bcp14>SHOULD</bcp14> be
          represented with <tt>opaque_marker = "noresp"</tt> rather
          than being silently omitted.  The hop list maintains
          1-based indices that match the TTL used by the probe.
        </t>
      </section>
      <section>
        <name>Typical Bundle Size</name>
        <t>
          A bundle with 4 vantages, 10 hops per snapshot, and 3 RTT
          samples per hop typically serializes to a few kilobytes
          uncompressed.  Implementations producing high collection
          rates <bcp14>SHOULD</bcp14> apply gzip or zstd compression
          at storage time.
        </t>
      </section>
      <section>
        <name>Retention</name>
        <t>
          This document does not prescribe retention policies.
          Operators publishing bundles in compliance with regional
          regulation should consult their privacy frameworks
          (for example, GDPR, LGPD).
        </t>
      </section>
    </section>

    <section anchor="privacy">
      <name>Privacy Considerations</name>

      <t>
        Bundles published as research datasets
        <bcp14>SHOULD</bcp14> apply the following measures:
      </t>
      <ul spacing="normal">
        <li>
          Operator-internal IP addresses
          <bcp14>SHOULD</bcp14> be replaced with documentation
          prefixes per <xref target="RFC5737"/>
          (<tt>192.0.2.0/24</tt>, <tt>198.51.100.0/24</tt>) or with
          <tt>opaque_marker = "redacted"</tt>.
        </li>
        <li>
          Hostnames <bcp14>MUST NOT</bcp14> appear in bundles
          (the format defines no field for hostnames).
        </li>
        <li>
          Declared geolocation
          <bcp14>SHOULD</bcp14> be reduced to a granularity not
          finer than 0.01 degrees (approximately 1&nbsp;km).
        </li>
        <li>
          ASN values
          <bcp14>MAY</bcp14> be replaced with placeholders when
          publishing bundles where operator identity must remain
          confidential.
        </li>
      </ul>

      <t>
        Implementers should be aware that temporal correlation of
        path-fingerprints across publications can be used to
        reidentify operators even when individual fields are
        anonymized.
      </t>
    </section>

    <section anchor="security">
      <name>Security Considerations</name>

      <section>
        <name>Reconnaissance Amplification</name>
        <t>
          Coordinated multi-vantage probing can be misused as a
          reconnaissance amplifier: an attacker controlling a
          collection point can map internal network topology at a
          rate disproportionate to single-vantage probing.
          Implementations <bcp14>SHOULD</bcp14> apply rate-limiting
          on probe issuance, restrict the set of permitted
          destinations, and require authentication for control
          channels that trigger coordinated collection.
        </t>
      </section>
      <section>
        <name>Bundle Poisoning</name>
        <t>
          A hostile or compromised vantage may produce a snapshot
          containing fabricated hops or RTT samples.  Consumers
          <bcp14>SHOULD NOT</bcp14> rely on any single vantage's
          claims without corroboration.  Sanity checks computed by
          consumers (for example, comparing observed cross-vantage
          RTTs against speed-of-light feasibility bounds, or
          comparing path-fingerprints across redundant vantages) can
          detect a subset of fabrications; the specific analytical
          machinery is out of scope of this document.  A future
          revision MAY define an optional per-snapshot cryptographic
          signature to bind a snapshot to its declared vantage.
        </t>
      </section>
      <section>
        <name>Replay</name>
        <t>
          Older bundles can be republished and presented as recent
          observations.  Consumers <bcp14>SHOULD</bcp14> validate
          the coordination-window timestamps against an external
          time reference, and <bcp14>MAY</bcp14> chain bundles
          via cryptographic accumulators (out of scope of this
          document) to detect replay.
        </t>
      </section>
      <section>
        <name>Information Disclosure</name>
        <t>
          Declared latitude and longitude
          <bcp14>MUST</bcp14> respect the granularity guidance in
          <xref target="privacy"/>.  Path-fingerprints, being
          deterministic, can reveal the existence of recurring path
          patterns; this is intended behaviour but should be weighed
          against the operator's exposure model before publication.
        </t>
      </section>
    </section>

    <section anchor="iana">
      <name>IANA Considerations</name>

      <section>
        <name>YANG Module Name</name>
        <t>
          The YANG module shipped with this document uses the
          namespace
          <tt>urn:catellix:params:xml:ns:yang:catellix-mvps-bundle</tt>,
          which is under the author's control and does not require
          IANA action.
        </t>
        <t>
          If this document is adopted by an IETF working group, the
          module name SHOULD be renamed to <tt>ietf-mvps-bundle</tt>
          and the namespace to
          <tt>urn:ietf:params:xml:ns:yang:ietf-mvps-bundle</tt> in
          accordance with <xref target="RFC8407"/> section 4.3.1,
          and the following registration SHOULD be requested in the
          "YANG Module Names" registry (<xref target="RFC6020"/>):
        </t>
        <dl>
          <dt>Name:</dt><dd>ietf-mvps-bundle</dd>
          <dt>Namespace:</dt><dd>urn:ietf:params:xml:ns:yang:ietf-mvps-bundle</dd>
          <dt>Prefix:</dt><dd>mvps</dd>
          <dt>Reference:</dt><dd>this document</dd>
        </dl>
      </section>

      <section>
        <name>Media Type Registration</name>
        <t>
          IANA is requested to register the media type
          <tt>application/mvps-bundle+json</tt> with the following
          parameters:
        </t>
        <dl>
          <dt>Type name:</dt><dd>application</dd>
          <dt>Subtype name:</dt><dd>mvps-bundle+json</dd>
          <dt>Required parameters:</dt><dd>none</dd>
          <dt>Optional parameters:</dt><dd>none</dd>
          <dt>Encoding considerations:</dt><dd>see RFC 8259</dd>
          <dt>Security considerations:</dt><dd>see <xref target="security"/></dd>
          <dt>Interoperability considerations:</dt><dd>this document</dd>
          <dt>Published specification:</dt><dd>this document</dd>
        </dl>
      </section>

      <section>
        <name>MVPS Bundle Capability Flags Registry</name>
        <t>
          IANA is requested to create the "MVPS Bundle Capability
          Flags" registry, with assignment policy
          "Specification Required" (<xref target="RFC8126"/>).
          Initial contents: none.
        </t>
      </section>
    </section>

    <section anchor="acknowledgements">
      <name>Acknowledgements</name>
      <t>
        The author thanks the IPPM working group for prior work on
        AURA (<xref target="RFC9198"/>) and active measurement
        primitives, and acknowledges related work on coordinated
        Internet measurement at RIPE Atlas, CAIDA, and ThousandEyes
        whose deployment experience motivated this format.
      </t>
    </section>

  </middle>

  <back>

    <references>
      <name>References</name>

      <references>
        <name>Normative References</name>

        <reference anchor="RFC2119" target="https://www.rfc-editor.org/info/rfc2119">
          <front>
            <title>Key words for use in RFCs to Indicate Requirement Levels</title>
            <author initials="S." surname="Bradner" fullname="S. Bradner"/>
            <date year="1997" month="March"/>
          </front>
          <seriesInfo name="BCP" value="14"/>
          <seriesInfo name="RFC" value="2119"/>
        </reference>

        <reference anchor="RFC5737" target="https://www.rfc-editor.org/info/rfc5737">
          <front>
            <title>IPv4 Address Blocks Reserved for Documentation</title>
            <author initials="J." surname="Arkko" fullname="J. Arkko"/>
            <author initials="M." surname="Cotton" fullname="M. Cotton"/>
            <author initials="L." surname="Vegoda" fullname="L. Vegoda"/>
            <date year="2010" month="January"/>
          </front>
          <seriesInfo name="RFC" value="5737"/>
        </reference>

        <reference anchor="RFC5837" target="https://www.rfc-editor.org/info/rfc5837">
          <front>
            <title>Extending ICMP for Interface and Next-Hop Identification</title>
            <author initials="A." surname="Atlas" fullname="A. Atlas"/>
            <date year="2010" month="April"/>
          </front>
          <seriesInfo name="RFC" value="5837"/>
        </reference>

        <reference anchor="RFC5905" target="https://www.rfc-editor.org/info/rfc5905">
          <front>
            <title>Network Time Protocol Version 4: Protocol and Algorithms Specification</title>
            <author initials="D." surname="Mills" fullname="D. Mills"/>
            <date year="2010" month="June"/>
          </front>
          <seriesInfo name="RFC" value="5905"/>
        </reference>

        <reference anchor="RFC5952" target="https://www.rfc-editor.org/info/rfc5952">
          <front>
            <title>A Recommendation for IPv6 Address Text Representation</title>
            <author initials="S." surname="Kawamura" fullname="S. Kawamura"/>
            <author initials="M." surname="Kawashima" fullname="M. Kawashima"/>
            <date year="2010" month="August"/>
          </front>
          <seriesInfo name="RFC" value="5952"/>
        </reference>

        <reference anchor="RFC6020" target="https://www.rfc-editor.org/info/rfc6020">
          <front>
            <title>YANG - A Data Modeling Language for the Network Configuration Protocol (NETCONF)</title>
            <author initials="M." surname="Bjorklund" fullname="M. Bjorklund"/>
            <date year="2010" month="October"/>
          </front>
          <seriesInfo name="RFC" value="6020"/>
        </reference>

        <reference anchor="RFC6234" target="https://www.rfc-editor.org/info/rfc6234">
          <front>
            <title>US Secure Hash Algorithms (SHA and SHA-based HMAC and HKDF)</title>
            <author initials="D." surname="Eastlake" fullname="D. Eastlake"/>
            <author initials="T." surname="Hansen" fullname="T. Hansen"/>
            <date year="2011" month="May"/>
          </front>
          <seriesInfo name="RFC" value="6234"/>
        </reference>

        <reference anchor="RFC7950" target="https://www.rfc-editor.org/info/rfc7950">
          <front>
            <title>The YANG 1.1 Data Modeling Language</title>
            <author initials="M." surname="Bjorklund" fullname="M. Bjorklund"/>
            <date year="2016" month="August"/>
          </front>
          <seriesInfo name="RFC" value="7950"/>
        </reference>

        <reference anchor="RFC7951" target="https://www.rfc-editor.org/info/rfc7951">
          <front>
            <title>JSON Encoding of Data Modeled with YANG</title>
            <author initials="L." surname="Lhotka" fullname="L. Lhotka"/>
            <date year="2016" month="August"/>
          </front>
          <seriesInfo name="RFC" value="7951"/>
        </reference>

        <reference anchor="RFC8407" target="https://www.rfc-editor.org/info/rfc8407">
          <front>
            <title>Guidelines for Authors and Reviewers of Documents Containing YANG Data Models</title>
            <author initials="A." surname="Bierman" fullname="A. Bierman"/>
            <date year="2018" month="October"/>
          </front>
          <seriesInfo name="BCP" value="216"/>
          <seriesInfo name="RFC" value="8407"/>
        </reference>

        <reference anchor="RFC8126" target="https://www.rfc-editor.org/info/rfc8126">
          <front>
            <title>Guidelines for Writing an IANA Considerations Section in RFCs</title>
            <author initials="M." surname="Cotton" fullname="M. Cotton"/>
            <author initials="B." surname="Leiba" fullname="B. Leiba"/>
            <author initials="T." surname="Narten" fullname="T. Narten"/>
            <date year="2017" month="June"/>
          </front>
          <seriesInfo name="BCP" value="26"/>
          <seriesInfo name="RFC" value="8126"/>
        </reference>

        <reference anchor="RFC8174" target="https://www.rfc-editor.org/info/rfc8174">
          <front>
            <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
            <author initials="B." surname="Leiba" fullname="B. Leiba"/>
            <date year="2017" month="May"/>
          </front>
          <seriesInfo name="BCP" value="14"/>
          <seriesInfo name="RFC" value="8174"/>
        </reference>

        <reference anchor="RFC8259" target="https://www.rfc-editor.org/info/rfc8259">
          <front>
            <title>The JavaScript Object Notation (JSON) Data Interchange Format</title>
            <author initials="T." surname="Bray" fullname="T. Bray"/>
            <date year="2017" month="December"/>
          </front>
          <seriesInfo name="STD" value="90"/>
          <seriesInfo name="RFC" value="8259"/>
        </reference>

        <reference anchor="RFC8785" target="https://www.rfc-editor.org/info/rfc8785">
          <front>
            <title>JSON Canonicalization Scheme (JCS)</title>
            <author initials="A." surname="Rundgren" fullname="A. Rundgren"/>
            <author initials="B." surname="Jordan" fullname="B. Jordan"/>
            <author initials="S." surname="Erdtman" fullname="S. Erdtman"/>
            <date year="2020" month="June"/>
          </front>
          <seriesInfo name="RFC" value="8785"/>
        </reference>

        <reference anchor="RFC9198" target="https://www.rfc-editor.org/info/rfc9198">
          <front>
            <title>Advanced Unidirectional Route Assessment (AURA)</title>
            <author initials="J." surname="Alvarez-Hamelin" fullname="J. Alvarez-Hamelin"/>
            <date year="2022" month="April"/>
          </front>
          <seriesInfo name="RFC" value="9198"/>
        </reference>

      </references>

      <references>
        <name>Informative References</name>

        <reference anchor="RFC2681" target="https://www.rfc-editor.org/info/rfc2681">
          <front>
            <title>A Round-trip Delay Metric for IPPM</title>
            <author initials="G." surname="Almes" fullname="G. Almes"/>
            <author initials="S." surname="Kalidindi" fullname="S. Kalidindi"/>
            <author initials="M." surname="Zekauskas" fullname="M. Zekauskas"/>
            <date year="1999" month="September"/>
          </front>
          <seriesInfo name="RFC" value="2681"/>
        </reference>

        <reference anchor="RFC4122" target="https://www.rfc-editor.org/info/rfc4122">
          <front>
            <title>A Universally Unique IDentifier (UUID) URN Namespace</title>
            <author initials="P." surname="Leach" fullname="P. Leach"/>
            <date year="2005" month="July"/>
          </front>
          <seriesInfo name="RFC" value="4122"/>
        </reference>

        <reference anchor="RFC4884" target="https://www.rfc-editor.org/info/rfc4884">
          <front>
            <title>Extended ICMP to Support Multi-Part Messages</title>
            <author initials="R." surname="Bonica" fullname="R. Bonica"/>
            <date year="2007" month="April"/>
          </front>
          <seriesInfo name="RFC" value="4884"/>
        </reference>

        <reference anchor="RFC4950" target="https://www.rfc-editor.org/info/rfc4950">
          <front>
            <title>ICMP Extensions for Multiprotocol Label Switching</title>
            <author initials="R." surname="Bonica" fullname="R. Bonica"/>
            <date year="2007" month="August"/>
          </front>
          <seriesInfo name="RFC" value="4950"/>
        </reference>

        <reference anchor="RFC6991" target="https://www.rfc-editor.org/info/rfc6991">
          <front>
            <title>Common YANG Data Types</title>
            <author initials="J." surname="Schoenwaelder" fullname="J. Schoenwaelder"/>
            <date year="2013" month="July"/>
          </front>
          <seriesInfo name="RFC" value="6991"/>
        </reference>

        <reference anchor="RIPE-Atlas-Measurements">
          <front>
            <title>RIPE Atlas Measurement Results (operational documentation)</title>
            <author>
              <organization>RIPE NCC</organization>
            </author>
            <date year="2024"/>
          </front>
          <refcontent>https://atlas.ripe.net/docs/apis/result-format/</refcontent>
        </reference>

        <reference anchor="CAIDA-Warts">
          <front>
            <title>scamper / warts file format</title>
            <author>
              <organization>CAIDA</organization>
            </author>
            <date year="2024"/>
          </front>
          <refcontent>https://www.caida.org/catalog/software/scamper/</refcontent>
        </reference>

        <reference anchor="ParisTraceroute">
          <front>
            <title>Avoiding traceroute anomalies with Paris traceroute</title>
            <author initials="B." surname="Augustin" fullname="Brice Augustin"/>
            <author initials="X." surname="Cuvellier"/>
            <author initials="B." surname="Orgogozo"/>
            <author initials="F." surname="Viger"/>
            <author initials="T." surname="Friedman"/>
            <author initials="M." surname="Latapy"/>
            <author initials="C." surname="Magnien"/>
            <author initials="R." surname="Teixeira"/>
            <date year="2006" month="October"/>
          </front>
          <refcontent>ACM IMC 2006</refcontent>
        </reference>

      </references>

    </references>

    <section anchor="appendix-yang">
      <name>YANG Module (Normative)</name>
      <t>
        The full canonical module is distributed alongside this
        document as <tt>mvps-bundle.yang</tt>.  An identical copy
        appears below.
      </t>
      <sourcecode type="yang"><![CDATA[
module catellix-mvps-bundle {
  yang-version 1.1;
  namespace "urn:catellix:params:xml:ns:yang:catellix-mvps-bundle";
  prefix mvps;

  import ietf-yang-types {
    prefix yang;
    reference "RFC 6991: Common YANG Data Types";
  }

  import ietf-inet-types {
    prefix inet;
    reference "RFC 6991: Common YANG Data Types";
  }

  organization
    "Catellix (individual submission; not yet adopted by any IETF
     working group)";

  contact
    "Author:  Leonardo Melegassi
              <mailto:melegassi@catellix.com>";

  description
    "This YANG module defines the canonical data model for the
     Multi-Vantage Path Snapshot (MVPS) bundle envelope.  An MVPS
     bundle is a coordinated collection of per-vantage traceroute
     snapshots gathered within a bounded coordination window towards
     a common destination.

     This module is the normative source.  The JSON serialization
     follows RFC 7951 (JSON Encoding of Data Modeled with YANG)
     applied to this module, except that IP-address text forms
     follow the deviation declared in the companion specification
     (fully-expanded lowercase form for IPv6, NOT the compressed
     form of RFC 5952).

     This module does NOT define analytical metrics, anomaly
     detection, or failure classification.  Such functions are
     out of scope and are addressed in companion documents.

     The namespace 'urn:catellix:params:xml:ns:yang:' is used
     because this is an individual submission and not yet adopted
     by an IETF working group; the 'urn:ietf:params:xml:ns:yang:'
     prefix is reserved for adopted IETF modules per RFC 8407
     section 4.3.1.

     Copyright (c) 2026 Catellix and the contributors.
     Redistribution and use in source and binary forms, with or
     without modification, is permitted under the Revised BSD
     License (https://opensource.org/licenses/BSD-3-Clause).";

  revision 2026-05-17 {
    description
      "Initial individual submission (-00).";
    reference
      "draft-melegassi-ippm-mvps-bundle-00";
  }

  /*
   * Typedefs
   */

  typedef path-fingerprint {
    type string {
      length "64";
      pattern "[0-9a-f]{64}";
    }
    description
      "Lowercase hexadecimal representation of a SHA-256 digest
       computed over the canonical hop sequence of a snapshot, as
       defined in Section 5 of the MVPS specification.  Note that
       the fingerprint uses fully-expanded lowercase IPv6 form,
       which is a declared deviation from RFC 5952; this is
       intentional to remove ambiguity from the canonicalization
       step.";
  }

  typedef opaque-marker {
    type enumeration {
      enum mpls {
        description
          "Hop is opaque due to an MPLS Label-Switched Path that
           does not generate ICMP Time Exceeded.";
      }
      enum redacted {
        description
          "Hop has been deliberately redacted by the implementation,
           typically per RFC 5837 considerations.";
      }
      enum noresp {
        description
          "Hop did not respond within the implementation's timeout.";
      }
      enum filtered {
        description
          "Hop was filtered by an intermediate device (e.g., ACL
           dropping ICMP).";
      }
    }
    description
      "Reason category for an opaque hop, used when the responding
       IP address is unavailable.";
  }

  typedef coordination-tolerance {
    type enumeration {
      enum tight {
        description
          "Producer-asserted hint: window width is on the order of
           less than one second.  Intended for causality-sensitive
           consumers.  This is an INFORMATIONAL hint only; the only
           normative time bound is the (end - start) value of the
           coordination window.";
      }
      enum standard {
        description
          "Producer-asserted hint: window width is on the order of
           less than one minute.  Intended for general topology
           comparison.  INFORMATIONAL hint only.";
      }
      enum loose {
        description
          "Producer-asserted hint: window width is on the order of
           less than five minutes.  Intended for opportunistic
           aggregation.  INFORMATIONAL hint only.";
      }
    }
    description
      "Optional, non-normative producer hint about the intended
       use of a bundle's coordination window.  Consumers MAY use
       this hint to pre-filter bundles, but MUST rely on the
       numeric (end - start) value when making normative
       decisions.";
  }

  /*
   * Groupings
   */

  grouping vantage-identity {
    description
      "Identification of a measurement vantage.";

    leaf vantage-id {
      type string {
        length "1..64";
        pattern "[A-Za-z0-9_\\-]+";
      }
      mandatory true;
      description
        "Implementation-assigned identifier of the vantage, unique
         within a bundle.  Implementations MUST NOT include
         operator-internal hostnames or personally identifiable
         information.";
    }

    leaf declared-asn {
      type inet:as-number;
      description
        "Autonomous System Number declared by the vantage operator,
         when known.  Optional; absence MUST NOT be inferred as
         AS 0.";
    }

    leaf declared-lat {
      type decimal64 {
        fraction-digits 6;
        range "-90.0 .. 90.0";
      }
      units "degrees";
      description
        "Declared latitude (WGS-84) of the vantage.  Implementations
         publishing bundles SHOULD round to a granularity not finer
         than 0.01 degrees (approximately 1 km).";
    }

    leaf declared-lon {
      type decimal64 {
        fraction-digits 6;
        range "-180.0 .. 180.0";
      }
      units "degrees";
      description
        "Declared longitude (WGS-84) of the vantage.  See
         declared-lat for granularity guidance.";
    }
  }

  grouping rtt-sample {
    description
      "A single round-trip-time measurement.";

    leaf value-ms {
      type decimal64 {
        fraction-digits 3;
        range "0.0 .. 60000.0";
      }
      units "milliseconds";
      mandatory true;
      description
        "Round-trip time in milliseconds.";
    }

    leaf probe-sequence {
      type uint16;
      description
        "Per-hop sequence number of the probe that produced this
         sample, when meaningful.";
    }
  }

  grouping hop {
    description
      "A single hop observed in a traceroute.";

    leaf index {
      type uint8 {
        range "1..64";
      }
      mandatory true;
      description
        "1-based hop index in the path.";
    }

    leaf address {
      type inet:ip-address;
      description
        "Responding IP address of the hop, when observed.";
    }

    leaf opaque-marker {
      type opaque-marker;
      description
        "Reason for absence of address.  MUST be present if and
         only if address is absent.";
    }

    list rtt-samples {
      key "value-ms";
      uses rtt-sample;
      description
        "Observed RTT samples to this hop.  An empty list is
         permitted (indicating no successful probe).";
    }
  }

  grouping destination {
    description
      "Target of the coordinated measurement.";

    leaf address {
      type inet:ip-address;
      mandatory true;
      description
        "Destination IP address.  Conformant implementations MUST
         use the same address across all snapshots in a bundle.";
    }

    leaf asn {
      type inet:as-number;
      description
        "ASN of the destination, when known.";
    }

    leaf is-anycast {
      type boolean;
      default "false";
      description
        "True if the destination is known to be served by anycast.";
    }
  }

  grouping coordination-window {
    description
      "Temporal envelope of a bundle.";

    leaf start {
      type yang:date-and-time;
      mandatory true;
      description
        "Earliest snapshot start timestamp, in UTC.";
    }

    leaf end {
      type yang:date-and-time;
      mandatory true;
      description
        "Latest snapshot end timestamp, in UTC.  MUST NOT be earlier
         than start.";
    }

    leaf tolerance {
      type coordination-tolerance;
      description
        "Declared coordination tolerance intent.  Consumers MAY use
         this to filter bundles unsuitable for their analysis.";
    }

    leaf skew-bound-ms {
      type uint32;
      units "milliseconds";
      description
        "Implementation's best estimate of the maximum pairwise
         clock skew across vantages in this bundle.  Absence
         indicates that no estimate is provided and the bundle
         MUST NOT be used for timing-sensitive cross-vantage
         analysis.";
    }
  }

  grouping snapshot {
    description
      "A per-vantage observation in a bundle.";

    uses vantage-identity;

    leaf path-fingerprint {
      type path-fingerprint;
      mandatory true;
      description
        "Deterministic SHA-256 fingerprint of the canonical hop
         sequence.  Computed as specified in Section 5 of the MVPS
         specification.";
    }

    leaf start-timestamp {
      type yang:date-and-time;
      mandatory true;
      description
        "UTC timestamp at which this snapshot's traceroute was
         initiated.";
    }

      leaf end-timestamp {
        type yang:date-and-time;
        description
          "UTC timestamp of snapshot traceroute completion.";
      }

    list hops {
      key "index";
      ordered-by user;
      uses hop;
      min-elements 1;
      description
        "Ordered list of observed hops.";
    }
  }

  /*
   * Top-level container
   */

  container bundle {
    description
      "A single MVPS bundle.";

    leaf bundle-id {
      type yang:uuid;
      mandatory true;
      description
        "UUID of this bundle. Implementations MUST generate it
         with sufficient entropy (RFC 4122).";
    }

    leaf schema-version {
      type string {
        pattern "mvps-bundle-v[0-9]+";
      }
      default "mvps-bundle-v1";
      description
        "Schema version identifier.  Conformant implementations of
         this document produce mvps-bundle-v1.";
    }

    container destination {
      uses destination;
      description
        "Common destination of all snapshots in this bundle.";
    }

    container coordination-window {
      uses coordination-window;
      description
        "Temporal envelope of this bundle.";
    }

    list snapshots {
      key "vantage-id";
      min-elements 1;
      uses snapshot;
      description
        "Per-vantage observations.  A bundle MUST contain at least
         one snapshot.  A bundle with exactly one snapshot is
         well-formed but provides no cross-vantage information.";
    }
  }
}
]]></sourcecode>
    </section>

    <section anchor="appendix-json">
      <name>JSON Schema (Informative)</name>
      <t>
        The JSON Schema 2020-12 document is distributed alongside
        this document as <tt>mvps-bundle.schema.json</tt>.  It is
        provided for tooling convenience; where it disagrees with
        the YANG module in <xref target="appendix-yang"/>, the
        YANG module governs.
      </t>
      <sourcecode type="json"><![CDATA[
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://catellix.com/schemas/mvps-bundle/v1.json",
  "$comment":
    "Companion of YANG module (RFC 7951).",
  "title": "MVPS Bundle v1",
  "description":
    "JSON serialization (RFC 7951).",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "bundle_id",
    "schema_version",
    "destination",
    "coordination_window",
    "snapshots"
  ],
  "properties": {
    "bundle_id": {
      "type": "string",
      "format": "uuid",
      "description": "RFC 4122 UUID, lowercase."
    },
    "schema_version": {
      "const": "mvps-bundle-v1",
      "description": "Schema version identifier."
    },
    "destination": {
      "$ref": "#/$defs/destination"
    },
    "coordination_window": {
      "$ref": "#/$defs/coordination_window"
    },
    "snapshots": {
      "type": "array",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/snapshot"
      },
      "description": "Per-vantage observations."
    }
  },
  "$defs": {
    "destination": {
      "type": "object",
      "additionalProperties": false,
      "required": ["address"],
      "properties": {
        "address": {
          "anyOf": [
            { "type": "string", "format": "ipv4" },
            { "type": "string", "format": "ipv6" }
          ],
          "description":
            "Destination IP. IPv6 fully-expanded (Sec 5.2)."
        },
        "asn": {
          "type": "integer",
          "minimum": 0,
          "maximum": 4294967295
        },
        "is_anycast": {
          "type": "boolean",
          "default": false
        }
      }
    },
    "coordination_window": {
      "type": "object",
      "additionalProperties": false,
      "required": ["start", "end"],
      "properties": {
        "start": {
          "type": "string",
          "format": "date-time",
          "description": "RFC 3339, UTC."
        },
        "end": {
          "type": "string",
          "format": "date-time",
          "description":
            "RFC 3339, UTC. MUST NOT be earlier than start."
        },
        "tolerance": {
          "type": "string",
          "enum": ["tight", "standard", "loose"]
        },
        "skew_bound_ms": {
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "snapshot": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "vantage_id",
        "path_fingerprint",
        "start_timestamp",
        "hops"
      ],
      "properties": {
        "vantage_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "pattern": "^[A-Za-z0-9_-]+$"
        },
        "declared_asn": {
          "type": "integer",
          "minimum": 0,
          "maximum": 4294967295
        },
        "declared_lat": {
          "type": "number",
          "minimum": -90.0,
          "maximum": 90.0
        },
        "declared_lon": {
          "type": "number",
          "minimum": -180.0,
          "maximum": 180.0
        },
        "path_fingerprint": {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$",
          "description":
            "Lowercase hex SHA-256 of canonical hop seq (Section 6)."
        },
        "start_timestamp": {
          "type": "string",
          "format": "date-time"
        },
        "end_timestamp": {
          "type": "string",
          "format": "date-time"
        },
        "hops": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/hop" }
        }
      }
    },
    "hop": {
      "type": "object",
      "additionalProperties": false,
      "required": ["index"],
      "properties": {
        "index": {
          "type": "integer",
          "minimum": 1,
          "maximum": 64
        },
        "address": {
          "anyOf": [
            { "type": "string", "format": "ipv4" },
            { "type": "string", "format": "ipv6" }
          ],
          "description":
            "Responding IP of the hop, when observed."
        },
        "opaque_marker": {
          "type": "string",
          "enum": ["mpls", "redacted", "noresp", "filtered"]
        },
        "rtt_samples": {
          "type": "array",
          "items": { "$ref": "#/$defs/rtt_sample" },
          "default": []
        }
      },
      "allOf": [
        {
          "description":
            "Exactly one of address or opaque_marker.",
          "oneOf": [
            {
              "required": ["address"],
              "not": { "required": ["opaque_marker"] }
            },
            {
              "required": ["opaque_marker"],
              "not": { "required": ["address"] }
            }
          ]
        }
      ]
    },
    "rtt_sample": {
      "type": "object",
      "additionalProperties": false,
      "required": ["value_ms"],
      "properties": {
        "value_ms": {
          "type": "number",
          "minimum": 0.0,
          "maximum": 60000.0
        },
        "probe_sequence": {
          "type": "integer",
          "minimum": 0,
          "maximum": 65535
        }
      }
    }
  }
}
]]></sourcecode>
    </section>

    <section anchor="appendix-examples">
      <name>Examples</name>

      <section>
        <name>Minimal Single-Vantage Bundle</name>
        <sourcecode type="json"><![CDATA[
{
  "bundle_id": "11111111-1111-4111-8111-111111111111",
  "coordination_window": {
    "end": "2026-05-17T18:00:00.500Z",
    "start": "2026-05-17T18:00:00.000Z",
    "tolerance": "tight"
  },
  "destination": {
    "address": "192.0.2.1",
    "is_anycast": false
  },
  "schema_version": "mvps-bundle-v1",
  "snapshots": [
    {
      "declared_lat": -23.55,
      "declared_lon": -46.63,
      "hops": [
        {
          "address": "198.51.100.1",
          "index": 1,
          "rtt_samples": [{"value_ms": 1.234}]
        },
        {
          "address": "198.51.100.42",
          "index": 2,
          "rtt_samples": [{"value_ms": 5.678}]
        },
        {
          "address": "192.0.2.1",
          "index": 3,
          "rtt_samples": [{"value_ms": 12.345}]
        }
      ],
      "path_fingerprint":
"55d4f7f8d6a4c5b9a8df7e6c3b2a190f5e4d3c2b1a0e9f8d7c6b5a4938271605",
      "start_timestamp": "2026-05-17T18:00:00.000Z",
      "vantage_id": "V0"
    }
  ]
}
        ]]></sourcecode>
        <t>
          (The fingerprint value above is illustrative; conformance
          test vectors carry exact expected values.)
        </t>
      </section>
    </section>

    <section anchor="appendix-vectors">
      <name>Conformance Test Vectors</name>
      <t>
        At minimum 20 conformance test vectors are distributed
        alongside this document under <tt>test-vectors/v01.json</tt>
        through <tt>test-vectors/vNN.json</tt>.  Each vector contains:
      </t>
      <ul spacing="compact">
        <li>a <tt>name</tt> identifying the case;</li>
        <li>an <tt>input</tt> snapshot;</li>
        <li>an <tt>expected_path_fingerprint</tt>.</li>
      </ul>
      <t>
        Implementations conformant to this document
        <bcp14>MUST</bcp14> reproduce every
        <tt>expected_path_fingerprint</tt> bit-identically.
      </t>
    </section>

    <section anchor="appendix-non-goals">
      <name>Out-of-Scope Topics</name>
      <t>
        For clarity to reviewers, the following topics are explicitly
        out of scope for this document and will be addressed, if at
        all, in companion documents:
      </t>
      <ul spacing="normal">
        <li>
          Cross-vantage analytical metrics (speed-of-light feasibility
          bounds, Jensen-Shannon path divergence, topological-overlap
          measures, etc.).  These belong in a companion
          Experimental-track document, not in the bundle-format
          specification.
        </li>
        <li>
          Anomaly detection, failure classification, regime detection,
          critical-slowing-down indicators, or any other consumer-side
          analytical layer.
        </li>
        <li>
          The probing protocol used to populate a bundle.  MVPS is a
          reporting envelope; the probing protocol is independent
          (typical implementations use ICMP/UDP traceroute, but
          MVPS does not constrain this choice).
        </li>
        <li>
          Visualization, dashboarding, or operator-facing user
          interfaces.
        </li>
      </ul>
    </section>

  </back>
</rfc>
