at.tensei.mapping

tensei.at

Schema Diff

+42 -6

From

CID
bafyreig2bpqv37k...
Indexed At
2026-10-16 08:32 UTC
View this version

To

CID
bafyreig4pmoxl4o...
Indexed At
2026-10-16 13:31 UTC
View this version

Compatibility Analysis

Breaking Changes Detected

3 breaking changes, 6 non-breaking changes.

Breaking Changes (3)
  • RequiredEdgeAdded RequiredEdgeAdded { vertex_id: "at.tensei.mapping#fixedPoint", src: "at.tensei.mapping#fixedPoint", tgt: "at.tensei.mapping#fixedPoint.value", kind: "prop", name: Some("value") }
  • RequiredEdgeAdded RequiredEdgeAdded { vertex_id: "at.tensei.mapping#scale", src: "at.tensei.mapping#scale", tgt: "at.tensei.mapping#scale.min", kind: "prop", name: Some("min") }
  • RequiredEdgeAdded RequiredEdgeAdded { vertex_id: "at.tensei.mapping#scale", src: "at.tensei.mapping#scale", tgt: "at.tensei.mapping#scale.max", kind: "prop", name: Some("max") }
Non-Breaking Changes (6)
  • AddedVertex AddedVertex { vertex_id: "at.tensei.mapping#fixedPoint" }
  • AddedVertex AddedVertex { vertex_id: "at.tensei.mapping#fixedPoint.value" }
  • AddedVertex AddedVertex { vertex_id: "at.tensei.mapping#mappingField.confidence" }
  • AddedVertex AddedVertex { vertex_id: "at.tensei.mapping#scale.max" }
  • AddedVertex AddedVertex { vertex_id: "at.tensei.mapping#scale.min" }
  • AddedEdge AddedEdge { src: "at.tensei.mapping#mappingField", tgt: "at.tensei.mapping#mappingField.confidence", kind: "prop", name: Some("confidence") }

Migration Guidance

Added Elements

  • AddedVertex { vertex_id: "at.tensei.mapping#fixedPoint" }
  • AddedVertex { vertex_id: "at.tensei.mapping#fixedPoint.value" }
  • AddedVertex { vertex_id: "at.tensei.mapping#mappingField.confidence" }
  • AddedVertex { vertex_id: "at.tensei.mapping#scale.max" }
  • AddedVertex { vertex_id: "at.tensei.mapping#scale.min" }

Additional Notes

  • Breaking: RequiredEdgeAdded { vertex_id: "at.tensei.mapping#scale", src: "at.tensei.mapping#scale", tgt: "at.tensei.mapping#scale.min", kind: "prop", name: Some("min") }
  • Breaking: RequiredEdgeAdded { vertex_id: "at.tensei.mapping#scale", src: "at.tensei.mapping#scale", tgt: "at.tensei.mapping#scale.max", kind: "prop", name: Some("max") }
  • Breaking: RequiredEdgeAdded { vertex_id: "at.tensei.mapping#fixedPoint", src: "at.tensei.mapping#fixedPoint", tgt: "at.tensei.mapping#fixedPoint.value", kind: "prop", name: Some("value") }
  • Non-breaking: AddedEdge { src: "at.tensei.mapping#mappingField", tgt: "at.tensei.mapping#mappingField.confidence", kind: "prop", name: Some("confidence") }
1 1
{
2 2
  "$type": "com.atproto.lexicon.schema",
3 3
  "defs": {
4 4
    "fieldDefinition": {
5 5
      "description": "A field's declared type and authored requiredness (§3.1). `required` is always written; validation supplies no default.",
6 6
      "properties": {
7 7
        "required": {
8 8
          "type": "boolean"
9 9
        },
10 10
        "type": {
11 11
          "ref": "#fieldType",
12 12
          "type": "ref"
13 13
        }
14 14
      },
15 15
      "required": [
16 16
        "type",
17 17
        "required"
18 18
      ],
19 19
      "type": "object"
20 20
    },
21 21
    "fieldType": {
22 22
      "description": "A declared output type (§3.1). `namespace` is required when `kind` is key and names the key namespace (§3.6).",
23 23
      "properties": {
24 24
        "kind": {
25 25
          "enum": [
26 26
            "text",
27 27
            "text[]",
28 28
            "number",
29 29
            "bool",
30 30
            "datetime",
31 31
            "uri",
32 32
            "did",
33 33
            "ref",
34 34
            "media",
35 35
            "score",
36 36
            "key",
37 37
            "json"
38 38
          ],
39 39
          "type": "string"
40 40
        },
41 41
        "namespace": {
42 42
          "ref": "#keyNamespace",
43 43
          "type": "ref"
44 44
        }
45 45
      },
46 46
      "required": [
47 47
        "kind"
48 48
      ],
49 49
      "type": "object"
50 50
    },
51 +
    "fixedPoint": {
52 +
      "description": "A number that is not an integer, where no type is declared for it (§1.5): `value` is the number times 10⁶, rounded to six decimal places, so 0.4 is {value: 400000}. An integer is never written this way; it is written as it is. Wherever `$type` names this def, the object is that number. Not stated by the lexicon, and enforced by the reader: it holds no property but `$type` and `value`.",
53 +
      "properties": {
54 +
        "value": {
55 +
          "maximum": 999999999999999,
56 +
          "minimum": -999999999999999,
57 +
          "type": "integer"
58 +
        }
59 +
      },
60 +
      "required": [
61 +
        "value"
62 +
      ],
63 +
      "type": "object"
64 +
    },
51 65
    "guard": {
52 -
      "description": "A §5 leaf. `field` is a §4.2 source path. `value` is a scalar (a string, a finite number or a boolean; never null or an object), or for in/notIn an array of scalars, and is absent for exists/notExists; the lexicon language has no type for a scalar of any kind, so `value` is not declared.",
66 +
      "description": "A §5 leaf. `field` is a §4.2 source path. `value` is a scalar (a string, an integer, a #fixedPoint for any other number, or a boolean; never null or an object), or for in/notIn an array of scalars, and is absent for exists/notExists; the lexicon language has no type for a scalar of any kind, so `value` is not declared.",
53 67
      "properties": {
54 68
        "field": {
55 69
          "minLength": 1,
56 70
          "type": "string"
57 71
        },
58 72
        "op": {
59 73
          "ref": "#predicateOp",
60 74
          "type": "ref"
61 75
        }
62 76
      },
63 77
      "required": [
64 78
        "field",
65 79
        "op"
66 80
      ],
67 81
      "type": "object"
68 82
    },
69 83
    "keyNamespace": {
70 84
      "enum": [
71 85
        "isbn",
72 86
        "olwork",
73 87
        "goodreads",
74 88
        "hive",
75 89
        "tmdb",
76 90
        "url"
77 91
      ],
78 92
      "type": "string"
79 93
    },
80 94
    "main": {
81 95
      "description": "One source collection → user-shaped output (§4.1). Also defines the field, expression and presentation types that at.tensei.pipeline refers to.",
82 96
      "key": "tid",
83 97
      "record": {
84 98
        "properties": {
85 99
          "createdAt": {
86 100
            "description": "When the mapping was created: a datetime as §3.1 reads one, which accepts forms atproto's `datetime` format does not, so the format is not declared.",
87 101
            "type": "string"
88 102
          },
89 103
          "guards": {
90 104
            "description": "§5 predicate leaves over source paths (§4.1); no all/any/not groups.",
91 105
            "items": {
92 106
              "ref": "#guard",
93 107
              "type": "ref"
94 108
            },
95 109
            "type": "array"
96 110
          },
97 111
          "origin": {
98 112
            "description": "Present on an adopted copy: the exact version copied (§1.3). Attribution and update-check input; execution never reads it.",
99 113
            "ref": "#origin",
100 114
            "type": "ref"
101 115
          },
102 116
          "presentation": {
103 117
            "description": "For a standalone preview and to keep a migrated draft's choices; a pipeline's own presentation is authoritative after downstream edits (§3.8). Its entries must name fields in slots.",
104 118
            "ref": "#presentation",
105 119
            "type": "ref"
106 120
          },
107 121
          "schema": {
108 122
            "ref": "#schemaPin",
109 123
            "type": "ref"
110 124
          },
111 125
          "slots": {
112 126
            "description": "The output fields: an object keyed by field names the user chooses, any non-empty name and not limited to a preset's, each value a #mappingField. The lexicon language has no map type, so the names and values are stated here rather than declared.",
113 127
            "type": "unknown"
114 128
          },
115 129
          "source": {
116 130
            "description": "The NSID of the collection this mapping reads, and the only place its field names appear.",
117 131
            "format": "nsid",
118 132
            "type": "string"
119 133
          },
120 134
          "version": {
121 135
            "const": 1,
122 136
            "description": "The document version. A document without one is the Phase-2 fixed-preset form, which is migrated explicitly to version 1 (§8.3) and never published as it is.",
123 137
            "type": "integer"
124 138
          },
125 139
          "viewType": {
126 140
            "description": "The built-in preset this mapping started from, as attribution only. It never adds, limits or requires a field (§3.8).",
127 141
            "enum": [
128 142
              "review",
129 143
              "bookmark"
130 144
            ],
131 145
            "type": "string"
132 146
          }
133 147
        },
134 148
        "required": [
135 149
          "version",
136 150
          "source",
137 151
          "slots"
138 152
        ],
139 153
        "type": "object"
140 154
      },
141 155
      "type": "record"
142 156
    },
143 157
    "mappingExpression": {
144 -
      "description": "A source-mapping expression (§4.3): path {path}, constant {value}, operation {op, inputs, args?} or lookup {input, from, to}. Not stated by the lexicon: which properties each kind requires; `value`, any JSON value; `args`, whose count and types each operation fixes (a string separator for split, join and concat, a string find and replacement for replace, an optional string format for parseDate, a finite non-zero divisor for divide, none otherwise); the input count (one, or one or more for coalesce and concat); §4.2 path syntax; and a nesting depth of at most 64 levels.",
158 +
      "description": "A source-mapping expression (§4.3): path {path}, constant {value}, operation {op, inputs, args?} or lookup {input, from, to}. Not stated by the lexicon: which properties each kind requires; `value`, any JSON value, in which a number that is not an integer is a #fixedPoint; `args`, whose count and types each operation fixes (a string separator for split, join and concat, a string find and replacement for replace, an optional string format for parseDate, a non-zero divisor for divide, none otherwise; a number that is not an integer is a #fixedPoint); the input count (one, or one or more for coalesce and concat); §4.2 path syntax; and a nesting depth of at most 64 levels.",
145 159
      "properties": {
146 160
        "from": {
147 161
          "ref": "#keyNamespace",
148 162
          "type": "ref"
149 163
        },
150 164
        "input": {
151 165
          "ref": "#mappingExpression",
152 166
          "type": "ref"
153 167
        },
154 168
        "inputs": {
155 169
          "items": {
156 170
            "ref": "#mappingExpression",
157 171
            "type": "ref"
158 172
          },
159 173
          "type": "array"
160 174
        },
161 175
        "kind": {
162 176
          "enum": [
163 177
            "path",
164 178
            "constant",
165 179
            "operation",
166 180
            "lookup"
167 181
          ],
168 182
          "type": "string"
169 183
        },
170 184
        "op": {
171 185
          "ref": "#operationName",
172 186
          "type": "ref"
173 187
        },
174 188
        "path": {
175 189
          "minLength": 1,
176 190
          "type": "string"
177 191
        },
178 192
        "to": {
179 193
          "ref": "#keyNamespace",
180 194
          "type": "ref"
181 195
        }
182 196
      },
183 197
      "required": [
184 198
        "kind"
185 199
      ],
186 200
      "type": "object"
187 201
    },
188 202
    "mappingField": {
189 -
      "description": "One output field of a mapping (§4.1): a #fieldDefinition plus how this source fills it. An unmapped field has no `expr`. `confidence`, a number from 0 to 1, is proposal metadata; the lexicon language has no non-integer number type, so it is not declared.",
203 +
      "description": "One output field of a mapping (§4.1): a #fieldDefinition plus how this source fills it. An unmapped field has no `expr`.",
190 204
      "properties": {
191 205
        "by": {
192 206
          "enum": [
193 207
            "constraint",
194 208
            "name",
195 209
            "stats",
196 210
            "model",
197 211
            "human"
198 212
          ],
199 213
          "type": "string"
200 214
        },
215 +
        "confidence": {
216 +
          "description": "Proposal metadata: a number from 0 to 1, in fixed point, the value times 10⁶ (§1.5), as a bare integer: 0.4 is 400000.",
217 +
          "maximum": 1000000,
218 +
          "minimum": 0,
219 +
          "type": "integer"
220 +
        },
201 221
        "expr": {
202 222
          "ref": "#mappingExpression",
203 223
          "type": "ref"
204 224
        },
205 225
        "normalization": {
206 226
          "ref": "#normalization",
207 227
          "type": "ref"
208 228
        },
209 229
        "required": {
210 230
          "type": "boolean"
211 231
        },
212 232
        "type": {
213 233
          "ref": "#fieldType",
214 234
          "type": "ref"
215 235
        }
216 236
      },
217 237
      "required": [
218 238
        "type",
219 239
        "required"
220 240
      ],
221 241
      "type": "object"
222 242
    },
223 243
    "normalization": {
224 -
      "description": "Source-specific input normalization (§4.1), not part of the shared field type. A mapped score field needs {kind: score, scale}, a mapped media field {kind: media, mediaKind}, and canonical {namespace} belongs on a uri field only. `scale` is {min, max}, finite numbers with min < max; the lexicon language has no non-integer number type, so they are not declared.",
244 +
      "description": "Source-specific input normalization (§4.1), not part of the shared field type. A mapped score field needs {kind: score, scale}, a mapped media field {kind: media, mediaKind}, and canonical {namespace} belongs on a uri field only.",
225 245
      "properties": {
226 246
        "kind": {
227 247
          "enum": [
228 248
            "score",
229 249
            "media",
230 250
            "canonical"
231 251
          ],
232 252
          "type": "string"
233 253
        },
234 254
        "mediaKind": {
235 255
          "enum": [
236 256
            "blob",
237 257
            "url"
238 258
          ],
239 259
          "type": "string"
240 260
        },
241 261
        "namespace": {
242 262
          "ref": "#keyNamespace",
243 263
          "type": "ref"
244 264
        },
245 265
        "scale": {
246 266
          "ref": "#scale",
247 267
          "type": "ref"
248 268
        }
249 269
      },
250 270
      "required": [
251 271
        "kind"
252 272
      ],
253 273
      "type": "object"
254 274
    },
255 275
    "operationName": {
256 276
      "description": "§4.3's closed list of operations. A new operation is a spec change, not an extension point.",
257 277
      "enum": [
258 278
        "split",
259 279
        "join",
260 280
        "wrap",
261 281
        "first",
262 282
        "trim",
263 283
        "lower",
264 284
        "replace",
265 285
        "divide",
266 286
        "parseDate",
267 287
        "coalesce",
268 288
        "concat"
269 289
      ],
270 290
      "type": "string"
271 291
    },
272 292
    "origin": {
273 293
      "description": "The original record an independent copy was made from (§1.3, §8.3).",
274 294
      "properties": {
275 295
        "cid": {
276 296
          "description": "The CID of the exact version copied. The `cid` format accepts any spelling of a CID; the validator accepts only the form atproto writes: version 1, in base32, over a SHA-256 digest.",
277 297
          "format": "cid",
278 298
          "type": "string"
279 299
        },
280 300
        "uri": {
281 301
          "description": "The record copied. Not stated by the lexicon, and enforced by the validator: exactly at://<DID>/<collection NSID>/<record key>, with a DID rather than a handle for its authority.",
282 302
          "format": "at-uri",
283 303
          "type": "string"
284 304
        }
285 305
      },
286 306
      "required": [
287 307
        "uri",
288 308
        "cid"
289 309
      ],
290 310
      "type": "object"
291 311
    },
292 312
    "predicateOp": {
293 313
      "description": "§5's closed list of operators.",
294 314
      "enum": [
295 315
        "eq",
296 316
        "ne",
297 317
        "lt",
298 318
        "lte",
299 319
        "gt",
300 320
        "gte",
301 321
        "contains",
302 322
        "notContains",
303 323
        "startsWith",
304 324
        "matches",
305 325
        "exists",
306 326
        "notExists",
307 327
        "in",
308 328
        "notIn"
309 329
      ],
310 330
      "type": "string"
311 331
    },
312 332
    "presentation": {
313 333
      "description": "How the output is shown (§3.8); it never changes data or requiredness. Entries are unique by `field`, each names a field of the shape it presents, and `display` is allowed on a score field only.",
314 334
      "properties": {
315 335
        "fields": {
316 336
          "items": {
317 337
            "ref": "#presentationField",
318 338
            "type": "ref"
319 339
          },
320 340
          "type": "array"
321 341
        },
322 342
        "layout": {
323 343
          "enum": [
324 344
            "list",
325 345
            "table",
326 346
            "review",
327 347
            "bookmark"
328 348
          ],
329 349
          "type": "string"
330 350
        }
331 351
      },
332 352
      "required": [
333 353
        "layout",
334 354
        "fields"
335 355
      ],
336 356
      "type": "object"
337 357
    },
338 358
    "presentationField": {
339 359
      "properties": {
340 360
        "display": {
341 361
          "enum": [
342 362
            "stars5",
343 363
            "stars10",
344 364
            "percent",
345 365
            "raw"
346 366
          ],
347 367
          "type": "string"
348 368
        },
349 369
        "field": {
350 370
          "minLength": 1,
351 371
          "type": "string"
352 372
        },
353 373
        "label": {
354 374
          "type": "string"
355 375
        },
356 376
        "visible": {
357 377
          "type": "boolean"
358 378
        }
359 379
      },
360 380
      "required": [
361 381
        "field",
362 382
        "visible"
363 383
      ],
364 384
      "type": "object"
365 385
    },
366 386
    "scale": {
367 -
      "description": "A source's score scale (§3.4): {min, max}, finite numbers with min < max, not declared because they need not be integers.",
387 +
      "description": "A source's score scale (§3.4), each bound in fixed point, the value times 10⁶ (§1.5), as a bare integer: a scale of 1 to 10 is 1000000 to 10000000. Not stated by the lexicon, and enforced by the validator: min < max.",
388 +
      "properties": {
389 +
        "max": {
390 +
          "maximum": 999999999999999,
391 +
          "minimum": -999999999999999,
392 +
          "type": "integer"
393 +
        },
394 +
        "min": {
395 +
          "maximum": 999999999999999,
396 +
          "minimum": -999999999999999,
397 +
          "type": "integer"
398 +
        }
399 +
      },
400 +
      "required": [
401 +
        "min",
402 +
        "max"
403 +
      ],
368 404
      "type": "object"
369 405
    },
370 406
    "schemaPin": {
371 407
      "description": "The schema version the mapping was written against (§4.1, §11). Not stated by the lexicon, and enforced by the validator: `nsid` is the mapping's `source`; a published pin has `cid` and no `hash`, an inferred one `hash` and no `cid`.",
372 408
      "properties": {
373 409
        "cid": {
374 410
          "description": "A published pin's version: the CID of the lexicon record. The `cid` format accepts any spelling of a CID; the validator accepts only the form atproto writes: version 1, in base32, over a SHA-256 digest.",
375 411
          "format": "cid",
376 412
          "type": "string"
377 413
        },
378 414
        "hash": {
379 415
          "description": "An inferred pin's version: `sha256:` and 64 lowercase hex digits, a form the lexicon language cannot state.",
380 416
          "minLength": 1,
381 417
          "type": "string"
382 418
        },
383 419
        "nsid": {
384 420
          "format": "nsid",
385 421
          "type": "string"
386 422
        },
387 423
        "provenance": {
388 424
          "enum": [
389 425
            "published",
390 426
            "inferred"
391 427
          ],
392 428
          "type": "string"
393 429
        },
394 430
        "resolvedAt": {
395 431
          "description": "When the version was resolved: a datetime as §3.1 reads one, which accepts forms atproto's `datetime` format does not, so the format is not declared.",
396 432
          "type": "string"
397 433
        }
398 434
      },
399 435
      "required": [
400 436
        "nsid",
401 437
        "provenance"
402 438
      ],
403 439
      "type": "object"
404 440
    }
405 441
  },
406 -
  "description": "A Tensei mapping, version 1 (spec §4): records of one source collection mapped into output fields the user authors. Built-in presets are starting points, never a contract: any field name is allowed, and a field is mandatory only when the user marks it `required`. Where the lexicon language cannot state a rule the version-1 validator enforces, the property is left permissive here and the rule is named in its description.",
442 +
  "description": "A Tensei mapping, version 1 (spec §4): records of one source collection mapped into output fields the user authors. Built-in presets are starting points, never a contract: any field name is allowed, and a field is mandatory only when the user marks it `required`. The atproto data model has integers only, so a number in the record is an integer written as it is, or any other number in fixed point, the value times 10⁶ (spec §1.5): a bare integer where this lexicon declares it — a #mappingField's `confidence` and a #scale's `min` and `max` — and a #fixedPoint everywhere else. Where the lexicon language cannot state a rule the version-1 validator enforces, the property is left permissive here and the rule is named in its description.",
407 443
  "id": "at.tensei.mapping",
408 444
  "lexicon": 1
409 445
}

Compare Other Versions

Lexicon Garden

@