{
  "profile": "cbor-det-v1",
  "spec": "The SDX Protocol deterministic encoding profile, with its shortest-form integer rule. Each vector pins the hex-encoded canonical CBOR bytes for a known input, computed via cbor-x 1.6.4 with the canonical-profile options (useRecords:false, variableMapSize:true, mapsAsObjects:false, tagUint8Array:false, useFloat32:0, alwaysUseFloat:false, bundleStrings:false) plus repo-side normalisation (sorted-by-canonical-key-bytes carried as a Map<string,V>, safe integers needing an 8-byte head widened to BigInt so cbor-x emits major-type-0/1 rather than float64, Date to ms-int, Set to sorted-array, Map input sorted by key bytes, undefined-elision). A bigint INPUT is refused outright (profile rule, fixed 2026-08-11), so bigints appear here only as an internal widening, never as a vector input.",
  "vectors": [
    {
      "name": "empty-map",
      "doc": "RFC 8949 §3.2.1: empty map encodes to 0xa0 (major-type 5, length 0)",
      "input": {
        "kind": "value",
        "value": {}
      },
      "encoded_hex": "a0"
    },
    {
      "name": "single-key-map",
      "doc": "Single string key 'a' (1 byte UTF-8) -> integer 1",
      "input": {
        "kind": "value",
        "value": {
          "a": 1
        }
      },
      "encoded_hex": "a1616101"
    },
    {
      "name": "sorted-multi-key-map-from-insertion-order",
      "doc": "Insertion order {b,a,c} canonicalises to bytewise key order {a,b,c}. Each key is one byte (0x6161, 0x6162, 0x6163), so canonical sort is equivalent to lexicographic sort over the keys.",
      "input": {
        "kind": "value",
        "value": {
          "b": 1,
          "a": 2,
          "c": 3
        }
      },
      "encoded_hex": "a3616102616201616303"
    },
    {
      "name": "byte-sort-not-string-sort",
      "doc": "Keys 'aaa' (3-byte UTF-8 prefix tag 0x63 + bytes) and 'z' (1-byte tag 0x61 + byte). Bytewise CBOR-key order: 0x61 < 0x63, so 'z' sorts before 'aaa'. JS UTF-16 sort would give the opposite. Locks the encoder to RFC 8949 §4.2.1 bytewise ordering.",
      "input": {
        "kind": "value",
        "value": {
          "aaa": 1,
          "z": 2
        }
      },
      "encoded_hex": "a2617a026361616101"
    },
    {
      "name": "nested-map",
      "doc": "Outer map with one key 'outer' (5 chars) -> inner map {z:1, a:2}. Inner keys also sort bytewise (a < z).",
      "input": {
        "kind": "value",
        "value": {
          "outer": {
            "z": 1,
            "a": 2
          }
        }
      },
      "encoded_hex": "a1656f75746572a2616102617a01"
    },
    {
      "name": "long-string-23-bytes",
      "doc": "23 'a' chars uses major-type-3 with length encoded inline in the type byte (0x77 = 0x60 | 23). Boundary case before the 1-byte-length-prefix form.",
      "input": {
        "kind": "value",
        "value": "aaaaaaaaaaaaaaaaaaaaaaa"
      },
      "encoded_hex": "776161616161616161616161616161616161616161616161"
    },
    {
      "name": "long-string-24-bytes",
      "doc": "24 'a' chars triggers the 1-byte-length-prefix form (0x78 0x18 + 24 bytes). Boundary above 23.",
      "input": {
        "kind": "value",
        "value": "aaaaaaaaaaaaaaaaaaaaaaaa"
      },
      "encoded_hex": "7818616161616161616161616161616161616161616161616161"
    },
    {
      "name": "negative-integer-small",
      "doc": "Negative integer -1 encodes to 0x20 (major-type-1, value 0). CBOR negative-int encoding is -1 - n where n is the argument.",
      "input": {
        "kind": "value",
        "value": -1
      },
      "encoded_hex": "20"
    },
    {
      "name": "negative-integer-multibyte",
      "doc": "-100 encodes to 0x38 0x63 (major-type-1, 1-byte argument, value 99 -> -100).",
      "input": {
        "kind": "value",
        "value": -100
      },
      "encoded_hex": "3863"
    },
    {
      "name": "byte-string",
      "doc": "Uint8Array([1,2,3]) encodes as major-type-2 length 3 (0x43) followed by the bytes.",
      "input": {
        "kind": "uint8array",
        "bytes": [
          1,
          2,
          3
        ]
      },
      "encoded_hex": "43010203"
    },
    {
      "name": "array-positional",
      "doc": "Arrays preserve insertion order (positional, unlike objects which sort).",
      "input": {
        "kind": "value",
        "value": [
          1,
          2,
          3
        ]
      },
      "encoded_hex": "83010203"
    },
    {
      "name": "primitives",
      "doc": "null=0xf6, true=0xf5, false=0xf4 per RFC 8949 simple-value encoding inside a 3-element array.",
      "input": {
        "kind": "value",
        "value": [
          null,
          true,
          false
        ]
      },
      "encoded_hex": "83f6f5f4"
    },
    {
      "name": "mixed-shape",
      "doc": "Mixed object with 4 keys (n,b,s,a) inserted in NON-canonical order. Canonical sort by key bytes: a<b<n<s. Values exercise null, byte string, short string, and nested array of negative+zero ints.",
      "input": {
        "kind": "object",
        "fields": {
          "n": {
            "kind": "value",
            "value": null
          },
          "b": {
            "kind": "uint8array",
            "bytes": [
              255,
              0
            ]
          },
          "s": {
            "kind": "value",
            "value": "hi"
          },
          "a": {
            "kind": "value",
            "value": [
              1,
              -1,
              0
            ]
          }
        }
      },
      "encoded_hex": "a4616183012000616242ff00616ef66173626869"
    },
    {
      "name": "integer-index-key-vs-empty-string",
      "doc": "Regression vector for the JS-engine integer-index-key reordering bug. Keys '' (empty string, 0x60) and '0' (one byte 0x6130). Canonical bytewise sort: '' (0x60) < '0' (0x6130). A plain JS object with these keys would enumerate '0' first because '0' is an array-index-canonical string per ECMA-262. The encoder MUST use a Map (or equivalent insertion-order-preserving carrier) so the canonical sort survives. Bit-exact output proves the carrier is correct.",
      "input": {
        "kind": "object",
        "fields": {
          "0": {
            "kind": "value",
            "value": "a"
          },
          "": {
            "kind": "value",
            "value": "b"
          }
        }
      },
      "encoded_hex": "a260616261306161"
    },
    {
      "name": "integer-index-and-letter-key",
      "doc": "Mixed integer-like and letter keys. {a:1, '0':2}: canonical bytewise sort places '0' (0x6130) before 'a' (0x6161). A plain JS object enumerates ['0','a'] (array-index first), which happens to agree with canonical here, but only by coincidence; the previous vector locks the actual bug.",
      "input": {
        "kind": "object",
        "fields": {
          "a": {
            "kind": "value",
            "value": 1
          },
          "0": {
            "kind": "value",
            "value": 2
          }
        }
      },
      "encoded_hex": "a2613002616101"
    },
    {
      "name": "int-ladder-0",
      "doc": "Integer ladder, rung 1. the profile's shortest-form integer rule: smallest representation. 0 is inline in the head byte. Unchanged by the integer-widening rule; pinned so the ladder covers the whole range rather than only the range that moved.",
      "input": {
        "kind": "value",
        "value": 0
      },
      "encoded_hex": "00"
    },
    {
      "name": "int-ladder-23-last-inline",
      "doc": "Integer ladder. 23 is the last value carried inline in the head byte (additional info 0..23). Unchanged by the integer-widening rule.",
      "input": {
        "kind": "value",
        "value": 23
      },
      "encoded_hex": "17"
    },
    {
      "name": "int-ladder-24-first-1-byte-arg",
      "doc": "Integer ladder. 24 is the first value needing a separate argument byte (additional info 24). Unchanged by the integer-widening rule.",
      "input": {
        "kind": "value",
        "value": 24
      },
      "encoded_hex": "1818"
    },
    {
      "name": "int-ladder-255-last-1-byte-arg",
      "doc": "Integer ladder. Last value expressible in a 1-byte argument. Unchanged by the integer-widening rule.",
      "input": {
        "kind": "value",
        "value": 255
      },
      "encoded_hex": "18ff"
    },
    {
      "name": "int-ladder-256-first-2-byte-arg",
      "doc": "Integer ladder. First value needing a 2-byte argument (additional info 25). Unchanged by the integer-widening rule.",
      "input": {
        "kind": "value",
        "value": 256
      },
      "encoded_hex": "190100"
    },
    {
      "name": "int-ladder-65535-last-2-byte-arg",
      "doc": "Integer ladder. Last value expressible in a 2-byte argument. Unchanged by the integer-widening rule.",
      "input": {
        "kind": "value",
        "value": 65535
      },
      "encoded_hex": "19ffff"
    },
    {
      "name": "int-ladder-65536-first-4-byte-arg",
      "doc": "Integer ladder. First value needing a 4-byte argument (additional info 26). Unchanged by the integer-widening rule.",
      "input": {
        "kind": "value",
        "value": 65536
      },
      "encoded_hex": "1a00010000"
    },
    {
      "name": "int-ladder-4294967295-last-4-byte-arg",
      "doc": "Integer ladder, THE BOUNDARY, low side. 2^32-1 is the last value the pre-widening encoder wrote as an integer at all. It must keep its 4-byte form: a fix that over-widened would push this to an 8-byte head and break shortest-form in the other direction.",
      "input": {
        "kind": "value",
        "value": 4294967295
      },
      "encoded_hex": "1affffffff"
    },
    {
      "name": "int-ladder-4294967296-first-8-byte-arg",
      "doc": "Integer ladder, THE BOUNDARY, high side. 2^32 needs an 8-byte argument (additional info 27). Before the integer-widening rule, this encoded as float64 fb41f0000000000000, contradicting the profile's shortest-form integer rule, RFC 8949 §4.2.2, and the profile's unsigned-integer grammar. Expected bytes derived from RFC 8949 §3.1 independently of cbor-x, and cross-checked against an independent downstream measurement naming 1b0000000100000000 as the post-fix expectation.",
      "input": {
        "kind": "value",
        "value": 4294967296
      },
      "encoded_hex": "1b0000000100000000"
    },
    {
      "name": "int-ladder-4294967297",
      "doc": "Integer ladder. 2^32+1, guarding against a fix that special-cases only the round boundary value.",
      "input": {
        "kind": "value",
        "value": 4294967297
      },
      "encoded_hex": "1b0000000100000001"
    },
    {
      "name": "int-ladder-max-safe-integer",
      "doc": "Integer ladder. 2^53-1, the top of this profile's integer value domain: the largest integer a JS number holds exactly.",
      "input": {
        "kind": "value",
        "value": 9007199254740991
      },
      "encoded_hex": "1b001fffffffffffff"
    },
    {
      "name": "int-ladder-negative-4294967296-last-4-byte-arg",
      "doc": "Integer ladder, negative boundary, low side. Major type 1 encodes -1-n, so -(2^32) still fits a 4-byte argument (ffffffff) where the positive side has already run out. The negative boundary therefore sits one value further from zero than the positive one, which is exactly the off-by-one a naive symmetric fix gets wrong.",
      "input": {
        "kind": "value",
        "value": -4294967296
      },
      "encoded_hex": "3affffffff"
    },
    {
      "name": "int-ladder-negative-4294967297-first-8-byte-arg",
      "doc": "Integer ladder, negative boundary, high side. -(2^32)-1 is the first negative value needing an 8-byte argument. Before the integer-widening rule, this encoded as float64 fbc1f0000000100000.",
      "input": {
        "kind": "value",
        "value": -4294967297
      },
      "encoded_hex": "3b0000000100000000"
    },
    {
      "name": "int-ladder-min-safe-integer",
      "doc": "Integer ladder. -(2^53-1), the bottom of the integer value domain. Major type 1 argument is -1-n = 2^53-2 = 001ffffffffffffe.",
      "input": {
        "kind": "value",
        "value": -9007199254740991
      },
      "encoded_hex": "3b001ffffffffffffe"
    },
    {
      "name": "int-action-time-timestamp",
      "doc": "the integer-widening rule. A real Action.time in milliseconds since the epoch, the value that makes this defect universal: every Action carries one and every one of them is above 2^32. Before the integer-widening rule, this encoded as float64 fb4279ff1610b3d000.",
      "input": {
        "kind": "value",
        "value": 1786461096765
      },
      "encoded_hex": "1b0000019ff1610b3d"
    },
    {
      "name": "int-grant-expires-at-in-map",
      "doc": "the integer-widening rule. The delegation grammar declares the grant expiry as an unsigned integer; it was historically pinned as a float64 and the fix was deferred to this profile. Pinned inside a map so the widening is proven in a structural position, not only as a bare top-level value.",
      "input": {
        "kind": "value",
        "value": {
          "expiresAt": 1798761096765
        }
      },
      "encoded_hex": "a1696578706972657341741b000001a2ce84263d"
    },
    {
      "name": "int-above-safe-range-stays-float64",
      "doc": "the integer-widening rule negative-space vector. 2^53 is integral as a JS double but is NOT an exact integer in this profile's value domain, and the profile's shortest-form integer rule forbids tag 2/3 bignum, so no integer head can carry it. It therefore stays a float64, exactly as before the widening rule (confirmed 2026-08-11). Pinning it stops a future widening from quietly claiming integer precision the double does not have.",
      "input": {
        "kind": "value",
        "value": 9007199254740992
      },
      "encoded_hex": "fb4340000000000000"
    },
    {
      "name": "int-far-above-safe-range-stays-float64",
      "doc": "the integer-widening rule negative-space vector. 1e21 satisfies Number.isInteger but exceeds even uint64, so it could only be an integer via the forbidden bignum tag. Stays float64.",
      "input": {
        "kind": "value",
        "value": 1e+21
      },
      "encoded_hex": "fb444b1ae4d6e2ef50"
    },
    {
      "name": "int-near-boundary-non-integer-stays-float64",
      "doc": "the integer-widening rule negative-space vector. 4294967296.5 sits just past the boundary in magnitude but is not an integer, so the widening must not touch it.",
      "input": {
        "kind": "value",
        "value": 4294967296.5
      },
      "encoded_hex": "fb41f0000000080000"
    },
    {
      "name": "float-double-1.5",
      "doc": "Floating-point 1.5 encodes as IEEE 754 double-precision (CBOR major-type-7 simple-value 27 = 0xfb) followed by 8 big-endian bytes 0x3ff8000000000000. The encoder's alwaysUseFloat=false option keeps integers as integer-major-type while preserving float bit-exactness for non-integer numbers.",
      "input": {
        "kind": "value",
        "value": 1.5
      },
      "encoded_hex": "fb3ff8000000000000"
    },
    {
      "name": "float-zero-half",
      "doc": "Floating-point 0.5 encodes as 0xfb 0x3fe0000000000000.",
      "input": {
        "kind": "value",
        "value": 0.5
      },
      "encoded_hex": "fb3fe0000000000000"
    }
  ]
}