refactor(scripts): key persistence schema nodes by subgraph digest

Store one node table keyed by the 16-hex digest prefix of each node's
canonical subgraph instead of per-root graphs with numeric indices, so a
type change rewrites only the changed nodes and their ancestors.

Refs #6597
This commit is contained in:
turtle1999
2026-10-19 18:37:48 +08:00
parent 85f4eca731
commit 1edcd49192
63 changed files with 13147 additions and 662264 deletions
File diff suppressed because it is too large Load Diff
@@ -1,910 +1,58 @@
{
"formatVersion": 1,
"roots": [
{
"key": "event:tool/ptc-dispatch",
"kind": "event",
"event": "tool/ptc-dispatch",
"surface": false,
"digest": "b5d66eaebed4da391b13498623b11142222149fbf5e025975dbc6d94f0d06796",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 29,
"optional": true
},
{
"name": "seq",
"type": 17,
"optional": false
},
{
"name": "time",
"type": 17,
"optional": false
},
{
"name": "type",
"type": 30,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "arguments",
"type": 2,
"optional": false
},
{
"name": "content",
"type": 3,
"optional": false
},
{
"name": "error",
"type": 28,
"optional": true
},
{
"name": "isError",
"type": 6,
"optional": false
},
{
"name": "name",
"type": 7,
"optional": false
},
{
"name": "parentCallId",
"type": 7,
"optional": false
},
{
"name": "rootCallId",
"type": 7,
"optional": false
},
{
"name": "subCallId",
"type": 7,
"optional": false
}
],
"indices": []
},
{
"kind": "opaque",
"reason": "unknown"
},
{
"kind": "array",
"element": 4
},
{
"kind": "union",
"types": [
5,
9,
11,
13,
15,
25
]
},
{
"kind": "object",
"properties": [
{
"name": "content",
"type": 3,
"optional": false
},
{
"name": "isError",
"type": 6,
"optional": true
},
{
"name": "toolCallId",
"type": 7,
"optional": false
},
{
"name": "type",
"type": 8,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "boolean"
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "literal",
"value": "tool-result"
},
{
"kind": "object",
"properties": [
{
"name": "text",
"type": 7,
"optional": false
},
{
"name": "type",
"type": 10,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "reasoning"
},
{
"kind": "object",
"properties": [
{
"name": "text",
"type": 7,
"optional": false
},
{
"name": "type",
"type": 12,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "text"
},
{
"kind": "object",
"properties": [
{
"name": "arguments",
"type": 7,
"optional": false
},
{
"name": "id",
"type": 7,
"optional": false
},
{
"name": "name",
"type": 7,
"optional": false
},
{
"name": "type",
"type": 14,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "tool-call"
},
{
"kind": "object",
"properties": [
{
"name": "attachment",
"type": 16,
"optional": false
},
{
"name": "type",
"type": 24,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "attachmentId",
"type": 7,
"optional": false
},
{
"name": "bytes",
"type": 17,
"optional": false
},
{
"name": "height",
"type": 17,
"optional": false
},
{
"name": "mediaType",
"type": 18,
"optional": false
},
{
"name": "name",
"type": 7,
"optional": true
},
{
"name": "originalDimensions",
"type": 23,
"optional": true
},
{
"name": "width",
"type": 17,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "union",
"types": [
19,
20,
21,
22
]
},
{
"kind": "literal",
"value": "image/gif"
},
{
"kind": "literal",
"value": "image/jpeg"
},
{
"kind": "literal",
"value": "image/png"
},
{
"kind": "literal",
"value": "image/webp"
},
{
"kind": "object",
"properties": [
{
"name": "height",
"type": 17,
"optional": false
},
{
"name": "width",
"type": 17,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "image"
},
{
"kind": "object",
"properties": [
{
"name": "attachment",
"type": 26,
"optional": false
},
{
"name": "type",
"type": 27,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "attachmentId",
"type": 7,
"optional": false
},
{
"name": "bytes",
"type": 17,
"optional": false
},
{
"name": "name",
"type": 7,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "file"
},
{
"kind": "object",
"properties": [
{
"name": "code",
"type": 7,
"optional": false
},
{
"name": "name",
"type": 7,
"optional": false
},
{
"name": "reason",
"type": 7,
"optional": true
}
],
"indices": []
},
{
"kind": "literal",
"value": true
},
{
"kind": "literal",
"value": "tool/ptc-dispatch"
}
]
}
},
{
"key": "event:tool/result",
"kind": "event",
"event": "tool/result",
"surface": true,
"digest": "3a803805bdeb805f32b229e399fb7258be8e32a89f89063a7c99957cfea942f7",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 37,
"optional": true
},
{
"name": "seq",
"type": 17,
"optional": false
},
{
"name": "sourceEventSeqs",
"type": 38,
"optional": true
},
{
"name": "surfaceOp",
"type": 39,
"optional": false
},
{
"name": "time",
"type": 17,
"optional": false
},
{
"name": "type",
"type": 43,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "error",
"type": 2,
"optional": true
},
{
"name": "message",
"type": 4,
"optional": false
},
{
"name": "meta",
"type": 33,
"optional": true
},
{
"name": "step",
"type": 17,
"optional": false
},
{
"name": "turn",
"type": 17,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "code",
"type": 3,
"optional": false
},
{
"name": "name",
"type": 3,
"optional": false
},
{
"name": "reason",
"type": 3,
"optional": true
}
],
"indices": []
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "object",
"properties": [
{
"name": "content",
"type": 5,
"optional": false
},
{
"name": "id",
"type": 3,
"optional": false
},
{
"name": "role",
"type": 30,
"optional": false
},
{
"name": "source",
"type": 31,
"optional": false
}
],
"indices": []
},
{
"kind": "tuple",
"elements": [
{
"type": 6,
"optional": false,
"rest": false
}
]
},
{
"kind": "object",
"properties": [
{
"name": "content",
"type": 7,
"optional": false
},
{
"name": "isError",
"type": 28,
"optional": true
},
{
"name": "toolCallId",
"type": 3,
"optional": false
},
{
"name": "type",
"type": 29,
"optional": false
}
],
"indices": []
},
{
"kind": "array",
"element": 8
},
{
"kind": "union",
"types": [
6,
9,
11,
13,
15,
25
]
},
{
"kind": "object",
"properties": [
{
"name": "text",
"type": 3,
"optional": false
},
{
"name": "type",
"type": 10,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "text"
},
{
"kind": "object",
"properties": [
{
"name": "text",
"type": 3,
"optional": false
},
{
"name": "type",
"type": 12,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "reasoning"
},
{
"kind": "object",
"properties": [
{
"name": "arguments",
"type": 3,
"optional": false
},
{
"name": "id",
"type": 3,
"optional": false
},
{
"name": "name",
"type": 3,
"optional": false
},
{
"name": "type",
"type": 14,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "tool-call"
},
{
"kind": "object",
"properties": [
{
"name": "attachment",
"type": 16,
"optional": false
},
{
"name": "type",
"type": 24,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "attachmentId",
"type": 3,
"optional": false
},
{
"name": "bytes",
"type": 17,
"optional": false
},
{
"name": "height",
"type": 17,
"optional": false
},
{
"name": "mediaType",
"type": 18,
"optional": false
},
{
"name": "name",
"type": 3,
"optional": true
},
{
"name": "originalDimensions",
"type": 23,
"optional": true
},
{
"name": "width",
"type": 17,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "union",
"types": [
19,
20,
21,
22
]
},
{
"kind": "literal",
"value": "image/gif"
},
{
"kind": "literal",
"value": "image/jpeg"
},
{
"kind": "literal",
"value": "image/png"
},
{
"kind": "literal",
"value": "image/webp"
},
{
"kind": "object",
"properties": [
{
"name": "height",
"type": 17,
"optional": false
},
{
"name": "width",
"type": 17,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "image"
},
{
"kind": "object",
"properties": [
{
"name": "attachment",
"type": 26,
"optional": false
},
{
"name": "type",
"type": 27,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "attachmentId",
"type": 3,
"optional": false
},
{
"name": "bytes",
"type": 17,
"optional": false
},
{
"name": "name",
"type": 3,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "file"
},
{
"kind": "primitive",
"type": "boolean"
},
{
"kind": "literal",
"value": "tool-result"
},
{
"kind": "literal",
"value": "user"
},
{
"kind": "object",
"properties": [
{
"name": "callId",
"type": 3,
"optional": false
},
{
"name": "kind",
"type": 32,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "tool"
},
{
"kind": "union",
"types": [
3,
17,
28,
34,
35,
36
]
},
{
"kind": "array",
"element": 33
},
{
"kind": "primitive",
"type": "null"
},
{
"kind": "object",
"properties": [],
"indices": [
{
"key": 3,
"value": 33
}
]
},
{
"kind": "literal",
"value": true
},
{
"kind": "array",
"element": 17
},
{
"kind": "union",
"types": [
40,
41
]
},
{
"kind": "literal",
"value": "append"
},
{
"kind": "object",
"properties": [
{
"name": "endSeq",
"type": 17,
"optional": false
},
{
"name": "op",
"type": 42,
"optional": false
},
{
"name": "startSeq",
"type": 17,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "replace"
},
{
"kind": "literal",
"value": "tool/result"
}
]
}
}
{"key":"event:tool/ptc-dispatch","kind":"event","event":"tool/ptc-dispatch","surface":false,"digest":"b5d66eaebed4da391b13498623b11142222149fbf5e025975dbc6d94f0d06796"},
{"key":"event:tool/result","kind":"event","event":"tool/result","surface":true,"digest":"3a803805bdeb805f32b229e399fb7258be8e32a89f89063a7c99957cfea942f7"}
],
"types": []
"types": [],
"nodes": {
"0657d3ca5ac4e7bf": {"kind":"primitive","type":"number"},
"0e560aad8fa9f338": {"kind":"array","element":"e1161343902a8da6"},
"0f46153645c29784": {"kind":"object","properties":[{"name":"height","type":"0657d3ca5ac4e7bf","optional":false},{"name":"width","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]},
"1f0133217eeeffd8": {"kind":"literal","value":"tool-call"},
"277b61f9c6f81704": {"kind":"literal","value":"file"},
"28aa673866746f44": {"kind":"object","properties":[{"name":"error","type":"a81059ca06999da1","optional":true},{"name":"message","type":"966fad765834f620","optional":false},{"name":"meta","type":"c592ce75aab73fca","optional":true},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]},
"2f1e6a7ebace43d7": {"kind":"union","types":["3f74ba74acd10644","58f94390b982f8a5","de30b1fa12c75f81","ebb768d85ec87fb6"]},
"335e242de1fcc17b": {"kind":"union","types":["bcf0caf62d964b2f","e438a9d4fb883b3e"]},
"3a803805bdeb805f": {"kind":"object","properties":[{"name":"data","type":"28aa673866746f44","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"570e850224346133","optional":false}],"indices":[]},
"3c2535ca0a2062f1": {"kind":"literal","value":"reasoning"},
"3f74ba74acd10644": {"kind":"literal","value":"image/jpeg"},
"4c12ebe118c00be8": {"kind":"object","properties":[{"name":"arguments","type":"bbc5696e3f13c758","optional":false},{"name":"content","type":"0e560aad8fa9f338","optional":false},{"name":"error","type":"a81059ca06999da1","optional":true},{"name":"isError","type":"aa93010d0e521ad7","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"parentCallId","type":"4cd48f3e5108bd6e","optional":false},{"name":"rootCallId","type":"4cd48f3e5108bd6e","optional":false},{"name":"subCallId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]},
"4cd48f3e5108bd6e": {"kind":"primitive","type":"string"},
"4e44c9118152f682": {"kind":"literal","value":"text"},
"4e88776858652856": {"kind":"object","properties":[{"name":"text","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"4e44c9118152f682","optional":false}],"indices":[]},
"529e0ccd0e34079a": {"kind":"object","properties":[],"indices":[{"key":"4cd48f3e5108bd6e","value":"c592ce75aab73fca"}]},
"570e850224346133": {"kind":"literal","value":"tool/result"},
"58f94390b982f8a5": {"kind":"literal","value":"image/png"},
"5d03ba38734809bc": {"kind":"array","element":"0657d3ca5ac4e7bf"},
"5eedf09e88b5ed9c": {"kind":"tuple","elements":[{"type":"9bdd77f2b39d9ae4","optional":false,"rest":false}]},
"68f44f59e91c4139": {"kind":"literal","value":"user"},
"72ec79127a9c0d02": {"kind":"array","element":"c592ce75aab73fca"},
"814d9434e1ddf907": {"kind":"object","properties":[{"name":"text","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"3c2535ca0a2062f1","optional":false}],"indices":[]},
"8ef06dd236128246": {"kind":"object","properties":[{"name":"attachment","type":"b0b0eb1b16490f6b","optional":false},{"name":"type","type":"fb8b6a2e53cb9cf4","optional":false}],"indices":[]},
"938f7c7357bc403e": {"kind":"primitive","type":"null"},
"966fad765834f620": {"kind":"object","properties":[{"name":"content","type":"5eedf09e88b5ed9c","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"68f44f59e91c4139","optional":false},{"name":"source","type":"d9c462b9f62fdd3e","optional":false}],"indices":[]},
"9bdd77f2b39d9ae4": {"kind":"object","properties":[{"name":"content","type":"0e560aad8fa9f338","optional":false},{"name":"isError","type":"aa93010d0e521ad7","optional":true},{"name":"toolCallId","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"e5244812c495a80c","optional":false}],"indices":[]},
"a13d8f379222ece8": {"kind":"literal","value":"tool"},
"a81059ca06999da1": {"kind":"object","properties":[{"name":"code","type":"4cd48f3e5108bd6e","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"reason","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]},
"aa93010d0e521ad7": {"kind":"primitive","type":"boolean"},
"b0b0eb1b16490f6b": {"kind":"object","properties":[{"name":"attachmentId","type":"4cd48f3e5108bd6e","optional":false},{"name":"bytes","type":"0657d3ca5ac4e7bf","optional":false},{"name":"height","type":"0657d3ca5ac4e7bf","optional":false},{"name":"mediaType","type":"2f1e6a7ebace43d7","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":true},{"name":"originalDimensions","type":"0f46153645c29784","optional":true},{"name":"width","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]},
"b5d66eaebed4da39": {"kind":"object","properties":[{"name":"data","type":"4c12ebe118c00be8","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"fb5e0517050d2c6d","optional":false}],"indices":[]},
"bbc5696e3f13c758": {"kind":"opaque","reason":"unknown"},
"bcf0caf62d964b2f": {"kind":"object","properties":[{"name":"endSeq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"op","type":"f6cb462f808309e3","optional":false},{"name":"startSeq","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]},
"c1f0f17b6493b85d": {"kind":"object","properties":[{"name":"arguments","type":"4cd48f3e5108bd6e","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"1f0133217eeeffd8","optional":false}],"indices":[]},
"c592ce75aab73fca": {"kind":"union","types":["0657d3ca5ac4e7bf","4cd48f3e5108bd6e","529e0ccd0e34079a","72ec79127a9c0d02","938f7c7357bc403e","aa93010d0e521ad7"]},
"d9c462b9f62fdd3e": {"kind":"object","properties":[{"name":"callId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"a13d8f379222ece8","optional":false}],"indices":[]},
"dacf3d5f3e4ff619": {"kind":"literal","value":true},
"de30b1fa12c75f81": {"kind":"literal","value":"image/gif"},
"e1161343902a8da6": {"kind":"union","types":["4e88776858652856","814d9434e1ddf907","8ef06dd236128246","9bdd77f2b39d9ae4","c1f0f17b6493b85d","f2da0f27c1e6add5"]},
"e438a9d4fb883b3e": {"kind":"literal","value":"append"},
"e5244812c495a80c": {"kind":"literal","value":"tool-result"},
"ebb768d85ec87fb6": {"kind":"literal","value":"image/webp"},
"f19bee84bf3d2090": {"kind":"object","properties":[{"name":"attachmentId","type":"4cd48f3e5108bd6e","optional":false},{"name":"bytes","type":"0657d3ca5ac4e7bf","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]},
"f2da0f27c1e6add5": {"kind":"object","properties":[{"name":"attachment","type":"f19bee84bf3d2090","optional":false},{"name":"type","type":"277b61f9c6f81704","optional":false}],"indices":[]},
"f6cb462f808309e3": {"kind":"literal","value":"replace"},
"fb5e0517050d2c6d": {"kind":"literal","value":"tool/ptc-dispatch"},
"fb8b6a2e53cb9cf4": {"kind":"literal","value":"image"}
}
}
@@ -1,76 +1,15 @@
{
"formatVersion": 1,
"roots": [
{
"key": "event:working-directory/change",
"kind": "event",
"event": "working-directory/change",
"surface": false,
"digest": "124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 3,
"optional": true
},
{
"name": "seq",
"type": 4,
"optional": false
},
{
"name": "time",
"type": 4,
"optional": false
},
{
"name": "type",
"type": 5,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "cwd",
"type": 2,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "literal",
"value": true
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "literal",
"value": "working-directory/change"
}
]
}
}
{"key":"event:working-directory/change","kind":"event","event":"working-directory/change","surface":false,"digest":"124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3"}
],
"types": []
"types": [],
"nodes": {
"0657d3ca5ac4e7bf": {"kind":"primitive","type":"number"},
"124aa62078ca3311": {"kind":"object","properties":[{"name":"data","type":"da60ed58a8fa23d7","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"d4c799e3d51cae10","optional":false}],"indices":[]},
"4cd48f3e5108bd6e": {"kind":"primitive","type":"string"},
"d4c799e3d51cae10": {"kind":"literal","value":"working-directory/change"},
"da60ed58a8fa23d7": {"kind":"object","properties":[{"name":"cwd","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]},
"dacf3d5f3e4ff619": {"kind":"literal","value":true}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,72 +1,14 @@
{
"formatVersion": 1,
"roots": [
{
"key": "event:workspace/changes",
"kind": "event",
"event": "workspace/changes",
"surface": false,
"digest": "e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 3,
"optional": true
},
{
"name": "seq",
"type": 2,
"optional": false
},
{
"name": "time",
"type": 2,
"optional": false
},
{
"name": "type",
"type": 4,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "turn",
"type": 2,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "literal",
"value": true
},
{
"kind": "literal",
"value": "workspace/changes"
}
]
}
}
{"key":"event:workspace/changes","kind":"event","event":"workspace/changes","surface":false,"digest":"e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72"}
],
"types": []
"types": [],
"nodes": {
"0657d3ca5ac4e7bf": {"kind":"primitive","type":"number"},
"8e8155594e078123": {"kind":"literal","value":"workspace/changes"},
"dacf3d5f3e4ff619": {"kind":"literal","value":true},
"e308ccf867a5398e": {"kind":"object","properties":[{"name":"data","type":"f90eb4ab2d3897bc","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"8e8155594e078123","optional":false}],"indices":[]},
"f90eb4ab2d3897bc": {"kind":"object","properties":[{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,302 +1,30 @@
{
"formatVersion": 1,
"roots": [
{
"key": "event:schedule/change",
"kind": "event",
"event": "schedule/change",
"surface": false,
"digest": "a0a2e5c42e1c929445ecd1cd70f49be6b66441894ec72c08e8ae332821d4a3cb",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 19,
"optional": true
},
{
"name": "seq",
"type": 13,
"optional": false
},
{
"name": "time",
"type": 13,
"optional": false
},
{
"name": "type",
"type": 20,
"optional": false
}
],
"indices": []
},
{
"kind": "union",
"types": [
2,
6,
8,
9
]
},
{
"kind": "object",
"properties": [
{
"name": "acceptedAt",
"type": 3,
"optional": false
},
{
"name": "id",
"type": 3,
"optional": false
},
{
"name": "operation",
"type": 4,
"optional": false
},
{
"name": "version",
"type": 5,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "literal",
"value": "dispatch"
},
{
"kind": "literal",
"value": 1
},
{
"kind": "object",
"properties": [
{
"name": "id",
"type": 3,
"optional": false
},
{
"name": "operation",
"type": 7,
"optional": false
},
{
"name": "version",
"type": 5,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "delete"
},
{
"kind": "object",
"properties": [
{
"name": "id",
"type": 3,
"optional": false
},
{
"name": "operation",
"type": 4,
"optional": false
},
{
"name": "version",
"type": 5,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "operation",
"type": 10,
"optional": false
},
{
"name": "schedule",
"type": 11,
"optional": false
},
{
"name": "version",
"type": 5,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "create"
},
{
"kind": "union",
"types": [
12,
15,
17
]
},
{
"kind": "object",
"properties": [
{
"name": "afterSeconds",
"type": 13,
"optional": false
},
{
"name": "id",
"type": 3,
"optional": false
},
{
"name": "kind",
"type": 14,
"optional": false
},
{
"name": "prompt",
"type": 3,
"optional": false
},
{
"name": "scheduledAt",
"type": 3,
"optional": false
},
{
"name": "title",
"type": 3,
"optional": true
}
],
"indices": []
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "literal",
"value": "after"
},
{
"kind": "object",
"properties": [
{
"name": "everySeconds",
"type": 13,
"optional": false
},
{
"name": "id",
"type": 3,
"optional": false
},
{
"name": "kind",
"type": 16,
"optional": false
},
{
"name": "prompt",
"type": 3,
"optional": false
},
{
"name": "scheduledAt",
"type": 3,
"optional": false
},
{
"name": "title",
"type": 3,
"optional": true
}
],
"indices": []
},
{
"kind": "literal",
"value": "every"
},
{
"kind": "object",
"properties": [
{
"name": "id",
"type": 3,
"optional": false
},
{
"name": "kind",
"type": 18,
"optional": false
},
{
"name": "prompt",
"type": 3,
"optional": false
},
{
"name": "scheduledAt",
"type": 3,
"optional": false
},
{
"name": "title",
"type": 3,
"optional": true
}
],
"indices": []
},
{
"kind": "literal",
"value": "at"
},
{
"kind": "literal",
"value": true
},
{
"kind": "literal",
"value": "schedule/change"
}
]
}
}
{"key":"event:schedule/change","kind":"event","event":"schedule/change","surface":false,"digest":"a0a2e5c42e1c929445ecd1cd70f49be6b66441894ec72c08e8ae332821d4a3cb"}
],
"types": []
"types": [],
"nodes": {
"0657d3ca5ac4e7bf": {"kind":"primitive","type":"number"},
"0e7b2a97f54f1afd": {"kind":"literal","value":"schedule/change"},
"17aae616ef020b8b": {"kind":"literal","value":"at"},
"18dc21f12bbac8ad": {"kind":"object","properties":[{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"operation","type":"d0fbb0ca1bbba059","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]},
"26c879bdd130e8b8": {"kind":"literal","value":"dispatch"},
"2e3523541e942ef6": {"kind":"union","types":["18dc21f12bbac8ad","8e8e91646a5cae3f","e6063d0451dc0202","eea687d437880bd5"]},
"3fdf9582c3b5597d": {"kind":"object","properties":[{"name":"afterSeconds","type":"0657d3ca5ac4e7bf","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"5b232a9b9bfcc0fc","optional":false},{"name":"prompt","type":"4cd48f3e5108bd6e","optional":false},{"name":"scheduledAt","type":"4cd48f3e5108bd6e","optional":false},{"name":"title","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]},
"4b1e632e05776982": {"kind":"union","types":["3fdf9582c3b5597d","a926dc71814edd01","b63b089a00b3b935"]},
"4cd48f3e5108bd6e": {"kind":"primitive","type":"string"},
"5b232a9b9bfcc0fc": {"kind":"literal","value":"after"},
"7624ca28c61ca0ea": {"kind":"literal","value":"create"},
"8e8e91646a5cae3f": {"kind":"object","properties":[{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"operation","type":"26c879bdd130e8b8","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]},
"a0a2e5c42e1c9294": {"kind":"object","properties":[{"name":"data","type":"2e3523541e942ef6","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"0e7b2a97f54f1afd","optional":false}],"indices":[]},
"a926dc71814edd01": {"kind":"object","properties":[{"name":"everySeconds","type":"0657d3ca5ac4e7bf","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"e42b0f40b5d0b628","optional":false},{"name":"prompt","type":"4cd48f3e5108bd6e","optional":false},{"name":"scheduledAt","type":"4cd48f3e5108bd6e","optional":false},{"name":"title","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]},
"b63b089a00b3b935": {"kind":"object","properties":[{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"17aae616ef020b8b","optional":false},{"name":"prompt","type":"4cd48f3e5108bd6e","optional":false},{"name":"scheduledAt","type":"4cd48f3e5108bd6e","optional":false},{"name":"title","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]},
"d0fbb0ca1bbba059": {"kind":"literal","value":"delete"},
"dacf3d5f3e4ff619": {"kind":"literal","value":true},
"e42b0f40b5d0b628": {"kind":"literal","value":"every"},
"e6063d0451dc0202": {"kind":"object","properties":[{"name":"acceptedAt","type":"4cd48f3e5108bd6e","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"operation","type":"26c879bdd130e8b8","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]},
"eea687d437880bd5": {"kind":"object","properties":[{"name":"operation","type":"7624ca28c61ca0ea","optional":false},{"name":"schedule","type":"4b1e632e05776982","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]},
"f88f1b7b6bb733f6": {"kind":"literal","value":1}
}
}
@@ -1,290 +1,28 @@
{
"formatVersion": 2,
"roots": [
{
"key": "event:subagent/catalog",
"kind": "event",
"event": "subagent/catalog",
"surface": false,
"digest": "53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 17,
"optional": true
},
{
"name": "seq",
"type": 3,
"optional": false
},
{
"name": "time",
"type": 3,
"optional": false
},
{
"name": "type",
"type": 18,
"optional": false
}
],
"indices": []
},
{
"kind": "union",
"types": [
2,
7,
9,
12,
14,
15
]
},
{
"kind": "object",
"properties": [
{
"name": "childCreatedAt",
"type": 3,
"optional": false
},
{
"name": "childId",
"type": 4,
"optional": false
},
{
"name": "label",
"type": 4,
"optional": false
},
{
"name": "mode",
"type": 5,
"optional": false
},
{
"name": "version",
"type": 6,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "literal",
"value": "continuable"
},
{
"kind": "literal",
"value": 0
},
{
"kind": "object",
"properties": [
{
"name": "childCreatedAt",
"type": 3,
"optional": false
},
{
"name": "childId",
"type": 4,
"optional": false
},
{
"name": "label",
"type": 4,
"optional": false
},
{
"name": "mode",
"type": 5,
"optional": false
},
{
"name": "version",
"type": 8,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": 1
},
{
"kind": "object",
"properties": [
{
"name": "childCreatedAt",
"type": 3,
"optional": false
},
{
"name": "childId",
"type": 4,
"optional": false
},
{
"name": "label",
"type": 4,
"optional": true
},
{
"name": "mode",
"type": 10,
"optional": false
},
{
"name": "version",
"type": 11,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "external"
},
{
"kind": "literal",
"value": 2
},
{
"kind": "object",
"properties": [
{
"name": "childCreatedAt",
"type": 3,
"optional": false
},
{
"name": "childId",
"type": 4,
"optional": false
},
{
"name": "label",
"type": 4,
"optional": true
},
{
"name": "mode",
"type": 13,
"optional": false
},
{
"name": "version",
"type": 6,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "one-shot"
},
{
"kind": "object",
"properties": [
{
"name": "childCreatedAt",
"type": 3,
"optional": false
},
{
"name": "childId",
"type": 4,
"optional": false
},
{
"name": "label",
"type": 4,
"optional": true
},
{
"name": "mode",
"type": 13,
"optional": false
},
{
"name": "version",
"type": 8,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "childCreatedAt",
"type": 3,
"optional": false
},
{
"name": "childId",
"type": 4,
"optional": false
},
{
"name": "label",
"type": 4,
"optional": true
},
{
"name": "mode",
"type": 16,
"optional": false
},
{
"name": "version",
"type": 8,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "unknown"
},
{
"kind": "literal",
"value": true
},
{
"kind": "literal",
"value": "subagent/catalog"
}
]
}
}
{"key":"event:subagent/catalog","kind":"event","event":"subagent/catalog","surface":false,"digest":"53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac"}
],
"types": []
"types": [],
"nodes": {
"05a2796621b1ee6c": {"kind":"literal","value":"unknown"},
"0657d3ca5ac4e7bf": {"kind":"primitive","type":"number"},
"0e10d7c096b47e61": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":false},{"name":"mode","type":"e5d3828df1ec3e2a","optional":false},{"name":"version","type":"93d38436347f67c9","optional":false}],"indices":[]},
"44c8c77aa9aa100b": {"kind":"literal","value":"subagent/catalog"},
"4cd48f3e5108bd6e": {"kind":"primitive","type":"string"},
"53750b9abf61efb7": {"kind":"object","properties":[{"name":"data","type":"f731ffe80810b028","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"44c8c77aa9aa100b","optional":false}],"indices":[]},
"5d542ac7bdf13310": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":true},{"name":"mode","type":"d28765f87d6b6ab4","optional":false},{"name":"version","type":"93d38436347f67c9","optional":false}],"indices":[]},
"93d38436347f67c9": {"kind":"literal","value":0},
"a0f25b5aeeebf2bf": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":true},{"name":"mode","type":"05a2796621b1ee6c","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]},
"a0f906856fc06f1c": {"kind":"literal","value":2},
"b6896938660a1dfe": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":true},{"name":"mode","type":"d28765f87d6b6ab4","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]},
"cbbe8377ee744964": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":true},{"name":"mode","type":"fc351843b8ecbd05","optional":false},{"name":"version","type":"a0f906856fc06f1c","optional":false}],"indices":[]},
"d28765f87d6b6ab4": {"kind":"literal","value":"one-shot"},
"dacf3d5f3e4ff619": {"kind":"literal","value":true},
"e2ad8f5bcd813f26": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":false},{"name":"mode","type":"e5d3828df1ec3e2a","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]},
"e5d3828df1ec3e2a": {"kind":"literal","value":"continuable"},
"f731ffe80810b028": {"kind":"union","types":["0e10d7c096b47e61","5d542ac7bdf13310","a0f25b5aeeebf2bf","b6896938660a1dfe","cbbe8377ee744964","e2ad8f5bcd813f26"]},
"f88f1b7b6bb733f6": {"kind":"literal","value":1},
"fc351843b8ecbd05": {"kind":"literal","value":"external"}
}
}
@@ -1,250 +1,25 @@
{
"formatVersion": 2,
"roots": [
{
"key": "event:subagent/catalog",
"kind": "event",
"event": "subagent/catalog",
"surface": false,
"digest": "3abae7324356f155cb42450c00b806d134ec93bd6439d2063b8d724162d58604",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 14,
"optional": true
},
{
"name": "seq",
"type": 3,
"optional": false
},
{
"name": "time",
"type": 3,
"optional": false
},
{
"name": "type",
"type": 15,
"optional": false
}
],
"indices": []
},
{
"kind": "union",
"types": [
2,
7,
9,
11,
12
]
},
{
"kind": "object",
"properties": [
{
"name": "childCreatedAt",
"type": 3,
"optional": false
},
{
"name": "childId",
"type": 4,
"optional": false
},
{
"name": "label",
"type": 4,
"optional": false
},
{
"name": "mode",
"type": 5,
"optional": false
},
{
"name": "version",
"type": 6,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "literal",
"value": "continuable"
},
{
"kind": "literal",
"value": 0
},
{
"kind": "object",
"properties": [
{
"name": "childCreatedAt",
"type": 3,
"optional": false
},
{
"name": "childId",
"type": 4,
"optional": false
},
{
"name": "label",
"type": 4,
"optional": false
},
{
"name": "mode",
"type": 5,
"optional": false
},
{
"name": "version",
"type": 8,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": 1
},
{
"kind": "object",
"properties": [
{
"name": "childCreatedAt",
"type": 3,
"optional": false
},
{
"name": "childId",
"type": 4,
"optional": false
},
{
"name": "label",
"type": 4,
"optional": true
},
{
"name": "mode",
"type": 10,
"optional": false
},
{
"name": "version",
"type": 6,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "one-shot"
},
{
"kind": "object",
"properties": [
{
"name": "childCreatedAt",
"type": 3,
"optional": false
},
{
"name": "childId",
"type": 4,
"optional": false
},
{
"name": "label",
"type": 4,
"optional": true
},
{
"name": "mode",
"type": 10,
"optional": false
},
{
"name": "version",
"type": 8,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "childCreatedAt",
"type": 3,
"optional": false
},
{
"name": "childId",
"type": 4,
"optional": false
},
{
"name": "label",
"type": 4,
"optional": true
},
{
"name": "mode",
"type": 13,
"optional": false
},
{
"name": "version",
"type": 8,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "unknown"
},
{
"kind": "literal",
"value": true
},
{
"kind": "literal",
"value": "subagent/catalog"
}
]
}
}
{"key":"event:subagent/catalog","kind":"event","event":"subagent/catalog","surface":false,"digest":"3abae7324356f155cb42450c00b806d134ec93bd6439d2063b8d724162d58604"}
],
"types": []
"types": [],
"nodes": {
"05a2796621b1ee6c": {"kind":"literal","value":"unknown"},
"0657d3ca5ac4e7bf": {"kind":"primitive","type":"number"},
"0e10d7c096b47e61": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":false},{"name":"mode","type":"e5d3828df1ec3e2a","optional":false},{"name":"version","type":"93d38436347f67c9","optional":false}],"indices":[]},
"1de64ea9a0849ce6": {"kind":"union","types":["0e10d7c096b47e61","5d542ac7bdf13310","a0f25b5aeeebf2bf","b6896938660a1dfe","e2ad8f5bcd813f26"]},
"3abae7324356f155": {"kind":"object","properties":[{"name":"data","type":"1de64ea9a0849ce6","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"44c8c77aa9aa100b","optional":false}],"indices":[]},
"44c8c77aa9aa100b": {"kind":"literal","value":"subagent/catalog"},
"4cd48f3e5108bd6e": {"kind":"primitive","type":"string"},
"5d542ac7bdf13310": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":true},{"name":"mode","type":"d28765f87d6b6ab4","optional":false},{"name":"version","type":"93d38436347f67c9","optional":false}],"indices":[]},
"93d38436347f67c9": {"kind":"literal","value":0},
"a0f25b5aeeebf2bf": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":true},{"name":"mode","type":"05a2796621b1ee6c","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]},
"b6896938660a1dfe": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":true},{"name":"mode","type":"d28765f87d6b6ab4","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]},
"d28765f87d6b6ab4": {"kind":"literal","value":"one-shot"},
"dacf3d5f3e4ff619": {"kind":"literal","value":true},
"e2ad8f5bcd813f26": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":false},{"name":"mode","type":"e5d3828df1ec3e2a","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]},
"e5d3828df1ec3e2a": {"kind":"literal","value":"continuable"},
"f88f1b7b6bb733f6": {"kind":"literal","value":1}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,297 +1,29 @@
{
"formatVersion": 2,
"roots": [
{
"key": "event:job/registered",
"kind": "event",
"event": "job/registered",
"surface": false,
"digest": "f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 5,
"optional": true
},
{
"name": "seq",
"type": 3,
"optional": false
},
{
"name": "time",
"type": 3,
"optional": false
},
{
"name": "type",
"type": 6,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "jobId",
"type": 2,
"optional": false
},
{
"name": "kind",
"type": 2,
"optional": false
},
{
"name": "label",
"type": 2,
"optional": false
},
{
"name": "owner",
"type": 2,
"optional": false
},
{
"name": "startedAt",
"type": 3,
"optional": false
},
{
"name": "toolCall",
"type": 4,
"optional": true
}
],
"indices": []
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "object",
"properties": [
{
"name": "callId",
"type": 2,
"optional": false
},
{
"name": "rootCallId",
"type": 2,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": true
},
{
"kind": "literal",
"value": "job/registered"
}
]
}
},
{
"key": "event:job/settled",
"kind": "event",
"event": "job/settled",
"surface": false,
"digest": "edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 14,
"optional": true
},
{
"name": "seq",
"type": 7,
"optional": false
},
{
"name": "time",
"type": 7,
"optional": false
},
{
"name": "type",
"type": 15,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "cause",
"type": 2,
"optional": false
},
{
"name": "detail",
"type": 6,
"optional": true
},
{
"name": "finishedAt",
"type": 7,
"optional": false
},
{
"name": "jobId",
"type": 6,
"optional": false
},
{
"name": "kind",
"type": 6,
"optional": false
},
{
"name": "label",
"type": 6,
"optional": false
},
{
"name": "owner",
"type": 6,
"optional": false
},
{
"name": "producerReported",
"type": 8,
"optional": false
},
{
"name": "startedAt",
"type": 7,
"optional": false
},
{
"name": "status",
"type": 9,
"optional": false
},
{
"name": "toolCall",
"type": 13,
"optional": true
}
],
"indices": []
},
{
"kind": "union",
"types": [
3,
4,
5
]
},
{
"kind": "literal",
"value": "producer"
},
{
"kind": "literal",
"value": "teardown"
},
{
"kind": "literal",
"value": "kill"
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "primitive",
"type": "boolean"
},
{
"kind": "union",
"types": [
10,
11,
12
]
},
{
"kind": "literal",
"value": "completed"
},
{
"kind": "literal",
"value": "failed"
},
{
"kind": "literal",
"value": "killed"
},
{
"kind": "object",
"properties": [
{
"name": "callId",
"type": 6,
"optional": false
},
{
"name": "rootCallId",
"type": 6,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": true
},
{
"kind": "literal",
"value": "job/settled"
}
]
}
}
{"key":"event:job/registered","kind":"event","event":"job/registered","surface":false,"digest":"f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1"},
{"key":"event:job/settled","kind":"event","event":"job/settled","surface":false,"digest":"edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf"}
],
"types": []
"types": [],
"nodes": {
"0657d3ca5ac4e7bf": {"kind":"primitive","type":"number"},
"1ebc6f9ff3aa9e3a": {"kind":"literal","value":"completed"},
"24a8188c4d61b70a": {"kind":"literal","value":"job/registered"},
"347de3380f2da308": {"kind":"literal","value":"job/settled"},
"39cf2d3ff8bac81c": {"kind":"object","properties":[{"name":"callId","type":"4cd48f3e5108bd6e","optional":false},{"name":"rootCallId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]},
"4cd48f3e5108bd6e": {"kind":"primitive","type":"string"},
"5cb63db9fe57dc09": {"kind":"literal","value":"teardown"},
"611d82d2dc50df70": {"kind":"object","properties":[{"name":"cause","type":"750a2b129d67704d","optional":false},{"name":"detail","type":"4cd48f3e5108bd6e","optional":true},{"name":"finishedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"jobId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":false},{"name":"owner","type":"4cd48f3e5108bd6e","optional":false},{"name":"producerReported","type":"aa93010d0e521ad7","optional":false},{"name":"startedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"status","type":"c2f49004d9ae38a9","optional":false},{"name":"toolCall","type":"39cf2d3ff8bac81c","optional":true}],"indices":[]},
"6cf082d0fb6034de": {"kind":"literal","value":"producer"},
"750a2b129d67704d": {"kind":"union","types":["5cb63db9fe57dc09","6cf082d0fb6034de","bab6865794007548"]},
"aa93010d0e521ad7": {"kind":"primitive","type":"boolean"},
"b57a2889408de36e": {"kind":"literal","value":"failed"},
"bab6865794007548": {"kind":"literal","value":"kill"},
"c2f49004d9ae38a9": {"kind":"union","types":["1ebc6f9ff3aa9e3a","b57a2889408de36e","ef226a90ddac6cd8"]},
"dacf3d5f3e4ff619": {"kind":"literal","value":true},
"e3056e0dda3a379e": {"kind":"object","properties":[{"name":"jobId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":false},{"name":"owner","type":"4cd48f3e5108bd6e","optional":false},{"name":"startedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"toolCall","type":"39cf2d3ff8bac81c","optional":true}],"indices":[]},
"edd37b1e4974af64": {"kind":"object","properties":[{"name":"data","type":"611d82d2dc50df70","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"347de3380f2da308","optional":false}],"indices":[]},
"ef226a90ddac6cd8": {"kind":"literal","value":"killed"},
"f5520c1d261ca86e": {"kind":"object","properties":[{"name":"data","type":"e3056e0dda3a379e","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"24a8188c4d61b70a","optional":false}],"indices":[]}
}
}
@@ -1,457 +1,47 @@
{
"formatVersion": 2,
"roots": [
{
"key": "event:tool/ptc-dispatch",
"kind": "event",
"event": "tool/ptc-dispatch",
"surface": false,
"digest": "d66e11dddf5dfa3c71547e2cbd7b0608824cd8d6243fc16f532017ff28e8fba5",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 29,
"optional": true
},
{
"name": "seq",
"type": 17,
"optional": false
},
{
"name": "time",
"type": 17,
"optional": false
},
{
"name": "type",
"type": 37,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "arguments",
"type": 2,
"optional": false
},
{
"name": "content",
"type": 3,
"optional": false
},
{
"name": "error",
"type": 31,
"optional": true
},
{
"name": "isError",
"type": 32,
"optional": false
},
{
"name": "meta",
"type": 33,
"optional": true
},
{
"name": "name",
"type": 6,
"optional": false
},
{
"name": "parentCallId",
"type": 6,
"optional": false
},
{
"name": "rootCallId",
"type": 6,
"optional": false
},
{
"name": "subCallId",
"type": 6,
"optional": false
}
],
"indices": []
},
{
"kind": "opaque",
"reason": "unknown"
},
{
"kind": "array",
"element": 4
},
{
"kind": "union",
"types": [
5,
8,
10,
13,
15,
19,
21
]
},
{
"kind": "object",
"properties": [
{
"name": "text",
"type": 6,
"optional": false
},
{
"name": "type",
"type": 7,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "literal",
"value": "reasoning"
},
{
"kind": "object",
"properties": [
{
"name": "text",
"type": 6,
"optional": false
},
{
"name": "type",
"type": 9,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "text"
},
{
"kind": "object",
"properties": [
{
"name": "tool",
"type": 11,
"optional": true
},
{
"name": "toolName",
"type": 6,
"optional": false
},
{
"name": "type",
"type": 12,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "never"
},
{
"kind": "literal",
"value": "tool-addition"
},
{
"kind": "object",
"properties": [
{
"name": "toolName",
"type": 6,
"optional": false
},
{
"name": "type",
"type": 14,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "tool-removal"
},
{
"kind": "object",
"properties": [
{
"name": "attachment",
"type": 16,
"optional": false
},
{
"name": "type",
"type": 18,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "attachmentId",
"type": 6,
"optional": false
},
{
"name": "bytes",
"type": 17,
"optional": false
},
{
"name": "name",
"type": 6,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "literal",
"value": "file"
},
{
"kind": "object",
"properties": [
{
"name": "arguments",
"type": 6,
"optional": false
},
{
"name": "id",
"type": 6,
"optional": false
},
{
"name": "name",
"type": 6,
"optional": false
},
{
"name": "type",
"type": 20,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "tool-call"
},
{
"kind": "object",
"properties": [
{
"name": "attachment",
"type": 22,
"optional": false
},
{
"name": "offloaded",
"type": 29,
"optional": true
},
{
"name": "type",
"type": 30,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "attachmentId",
"type": 6,
"optional": false
},
{
"name": "bytes",
"type": 17,
"optional": false
},
{
"name": "height",
"type": 17,
"optional": false
},
{
"name": "mediaType",
"type": 23,
"optional": false
},
{
"name": "name",
"type": 6,
"optional": true
},
{
"name": "originalDimensions",
"type": 28,
"optional": true
},
{
"name": "width",
"type": 17,
"optional": false
}
],
"indices": []
},
{
"kind": "union",
"types": [
24,
25,
26,
27
]
},
{
"kind": "literal",
"value": "image/gif"
},
{
"kind": "literal",
"value": "image/jpeg"
},
{
"kind": "literal",
"value": "image/png"
},
{
"kind": "literal",
"value": "image/webp"
},
{
"kind": "object",
"properties": [
{
"name": "height",
"type": 17,
"optional": false
},
{
"name": "width",
"type": 17,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": true
},
{
"kind": "literal",
"value": "image"
},
{
"kind": "object",
"properties": [
{
"name": "code",
"type": 6,
"optional": false
},
{
"name": "name",
"type": 6,
"optional": false
},
{
"name": "reason",
"type": 6,
"optional": true
}
],
"indices": []
},
{
"kind": "primitive",
"type": "boolean"
},
{
"kind": "union",
"types": [
6,
17,
32,
34,
35,
36
]
},
{
"kind": "array",
"element": 33
},
{
"kind": "primitive",
"type": "null"
},
{
"kind": "object",
"properties": [],
"indices": [
{
"key": 6,
"value": 33
}
]
},
{
"kind": "literal",
"value": "tool/ptc-dispatch"
}
]
}
}
{"key":"event:tool/ptc-dispatch","kind":"event","event":"tool/ptc-dispatch","surface":false,"digest":"d66e11dddf5dfa3c71547e2cbd7b0608824cd8d6243fc16f532017ff28e8fba5"}
],
"types": []
"types": [],
"nodes": {
"0657d3ca5ac4e7bf": {"kind":"primitive","type":"number"},
"0f46153645c29784": {"kind":"object","properties":[{"name":"height","type":"0657d3ca5ac4e7bf","optional":false},{"name":"width","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]},
"12d5808fac1996b9": {"kind":"object","properties":[{"name":"attachment","type":"b0b0eb1b16490f6b","optional":false},{"name":"offloaded","type":"dacf3d5f3e4ff619","optional":true},{"name":"type","type":"fb8b6a2e53cb9cf4","optional":false}],"indices":[]},
"1f0133217eeeffd8": {"kind":"literal","value":"tool-call"},
"277b61f9c6f81704": {"kind":"literal","value":"file"},
"2f1e6a7ebace43d7": {"kind":"union","types":["3f74ba74acd10644","58f94390b982f8a5","de30b1fa12c75f81","ebb768d85ec87fb6"]},
"3c2535ca0a2062f1": {"kind":"literal","value":"reasoning"},
"3f74ba74acd10644": {"kind":"literal","value":"image/jpeg"},
"43e311235aa537df": {"kind":"object","properties":[{"name":"toolName","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"83d6916021eede8f","optional":false}],"indices":[]},
"4cd48f3e5108bd6e": {"kind":"primitive","type":"string"},
"4e44c9118152f682": {"kind":"literal","value":"text"},
"4e88776858652856": {"kind":"object","properties":[{"name":"text","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"4e44c9118152f682","optional":false}],"indices":[]},
"529e0ccd0e34079a": {"kind":"object","properties":[],"indices":[{"key":"4cd48f3e5108bd6e","value":"c592ce75aab73fca"}]},
"58f94390b982f8a5": {"kind":"literal","value":"image/png"},
"72ec79127a9c0d02": {"kind":"array","element":"c592ce75aab73fca"},
"8064c03c87c357fd": {"kind":"union","types":["12d5808fac1996b9","43e311235aa537df","4e88776858652856","814d9434e1ddf907","8e611bf46821e500","c1f0f17b6493b85d","f2da0f27c1e6add5"]},
"814d9434e1ddf907": {"kind":"object","properties":[{"name":"text","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"3c2535ca0a2062f1","optional":false}],"indices":[]},
"83d6916021eede8f": {"kind":"literal","value":"tool-removal"},
"85f9b505b7bdff1f": {"kind":"literal","value":"tool-addition"},
"8e611bf46821e500": {"kind":"object","properties":[{"name":"tool","type":"fcf427290c6213c2","optional":true},{"name":"toolName","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"85f9b505b7bdff1f","optional":false}],"indices":[]},
"938f7c7357bc403e": {"kind":"primitive","type":"null"},
"a81059ca06999da1": {"kind":"object","properties":[{"name":"code","type":"4cd48f3e5108bd6e","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"reason","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]},
"aa93010d0e521ad7": {"kind":"primitive","type":"boolean"},
"b0b0eb1b16490f6b": {"kind":"object","properties":[{"name":"attachmentId","type":"4cd48f3e5108bd6e","optional":false},{"name":"bytes","type":"0657d3ca5ac4e7bf","optional":false},{"name":"height","type":"0657d3ca5ac4e7bf","optional":false},{"name":"mediaType","type":"2f1e6a7ebace43d7","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":true},{"name":"originalDimensions","type":"0f46153645c29784","optional":true},{"name":"width","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]},
"b52f215a6cd69105": {"kind":"array","element":"8064c03c87c357fd"},
"bbc5696e3f13c758": {"kind":"opaque","reason":"unknown"},
"c1f0f17b6493b85d": {"kind":"object","properties":[{"name":"arguments","type":"4cd48f3e5108bd6e","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"1f0133217eeeffd8","optional":false}],"indices":[]},
"c592ce75aab73fca": {"kind":"union","types":["0657d3ca5ac4e7bf","4cd48f3e5108bd6e","529e0ccd0e34079a","72ec79127a9c0d02","938f7c7357bc403e","aa93010d0e521ad7"]},
"cef171adf021a186": {"kind":"object","properties":[{"name":"arguments","type":"bbc5696e3f13c758","optional":false},{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"error","type":"a81059ca06999da1","optional":true},{"name":"isError","type":"aa93010d0e521ad7","optional":false},{"name":"meta","type":"c592ce75aab73fca","optional":true},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"parentCallId","type":"4cd48f3e5108bd6e","optional":false},{"name":"rootCallId","type":"4cd48f3e5108bd6e","optional":false},{"name":"subCallId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]},
"d66e11dddf5dfa3c": {"kind":"object","properties":[{"name":"data","type":"cef171adf021a186","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"fb5e0517050d2c6d","optional":false}],"indices":[]},
"dacf3d5f3e4ff619": {"kind":"literal","value":true},
"de30b1fa12c75f81": {"kind":"literal","value":"image/gif"},
"ebb768d85ec87fb6": {"kind":"literal","value":"image/webp"},
"f19bee84bf3d2090": {"kind":"object","properties":[{"name":"attachmentId","type":"4cd48f3e5108bd6e","optional":false},{"name":"bytes","type":"0657d3ca5ac4e7bf","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]},
"f2da0f27c1e6add5": {"kind":"object","properties":[{"name":"attachment","type":"f19bee84bf3d2090","optional":false},{"name":"type","type":"277b61f9c6f81704","optional":false}],"indices":[]},
"fb5e0517050d2c6d": {"kind":"literal","value":"tool/ptc-dispatch"},
"fb8b6a2e53cb9cf4": {"kind":"literal","value":"image"},
"fcf427290c6213c2": {"kind":"primitive","type":"never"}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,231 +1,28 @@
{
"formatVersion": 2,
"roots": [
{
"key": "event:web/deepseek-search-llm-request",
"kind": "event",
"event": "web/deepseek-search-llm-request",
"surface": false,
"digest": "0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 17,
"optional": true
},
{
"name": "seq",
"type": 4,
"optional": false
},
{
"name": "time",
"type": 4,
"optional": false
},
{
"name": "type",
"type": 18,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "apiVersion",
"type": 2,
"optional": false
},
{
"name": "body",
"type": 3,
"optional": false
},
{
"name": "endpoint",
"type": 2,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "object",
"properties": [
{
"name": "max_tokens",
"type": 4,
"optional": false
},
{
"name": "messages",
"type": 5,
"optional": false
},
{
"name": "model",
"type": 2,
"optional": false
},
{
"name": "thinking",
"type": 11,
"optional": true
},
{
"name": "tools",
"type": 13,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "tuple",
"elements": [
{
"type": 6,
"optional": false,
"rest": false
}
]
},
{
"kind": "object",
"properties": [
{
"name": "content",
"type": 7,
"optional": false
},
{
"name": "role",
"type": 10,
"optional": false
}
],
"indices": []
},
{
"kind": "tuple",
"elements": [
{
"type": 8,
"optional": false,
"rest": false
}
]
},
{
"kind": "object",
"properties": [
{
"name": "text",
"type": 2,
"optional": false
},
{
"name": "type",
"type": 9,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "text"
},
{
"kind": "literal",
"value": "user"
},
{
"kind": "object",
"properties": [
{
"name": "type",
"type": 12,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "disabled"
},
{
"kind": "tuple",
"elements": [
{
"type": 14,
"optional": false,
"rest": false
}
]
},
{
"kind": "object",
"properties": [
{
"name": "max_uses",
"type": 4,
"optional": false
},
{
"name": "name",
"type": 15,
"optional": false
},
{
"name": "type",
"type": 16,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": "web_search"
},
{
"kind": "literal",
"value": "web_search_20250305"
},
{
"kind": "literal",
"value": true
},
{
"kind": "literal",
"value": "web/deepseek-search-llm-request"
}
]
}
}
{"key":"event:web/deepseek-search-llm-request","kind":"event","event":"web/deepseek-search-llm-request","surface":false,"digest":"0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52"}
],
"types": []
"types": [],
"nodes": {
"0116b1f89c05221d": {"kind":"literal","value":"web_search_20250305"},
"0657d3ca5ac4e7bf": {"kind":"primitive","type":"number"},
"0b9b8facbe2c1977": {"kind":"literal","value":"disabled"},
"0c556c9b9f2a9623": {"kind":"object","properties":[{"name":"data","type":"abd6ca62a5ab1665","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"6f611bed14b21065","optional":false}],"indices":[]},
"2d11ca7b0d4493e2": {"kind":"object","properties":[{"name":"max_uses","type":"0657d3ca5ac4e7bf","optional":false},{"name":"name","type":"935c8d486292b13c","optional":false},{"name":"type","type":"0116b1f89c05221d","optional":false}],"indices":[]},
"37ff65426bcf130f": {"kind":"object","properties":[{"name":"type","type":"0b9b8facbe2c1977","optional":false}],"indices":[]},
"4aed17ec726b5397": {"kind":"tuple","elements":[{"type":"2d11ca7b0d4493e2","optional":false,"rest":false}]},
"4cd48f3e5108bd6e": {"kind":"primitive","type":"string"},
"4e44c9118152f682": {"kind":"literal","value":"text"},
"4e88776858652856": {"kind":"object","properties":[{"name":"text","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"4e44c9118152f682","optional":false}],"indices":[]},
"68f44f59e91c4139": {"kind":"literal","value":"user"},
"6f611bed14b21065": {"kind":"literal","value":"web/deepseek-search-llm-request"},
"8f6d609794bf5afc": {"kind":"tuple","elements":[{"type":"4e88776858652856","optional":false,"rest":false}]},
"935c8d486292b13c": {"kind":"literal","value":"web_search"},
"9a2a9029f8d7ede0": {"kind":"object","properties":[{"name":"content","type":"8f6d609794bf5afc","optional":false},{"name":"role","type":"68f44f59e91c4139","optional":false}],"indices":[]},
"9c001ecb5e1d3c38": {"kind":"object","properties":[{"name":"max_tokens","type":"0657d3ca5ac4e7bf","optional":false},{"name":"messages","type":"b993441f8ce7d27b","optional":false},{"name":"model","type":"4cd48f3e5108bd6e","optional":false},{"name":"thinking","type":"37ff65426bcf130f","optional":true},{"name":"tools","type":"4aed17ec726b5397","optional":false}],"indices":[]},
"abd6ca62a5ab1665": {"kind":"object","properties":[{"name":"apiVersion","type":"4cd48f3e5108bd6e","optional":false},{"name":"body","type":"9c001ecb5e1d3c38","optional":false},{"name":"endpoint","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]},
"b993441f8ce7d27b": {"kind":"tuple","elements":[{"type":"9a2a9029f8d7ede0","optional":false,"rest":false}]},
"dacf3d5f3e4ff619": {"kind":"literal","value":true}
}
}
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -15,8 +15,8 @@
en: 1ba6efaa0b5d6e47
zh: 6e216391ad24c541
/persistence-type-change-records/files-and-ownership:
en: 6cd0f79a9455bdf2
zh: a5978b7834c0802b
en: 41d4ab88aac68e7e
zh: bc1e688b078e1ac2
/persistence-type-change-records/compatibility-rules:
en: 218b28a5096b5e7d
zh: 5b987c852e204893
+2
View File
@@ -28,6 +28,8 @@ For complete schemas grouped by Session format, use the [format references](hist
The generated [catalog](../persistence-catalog.md) provides readable declarations and digests; the [schema inventory](../persistence-schema.json) contains the normalized types. Roots cover the logical Session header, the physical JSONL header line, the event envelope, and every repository-declared event. Referenced types contribute transitively to each affected root's digest.
Each `.schema.json` file stores one node table keyed by the first 16 hexadecimal digits of each node's subgraph digest, with one root, type, or node per line. Roots and types locate their graphs through their own digests, so a type change rewrites only the changed nodes, their ancestors, and the affected digests. Generators own the exact text; verifiers reject any other layout.
Each dated record has four sibling files:
| File | Owner |
+2
View File
@@ -28,6 +28,8 @@ description: "审阅和维护已记录的会话持久化类型变更、对应 sc
生成的[目录](../persistence-catalog.zh.md)提供可读声明和摘要;[schema 清单](../persistence-schema.json)包含规范化类型。根覆盖逻辑会话头、物理 JSONL 头行、事件封装以及每个仓库内声明的事件。被引用类型的变更会传递到每个受影响根的摘要。
每个 `.schema.json` 文件存储一张节点表,键为节点子图摘要的前 16 位十六进制字符,每个根类型、类型或节点占一行。根类型和类型通过自身摘要定位其类型图,因此类型变更只改写变化的节点、其祖先节点以及受影响的摘要。生成器决定确切文本;校验器拒绝其他布局。
每条带日期的记录有四个同目录文件:
| 文件 | 职责 |
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,6 @@
{
"formatVersion": 1,
"roots": [],
"types": []
"types": [],
"nodes": {}
}
@@ -1,5 +1,6 @@
{
"formatVersion": 1,
"roots": [],
"types": []
"types": [],
"nodes": {}
}
@@ -1,5 +1,6 @@
{
"formatVersion": 1,
"roots": [],
"types": []
"types": [],
"nodes": {}
}
@@ -1,5 +1,6 @@
{
"formatVersion": 1,
"roots": [],
"types": []
"types": [],
"nodes": {}
}
@@ -1,5 +1,6 @@
{
"formatVersion": 1,
"roots": [],
"types": []
"types": [],
"nodes": {}
}
@@ -1,5 +1,6 @@
{
"formatVersion": 1,
"roots": [],
"types": []
"types": [],
"nodes": {}
}
@@ -1,5 +1,6 @@
{
"formatVersion": 1,
"roots": [],
"types": []
"types": [],
"nodes": {}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+24 -411
View File
@@ -1,417 +1,30 @@
{
"formatVersion": 1,
"roots": [
{
"key": "event:permission/preset",
"kind": "event",
"event": "permission/preset",
"surface": false,
"digest": "7271e4b771406aaf06014c2269edd6cb68055bb8b8686571730813ce0ababc22",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 7,
"optional": true
},
{
"name": "seq",
"type": 8,
"optional": false
},
{
"name": "time",
"type": 8,
"optional": false
},
{
"name": "type",
"type": 9,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "origin",
"type": 2,
"optional": true
},
{
"name": "preset",
"type": 6,
"optional": false
}
],
"indices": []
},
{
"kind": "union",
"types": [
3,
4,
5
]
},
{
"kind": "literal",
"value": "default"
},
{
"kind": "literal",
"value": "inferred"
},
{
"kind": "literal",
"value": "selection"
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "literal",
"value": true
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "literal",
"value": "permission/preset"
}
]
}
}
{"key":"event:permission/preset","kind":"event","event":"permission/preset","surface":false,"digest":"7271e4b771406aaf06014c2269edd6cb68055bb8b8686571730813ce0ababc22"}
],
"types": [
{
"digest": "0657d3ca5ac4e7bf79b00bcf860b1c7159d01a9da137497221ec2f1776e365f7",
"schema": {
"root": 0,
"nodes": [
{
"kind": "primitive",
"type": "number"
}
]
},
"names": [],
"sources": []
},
{
"digest": "19a9088b778dbc75c9c3158d0145b3e1d3b9bba253821e249f869db82e98b713",
"schema": {
"root": 0,
"nodes": [
{
"kind": "literal",
"value": "inferred"
}
]
},
"names": [],
"sources": []
},
{
"digest": "4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b",
"schema": {
"root": 0,
"nodes": [
{
"kind": "primitive",
"type": "string"
}
]
},
"names": [
"packages/attachment/attachment/src/brand.ts#AttachmentId",
"packages/compaction/compaction/src/brand.ts#CompactionId",
"packages/core/session/src/types.ts#SessionId",
"packages/experimental/agent-team/src/types.ts#TeamId",
"packages/experimental/agent-team/src/types.ts#TeamMessageId",
"packages/experimental/agent-team/src/types.ts#TeamTaskId",
"packages/goal/goal/src/types.ts#GoalId",
"packages/host/apiproxy/src/api/rpc.ts#RpcId",
"packages/interaction/commands/src/brand.ts#CommandId",
"packages/interaction/user-approval/src/types.ts#ApprovalRequestId",
"packages/llm/llm-retry/src/brand.ts#RetryId",
"packages/llm/llm/src/brand.ts#CallId",
"packages/llm/llm/src/brand.ts#MessageId",
"packages/llm/llm/src/brand.ts#ProviderRequestId",
"packages/llm/llm/src/brand.ts#ReasoningEffortId",
"packages/schedule/schedule/src/types.ts#ScheduleId",
"packages/session/session-title/src/index.ts#SessionTitleProviderId",
"packages/workflow/workflow/src/types.ts#WorkflowRunId"
],
"sources": [
"packages/attachment/attachment/src/brand.ts",
"packages/compaction/compaction/src/brand.ts",
"packages/core/session/src/types.ts",
"packages/experimental/agent-team/src/types.ts",
"packages/goal/goal/src/types.ts",
"packages/host/apiproxy/src/api/rpc.ts",
"packages/interaction/commands/src/brand.ts",
"packages/interaction/user-approval/src/types.ts",
"packages/llm/llm-retry/src/brand.ts",
"packages/llm/llm/src/brand.ts",
"packages/schedule/schedule/src/types.ts",
"packages/session/session-title/src/index.ts",
"packages/workflow/workflow/src/types.ts"
]
},
{
"digest": "6c5d5215ad72616561350f3be63f968e321e75360d9de2a9408994d45d3be76f",
"schema": {
"root": 0,
"nodes": [
{
"kind": "union",
"types": [
1,
2,
3
]
},
{
"kind": "literal",
"value": "default"
},
{
"kind": "literal",
"value": "inferred"
},
{
"kind": "literal",
"value": "selection"
}
]
},
"names": [
"event:permission/preset[0].data.origin"
],
"sources": []
},
{
"digest": "7271e4b771406aaf06014c2269edd6cb68055bb8b8686571730813ce0ababc22",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 7,
"optional": true
},
{
"name": "seq",
"type": 8,
"optional": false
},
{
"name": "time",
"type": 8,
"optional": false
},
{
"name": "type",
"type": 9,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "origin",
"type": 2,
"optional": true
},
{
"name": "preset",
"type": 6,
"optional": false
}
],
"indices": []
},
{
"kind": "union",
"types": [
3,
4,
5
]
},
{
"kind": "literal",
"value": "default"
},
{
"kind": "literal",
"value": "inferred"
},
{
"kind": "literal",
"value": "selection"
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "literal",
"value": true
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "literal",
"value": "permission/preset"
}
]
},
"names": [
"event:permission/preset",
"event:permission/preset[0]"
],
"sources": [
"packages/core/session/src/types.ts"
]
},
{
"digest": "78d7455eb6b3c8da4d70c104eadd69d4a60df1c449e855517a60673b889e03fa",
"schema": {
"root": 0,
"nodes": [
{
"kind": "literal",
"value": "selection"
}
]
},
"names": [],
"sources": []
},
{
"digest": "ae63d9d1080ed8945031594d93f8d96651d7101b848719cc3e1475875cd3d649",
"schema": {
"root": 0,
"nodes": [
{
"kind": "literal",
"value": "permission/preset"
}
]
},
"names": [],
"sources": []
},
{
"digest": "d66ba5eb62ddba9fc264067c47d1152ca3bd8c2bdf2ededb66695e04ab588550",
"schema": {
"root": 0,
"nodes": [
{
"kind": "literal",
"value": "default"
}
]
},
"names": [],
"sources": []
},
{
"digest": "dacf3d5f3e4ff619c1320e64a48dec5e7ecd7870bece65fbd449a0e63adfa521",
"schema": {
"root": 0,
"nodes": [
{
"kind": "literal",
"value": true
}
]
},
"names": [],
"sources": []
},
{
"digest": "ebaac53bc4e8eb2ef8870c1ac58976a8a54987e3e412126b9f617f42bcd49829",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "origin",
"type": 1,
"optional": true
},
{
"name": "preset",
"type": 5,
"optional": false
}
],
"indices": []
},
{
"kind": "union",
"types": [
2,
3,
4
]
},
{
"kind": "literal",
"value": "default"
},
{
"kind": "literal",
"value": "inferred"
},
{
"kind": "literal",
"value": "selection"
},
{
"kind": "primitive",
"type": "string"
}
]
},
"names": [
"event:permission/preset[0].data"
],
"sources": [
"packages/interaction/permission-presets/src/index.ts"
]
}
]
{"digest":"0657d3ca5ac4e7bf79b00bcf860b1c7159d01a9da137497221ec2f1776e365f7","names":[],"sources":[]},
{"digest":"19a9088b778dbc75c9c3158d0145b3e1d3b9bba253821e249f869db82e98b713","names":[],"sources":[]},
{"digest":"4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b","names":["packages/attachment/attachment/src/brand.ts#AttachmentId","packages/compaction/compaction/src/brand.ts#CompactionId","packages/core/session/src/types.ts#SessionId","packages/experimental/agent-team/src/types.ts#TeamId","packages/experimental/agent-team/src/types.ts#TeamMessageId","packages/experimental/agent-team/src/types.ts#TeamTaskId","packages/goal/goal/src/types.ts#GoalId","packages/host/apiproxy/src/api/rpc.ts#RpcId","packages/interaction/commands/src/brand.ts#CommandId","packages/interaction/user-approval/src/types.ts#ApprovalRequestId","packages/llm/llm-retry/src/brand.ts#RetryId","packages/llm/llm/src/brand.ts#CallId","packages/llm/llm/src/brand.ts#MessageId","packages/llm/llm/src/brand.ts#ProviderRequestId","packages/llm/llm/src/brand.ts#ReasoningEffortId","packages/schedule/schedule/src/types.ts#ScheduleId","packages/session/session-title/src/index.ts#SessionTitleProviderId","packages/workflow/workflow/src/types.ts#WorkflowRunId"],"sources":["packages/attachment/attachment/src/brand.ts","packages/compaction/compaction/src/brand.ts","packages/core/session/src/types.ts","packages/experimental/agent-team/src/types.ts","packages/goal/goal/src/types.ts","packages/host/apiproxy/src/api/rpc.ts","packages/interaction/commands/src/brand.ts","packages/interaction/user-approval/src/types.ts","packages/llm/llm-retry/src/brand.ts","packages/llm/llm/src/brand.ts","packages/schedule/schedule/src/types.ts","packages/session/session-title/src/index.ts","packages/workflow/workflow/src/types.ts"]},
{"digest":"6c5d5215ad72616561350f3be63f968e321e75360d9de2a9408994d45d3be76f","names":["event:permission/preset[0].data.origin"],"sources":[]},
{"digest":"7271e4b771406aaf06014c2269edd6cb68055bb8b8686571730813ce0ababc22","names":["event:permission/preset","event:permission/preset[0]"],"sources":["packages/core/session/src/types.ts"]},
{"digest":"78d7455eb6b3c8da4d70c104eadd69d4a60df1c449e855517a60673b889e03fa","names":[],"sources":[]},
{"digest":"ae63d9d1080ed8945031594d93f8d96651d7101b848719cc3e1475875cd3d649","names":[],"sources":[]},
{"digest":"d66ba5eb62ddba9fc264067c47d1152ca3bd8c2bdf2ededb66695e04ab588550","names":[],"sources":[]},
{"digest":"dacf3d5f3e4ff619c1320e64a48dec5e7ecd7870bece65fbd449a0e63adfa521","names":[],"sources":[]},
{"digest":"ebaac53bc4e8eb2ef8870c1ac58976a8a54987e3e412126b9f617f42bcd49829","names":["event:permission/preset[0].data"],"sources":["packages/interaction/permission-presets/src/index.ts"]}
],
"nodes": {
"0657d3ca5ac4e7bf": {"kind":"primitive","type":"number"},
"19a9088b778dbc75": {"kind":"literal","value":"inferred"},
"4cd48f3e5108bd6e": {"kind":"primitive","type":"string"},
"6c5d5215ad726165": {"kind":"union","types":["19a9088b778dbc75","78d7455eb6b3c8da","d66ba5eb62ddba9f"]},
"7271e4b771406aaf": {"kind":"object","properties":[{"name":"data","type":"ebaac53bc4e8eb2e","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"ae63d9d1080ed894","optional":false}],"indices":[]},
"78d7455eb6b3c8da": {"kind":"literal","value":"selection"},
"ae63d9d1080ed894": {"kind":"literal","value":"permission/preset"},
"d66ba5eb62ddba9f": {"kind":"literal","value":"default"},
"dacf3d5f3e4ff619": {"kind":"literal","value":true},
"ebaac53bc4e8eb2e": {"kind":"object","properties":[{"name":"origin","type":"6c5d5215ad726165","optional":true},{"name":"preset","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,6 @@
{
"formatVersion": 1,
"roots": [],
"types": []
"types": [],
"nodes": {}
}
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,6 @@
{
"formatVersion": 1,
"roots": [],
"types": []
"types": [],
"nodes": {}
}
@@ -1,5 +1,6 @@
{
"formatVersion": 1,
"roots": [],
"types": []
"types": [],
"nodes": {}
}
File diff suppressed because it is too large Load Diff
+31 -777
View File
@@ -1,783 +1,37 @@
{
"formatVersion": 1,
"roots": [
{
"key": "event:feedback/message-delete",
"kind": "event",
"event": "feedback/message-delete",
"surface": false,
"digest": "3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 3,
"optional": true
},
{
"name": "seq",
"type": 4,
"optional": false
},
{
"name": "time",
"type": 4,
"optional": false
},
{
"name": "type",
"type": 5,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "messageId",
"type": 2,
"optional": false
},
{
"name": "sessionId",
"type": 2,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "literal",
"value": true
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "literal",
"value": "feedback/message-delete"
}
]
}
},
{
"key": "event:feedback/message-put",
"kind": "event",
"event": "feedback/message-put",
"surface": false,
"digest": "3b04fde0dc763cf84fbde7b6611b3194dd56d95d0c0bf0204311640468d586e1",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 8,
"optional": true
},
{
"name": "seq",
"type": 3,
"optional": false
},
{
"name": "time",
"type": 3,
"optional": false
},
{
"name": "type",
"type": 9,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "item",
"type": 2,
"optional": false
},
{
"name": "sessionId",
"type": 4,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "createdAt",
"type": 3,
"optional": false
},
{
"name": "messageId",
"type": 4,
"optional": false
},
{
"name": "note",
"type": 4,
"optional": true
},
{
"name": "rating",
"type": 5,
"optional": false
},
{
"name": "updatedAt",
"type": 3,
"optional": false
},
{
"name": "version",
"type": 4,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "union",
"types": [
6,
7
]
},
{
"kind": "literal",
"value": "negative"
},
{
"kind": "literal",
"value": "positive"
},
{
"kind": "literal",
"value": true
},
{
"kind": "literal",
"value": "feedback/message-put"
}
]
}
}
{"key":"event:feedback/message-delete","kind":"event","event":"feedback/message-delete","surface":false,"digest":"3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796"},
{"key":"event:feedback/message-put","kind":"event","event":"feedback/message-put","surface":false,"digest":"3b04fde0dc763cf84fbde7b6611b3194dd56d95d0c0bf0204311640468d586e1"}
],
"types": [
{
"digest": "0657d3ca5ac4e7bf79b00bcf860b1c7159d01a9da137497221ec2f1776e365f7",
"schema": {
"root": 0,
"nodes": [
{
"kind": "primitive",
"type": "number"
}
]
},
"names": [
"packages/core/session/src/types.ts#SessionSeq"
],
"sources": [
"packages/core/session/src/types.ts"
]
},
{
"digest": "390eed1028aba02774f14408647acec10ec5516e97d4f15ecde2b9989d5a69f2",
"schema": {
"root": 0,
"nodes": [
{
"kind": "union",
"types": [
1,
2
]
},
{
"kind": "literal",
"value": "negative"
},
{
"kind": "literal",
"value": "positive"
}
]
},
"names": [
"packages/feedback/message-feedback/src/types.ts#MessageFeedbackRating"
],
"sources": [
"packages/feedback/message-feedback/src/types.ts"
]
},
{
"digest": "3b04fde0dc763cf84fbde7b6611b3194dd56d95d0c0bf0204311640468d586e1",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 8,
"optional": true
},
{
"name": "seq",
"type": 3,
"optional": false
},
{
"name": "time",
"type": 3,
"optional": false
},
{
"name": "type",
"type": 9,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "item",
"type": 2,
"optional": false
},
{
"name": "sessionId",
"type": 4,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "createdAt",
"type": 3,
"optional": false
},
{
"name": "messageId",
"type": 4,
"optional": false
},
{
"name": "note",
"type": 4,
"optional": true
},
{
"name": "rating",
"type": 5,
"optional": false
},
{
"name": "updatedAt",
"type": 3,
"optional": false
},
{
"name": "version",
"type": 4,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "union",
"types": [
6,
7
]
},
{
"kind": "literal",
"value": "negative"
},
{
"kind": "literal",
"value": "positive"
},
{
"kind": "literal",
"value": true
},
{
"kind": "literal",
"value": "feedback/message-put"
}
]
},
"names": [
"event:feedback/message-put",
"event:feedback/message-put[0]"
],
"sources": [
"packages/core/session/src/types.ts"
]
},
{
"digest": "3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 3,
"optional": true
},
{
"name": "seq",
"type": 4,
"optional": false
},
{
"name": "time",
"type": 4,
"optional": false
},
{
"name": "type",
"type": 5,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "messageId",
"type": 2,
"optional": false
},
{
"name": "sessionId",
"type": 2,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "literal",
"value": true
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "literal",
"value": "feedback/message-delete"
}
]
},
"names": [
"event:feedback/message-delete",
"event:feedback/message-delete[0]"
],
"sources": [
"packages/core/session/src/types.ts"
]
},
{
"digest": "44818000e56acb97afdb5045ce235eb55e3fb1ccabc9fc7d0750bb38f36a7f8e",
"schema": {
"root": 0,
"nodes": [
{
"kind": "literal",
"value": "feedback/message-delete"
}
]
},
"names": [],
"sources": []
},
{
"digest": "4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b",
"schema": {
"root": 0,
"nodes": [
{
"kind": "primitive",
"type": "string"
}
]
},
"names": [
"packages/api/session-controller/src/types.ts#SessionRequestId",
"packages/attachment/attachment/src/brand.ts#AttachmentId",
"packages/compaction/compaction/src/brand.ts#CompactionId",
"packages/core/session/src/types.ts#SessionId",
"packages/experimental/agent-team/src/types.ts#TeamId",
"packages/experimental/agent-team/src/types.ts#TeamMessageId",
"packages/experimental/agent-team/src/types.ts#TeamTaskId",
"packages/feedback/message-feedback/src/types.ts#MessageFeedbackVersion",
"packages/goal/goal/src/types.ts#GoalId",
"packages/interaction/commands/src/brand.ts#CommandId",
"packages/interaction/user-approval/src/types.ts#ApprovalRequestId",
"packages/llm/llm-retry/src/brand.ts#RetryId",
"packages/llm/llm/src/brand.ts#MessageId",
"packages/llm/llm/src/brand.ts#ProviderRequestId",
"packages/llm/llm/src/brand.ts#ReasoningEffortId",
"packages/llm/llm/src/brand.ts#ToolCallId",
"packages/schedule/schedule/src/types.ts#ScheduleId",
"packages/session/session-title/src/index.ts#SessionTitleProviderId",
"packages/session/session-title/src/types.ts#SessionTitleProviderId",
"packages/webhook/webhook/src/brand.ts#WebhookDeliveryId",
"packages/webhook/webhook/src/brand.ts#WebhookRuleId",
"packages/webhook/webhook/src/brand.ts#WebhookSourceId",
"packages/workflow/workflow/src/types.ts#WorkflowRunId"
],
"sources": [
"packages/api/session-controller/src/types.ts",
"packages/attachment/attachment/src/brand.ts",
"packages/compaction/compaction/src/brand.ts",
"packages/core/session/src/types.ts",
"packages/experimental/agent-team/src/types.ts",
"packages/feedback/message-feedback/src/types.ts",
"packages/goal/goal/src/types.ts",
"packages/interaction/commands/src/brand.ts",
"packages/interaction/user-approval/src/types.ts",
"packages/llm/llm-retry/src/brand.ts",
"packages/llm/llm/src/brand.ts",
"packages/schedule/schedule/src/types.ts",
"packages/session/session-title/src/index.ts",
"packages/session/session-title/src/types.ts",
"packages/webhook/webhook/src/brand.ts",
"packages/workflow/workflow/src/types.ts"
]
},
{
"digest": "6cda0d6d3b4336d83998fe580628426df81c2afbab5d768b8813b56d569120e8",
"schema": {
"root": 0,
"nodes": [
{
"kind": "literal",
"value": "feedback/message-put"
}
]
},
"names": [],
"sources": []
},
{
"digest": "7c6129b6961cf4b2a030f36c78d2b0a5be38617651a9b81322e054ee512bee06",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "messageId",
"type": 1,
"optional": false
},
{
"name": "sessionId",
"type": 1,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "string"
}
]
},
"names": [
"packages/feedback/message-feedback/src/types.ts#MessageFeedbackDelete"
],
"sources": [
"packages/feedback/message-feedback/src/types.ts"
]
},
{
"digest": "b1f62380f6746fedd629e71065e3583cef83334875b499ac9665c2eb87acfbf5",
"schema": {
"root": 0,
"nodes": [
{
"kind": "literal",
"value": "negative"
}
]
},
"names": [],
"sources": []
},
{
"digest": "cb1df23dfd315032b6d34b3c5fe2c4e3d07f7b60334ab9afd9f17da48ad09379",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "item",
"type": 1,
"optional": false
},
{
"name": "sessionId",
"type": 3,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "createdAt",
"type": 2,
"optional": false
},
{
"name": "messageId",
"type": 3,
"optional": false
},
{
"name": "note",
"type": 3,
"optional": true
},
{
"name": "rating",
"type": 4,
"optional": false
},
{
"name": "updatedAt",
"type": 2,
"optional": false
},
{
"name": "version",
"type": 3,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "union",
"types": [
5,
6
]
},
{
"kind": "literal",
"value": "negative"
},
{
"kind": "literal",
"value": "positive"
}
]
},
"names": [
"packages/feedback/message-feedback/src/types.ts#MessageFeedbackPut"
],
"sources": [
"packages/feedback/message-feedback/src/types.ts"
]
},
{
"digest": "dacf3d5f3e4ff619c1320e64a48dec5e7ecd7870bece65fbd449a0e63adfa521",
"schema": {
"root": 0,
"nodes": [
{
"kind": "literal",
"value": true
}
]
},
"names": [],
"sources": []
},
{
"digest": "df5d9709b7db2a72510dc52052a23f0c38f39730af1504bab8bccabc14fae528",
"schema": {
"root": 0,
"nodes": [
{
"kind": "literal",
"value": "positive"
}
]
},
"names": [],
"sources": []
},
{
"digest": "e68568d70fde1c0701e8d3e7645e5bb796f856ca42f349b5479032c254f27375",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "createdAt",
"type": 1,
"optional": false
},
{
"name": "messageId",
"type": 2,
"optional": false
},
{
"name": "note",
"type": 2,
"optional": true
},
{
"name": "rating",
"type": 3,
"optional": false
},
{
"name": "updatedAt",
"type": 1,
"optional": false
},
{
"name": "version",
"type": 2,
"optional": false
}
],
"indices": []
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "union",
"types": [
4,
5
]
},
{
"kind": "literal",
"value": "negative"
},
{
"kind": "literal",
"value": "positive"
}
]
},
"names": [
"packages/feedback/message-feedback/src/types.ts#MessageFeedbackItem"
],
"sources": [
"packages/feedback/message-feedback/src/types.ts"
]
}
]
{"digest":"0657d3ca5ac4e7bf79b00bcf860b1c7159d01a9da137497221ec2f1776e365f7","names":["packages/core/session/src/types.ts#SessionSeq"],"sources":["packages/core/session/src/types.ts"]},
{"digest":"390eed1028aba02774f14408647acec10ec5516e97d4f15ecde2b9989d5a69f2","names":["packages/feedback/message-feedback/src/types.ts#MessageFeedbackRating"],"sources":["packages/feedback/message-feedback/src/types.ts"]},
{"digest":"3b04fde0dc763cf84fbde7b6611b3194dd56d95d0c0bf0204311640468d586e1","names":["event:feedback/message-put","event:feedback/message-put[0]"],"sources":["packages/core/session/src/types.ts"]},
{"digest":"3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796","names":["event:feedback/message-delete","event:feedback/message-delete[0]"],"sources":["packages/core/session/src/types.ts"]},
{"digest":"44818000e56acb97afdb5045ce235eb55e3fb1ccabc9fc7d0750bb38f36a7f8e","names":[],"sources":[]},
{"digest":"4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b","names":["packages/api/session-controller/src/types.ts#SessionRequestId","packages/attachment/attachment/src/brand.ts#AttachmentId","packages/compaction/compaction/src/brand.ts#CompactionId","packages/core/session/src/types.ts#SessionId","packages/experimental/agent-team/src/types.ts#TeamId","packages/experimental/agent-team/src/types.ts#TeamMessageId","packages/experimental/agent-team/src/types.ts#TeamTaskId","packages/feedback/message-feedback/src/types.ts#MessageFeedbackVersion","packages/goal/goal/src/types.ts#GoalId","packages/interaction/commands/src/brand.ts#CommandId","packages/interaction/user-approval/src/types.ts#ApprovalRequestId","packages/llm/llm-retry/src/brand.ts#RetryId","packages/llm/llm/src/brand.ts#MessageId","packages/llm/llm/src/brand.ts#ProviderRequestId","packages/llm/llm/src/brand.ts#ReasoningEffortId","packages/llm/llm/src/brand.ts#ToolCallId","packages/schedule/schedule/src/types.ts#ScheduleId","packages/session/session-title/src/index.ts#SessionTitleProviderId","packages/session/session-title/src/types.ts#SessionTitleProviderId","packages/webhook/webhook/src/brand.ts#WebhookDeliveryId","packages/webhook/webhook/src/brand.ts#WebhookRuleId","packages/webhook/webhook/src/brand.ts#WebhookSourceId","packages/workflow/workflow/src/types.ts#WorkflowRunId"],"sources":["packages/api/session-controller/src/types.ts","packages/attachment/attachment/src/brand.ts","packages/compaction/compaction/src/brand.ts","packages/core/session/src/types.ts","packages/experimental/agent-team/src/types.ts","packages/feedback/message-feedback/src/types.ts","packages/goal/goal/src/types.ts","packages/interaction/commands/src/brand.ts","packages/interaction/user-approval/src/types.ts","packages/llm/llm-retry/src/brand.ts","packages/llm/llm/src/brand.ts","packages/schedule/schedule/src/types.ts","packages/session/session-title/src/index.ts","packages/session/session-title/src/types.ts","packages/webhook/webhook/src/brand.ts","packages/workflow/workflow/src/types.ts"]},
{"digest":"6cda0d6d3b4336d83998fe580628426df81c2afbab5d768b8813b56d569120e8","names":[],"sources":[]},
{"digest":"7c6129b6961cf4b2a030f36c78d2b0a5be38617651a9b81322e054ee512bee06","names":["packages/feedback/message-feedback/src/types.ts#MessageFeedbackDelete"],"sources":["packages/feedback/message-feedback/src/types.ts"]},
{"digest":"b1f62380f6746fedd629e71065e3583cef83334875b499ac9665c2eb87acfbf5","names":[],"sources":[]},
{"digest":"cb1df23dfd315032b6d34b3c5fe2c4e3d07f7b60334ab9afd9f17da48ad09379","names":["packages/feedback/message-feedback/src/types.ts#MessageFeedbackPut"],"sources":["packages/feedback/message-feedback/src/types.ts"]},
{"digest":"dacf3d5f3e4ff619c1320e64a48dec5e7ecd7870bece65fbd449a0e63adfa521","names":[],"sources":[]},
{"digest":"df5d9709b7db2a72510dc52052a23f0c38f39730af1504bab8bccabc14fae528","names":[],"sources":[]},
{"digest":"e68568d70fde1c0701e8d3e7645e5bb796f856ca42f349b5479032c254f27375","names":["packages/feedback/message-feedback/src/types.ts#MessageFeedbackItem"],"sources":["packages/feedback/message-feedback/src/types.ts"]}
],
"nodes": {
"0657d3ca5ac4e7bf": {"kind":"primitive","type":"number"},
"390eed1028aba027": {"kind":"union","types":["b1f62380f6746fed","df5d9709b7db2a72"]},
"3b04fde0dc763cf8": {"kind":"object","properties":[{"name":"data","type":"cb1df23dfd315032","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"6cda0d6d3b4336d8","optional":false}],"indices":[]},
"3ee93b06f3a12585": {"kind":"object","properties":[{"name":"data","type":"7c6129b6961cf4b2","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"44818000e56acb97","optional":false}],"indices":[]},
"44818000e56acb97": {"kind":"literal","value":"feedback/message-delete"},
"4cd48f3e5108bd6e": {"kind":"primitive","type":"string"},
"6cda0d6d3b4336d8": {"kind":"literal","value":"feedback/message-put"},
"7c6129b6961cf4b2": {"kind":"object","properties":[{"name":"messageId","type":"4cd48f3e5108bd6e","optional":false},{"name":"sessionId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]},
"b1f62380f6746fed": {"kind":"literal","value":"negative"},
"cb1df23dfd315032": {"kind":"object","properties":[{"name":"item","type":"e68568d70fde1c07","optional":false},{"name":"sessionId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]},
"dacf3d5f3e4ff619": {"kind":"literal","value":true},
"df5d9709b7db2a72": {"kind":"literal","value":"positive"},
"e68568d70fde1c07": {"kind":"object","properties":[{"name":"createdAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"messageId","type":"4cd48f3e5108bd6e","optional":false},{"name":"note","type":"4cd48f3e5108bd6e","optional":true},{"name":"rating","type":"390eed1028aba027","optional":false},{"name":"updatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"version","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,6 @@
{
"formatVersion": 1,
"roots": [],
"types": []
"types": [],
"nodes": {}
}
@@ -1,5 +1,6 @@
{
"formatVersion": 1,
"roots": [],
"types": []
"types": [],
"nodes": {}
}
+1296 -26171
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -21,7 +21,7 @@ import { collectPluginRecords, type PluginRecordEntry } from './plugin-record-ca
export { annotateSurface, collectEventEnvelopeTypes, collectLogEvents, collectSurfaceEventTypes } from './persistence-catalog-source.ts'
export type { AnnotatedLogEventEntry, EventEnvelopeTypeEntry, LogEventEntry } from './persistence-catalog-source.ts'
import { persistenceSchemaSnapshot, type PersistenceSchemaInventory } from './persistence-schema-model.ts'
import { encodePersistenceSnapshot, type PersistenceSchemaInventory } from './persistence-schema-model.ts'
import { renderPersistenceSchemaDefinitions, renderPersistenceSchemaIndex } from './render-persistence-schema.ts'
const root = resolve(import.meta.dirname, '..')
@@ -225,7 +225,7 @@ export function persistenceCatalogArtifacts(scanRoot: string, schema: Persistenc
...renderPersistencePair(scanRoot, OUT, render(events, envelope, schema), render(events, envelope, schema, 'zh')),
...renderPersistencePair(scanRoot, OUT_PLUGIN_RECORDS, renderPluginRecordCatalog(pluginRecords), renderPluginRecordCatalog(pluginRecords, 'zh')),
{ path: OUT_RUNTIME_TYPES, content: renderKnownEventTypes(events) },
{ path: OUT_SCHEMA, content: `${JSON.stringify(persistenceSchemaSnapshot(schema), null, 2)}\n` },
{ path: OUT_SCHEMA, content: encodePersistenceSnapshot(schema) },
]
}
+60 -37
View File
@@ -5,7 +5,7 @@ import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSyn
import { tmpdir } from 'node:os'
import { join, posix, resolve } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { canonicalizeSchema, schemaDigest } from './persistence-schema-model.ts'
import { canonicalizeSchema, encodePersistenceSnapshot, schemaDigest } from './persistence-schema-model.ts'
import type { PersistenceRoot, PersistenceSchemaInventory, SchemaNode, SchemaProperty, SourceCompatibility } from './persistence-schema-model.ts'
import { extractPersistenceSchema } from './persistence-schema.ts'
import { persistenceCatalogArtifacts } from './gen-persistence-catalog.ts'
@@ -25,7 +25,7 @@ function runPersistenceChanges(
args: readonly string[], root: string, extract: (root: string) => PersistenceSchemaInventory,
): string {
return executePersistenceChanges(args, root, extract, (_root, current) => [{
path: 'docs/persistence-schema.json', content: JSON.stringify(current, null, 2) + '\n',
path: 'docs/persistence-schema.json', content: encodePersistenceSnapshot(current),
}])
}
@@ -102,6 +102,25 @@ function entry(
}
}
interface SnapshotText {
formatVersion: number
roots: Record<string, unknown>[]
types: Record<string, unknown>[]
nodes: Record<string, Record<string, unknown>>
}
/** Encode a snapshot, then apply one invalid edit to its committed JSON fields. */
function tamper(snapshot: PersistenceSchemaInventory, change: (value: SnapshotText) => void): string {
const value = JSON.parse(encodePersistenceSnapshot(snapshot)) as SnapshotText
change(value)
return JSON.stringify(value)
}
/** Replace the node that the first root graph starts from. */
function replaceRootNode(node: Record<string, unknown>): (value: SnapshotText) => void {
return (value) => { value.nodes[String(value.roots[0]!.digest).slice(0, 16)] = node }
}
const BASE_ID = '2026-09-11-baseline'
const NEXT_ID = '2026-09-11-change'
@@ -126,7 +145,7 @@ describe('historical persistence snapshot parsing', () => {
const root = snapshot.roots[0]!
const sources = ['packages/core/example/src/types.ts']
const complete = { ...snapshot, types: [{ schema: root.schema, digest: root.digest, names: ['Example'], sources }] }
expect(parseHistoricalPersistenceSnapshot(complete)).toEqual(complete)
expect(parseHistoricalPersistenceSnapshot(encodePersistenceSnapshot(complete))).toEqual(complete)
})
it.each([':8', ':8:3', '#L8', '#L8-L12'])('rejects source coordinates %s only in historical inventories', (suffix) => {
@@ -134,29 +153,31 @@ describe('historical persistence snapshot parsing', () => {
const root = snapshot.roots[0]!
const sources = [`packages/core/example/src/types.ts${suffix}`]
const complete = { ...snapshot, types: [{ schema: root.schema, digest: root.digest, names: [], sources }] }
expect(() => parseHistoricalPersistenceSnapshot(complete)).toThrow('historical schema sources must omit line numbers')
expect(parsePersistenceSnapshot(complete)).toEqual(complete)
expect(() => parseHistoricalPersistenceSnapshot(encodePersistenceSnapshot(complete))).toThrow('historical schema sources must omit line numbers')
expect(parsePersistenceSnapshot(encodePersistenceSnapshot(complete))).toEqual(complete)
})
it('admits optional surface operations only through the historical parser', () => {
const snapshot = historicalSurface('optional')
expect(() => parsePersistenceSnapshot(snapshot)).toThrow('surface metadata')
expect(parseHistoricalPersistenceSnapshot(snapshot)).toEqual(snapshot)
expect(parsePersistenceSnapshot(historicalSurface('required'))).toEqual(historicalSurface('required'))
expect(() => parsePersistenceSnapshot(encodePersistenceSnapshot(snapshot))).toThrow('surface metadata')
expect(parseHistoricalPersistenceSnapshot(encodePersistenceSnapshot(snapshot))).toEqual(snapshot)
expect(parsePersistenceSnapshot(encodePersistenceSnapshot(historicalSurface('required')))).toEqual(historicalSurface('required'))
})
it('still rejects absent operations, mismatched event tags, and invalid roots', () => {
expect(() => parseHistoricalPersistenceSnapshot(historicalSurface('absent'))).toThrow('surface metadata')
expect(() => parseHistoricalPersistenceSnapshot(historicalSurface('optional', 'wrong/tag'))).toThrow('type does not match')
expect(() => parseHistoricalPersistenceSnapshot(encodePersistenceSnapshot(historicalSurface('absent')))).toThrow('surface metadata')
expect(() => parseHistoricalPersistenceSnapshot(encodePersistenceSnapshot(historicalSurface('optional', 'wrong/tag')))).toThrow('type does not match')
const snapshot = historicalSurface('optional')
expect(() => parseHistoricalPersistenceSnapshot({ ...snapshot, roots: snapshot.roots.map(root => ({ ...root, key: 'event:wrong/key' })) })).toThrow('invalid event root key')
expect(() => parseHistoricalPersistenceSnapshot({ ...snapshot, roots: snapshot.roots.map(root => ({ ...root, surface: false })) })).toThrow('surface metadata')
expect(() => parseHistoricalPersistenceSnapshot(tamper(snapshot, (value) => { value.roots[0]!.key = 'event:wrong/key' }))).toThrow('invalid event root key')
expect(() => parseHistoricalPersistenceSnapshot(tamper(snapshot, (value) => { value.roots[0]!.surface = false }))).toThrow('surface metadata')
})
it('preserves graph and digest validation in historical mode', () => {
const snapshot = historicalSurface('optional')
expect(() => parseHistoricalPersistenceSnapshot({ ...snapshot, roots: snapshot.roots.map(root => ({ ...root, digest: 'f'.repeat(64) })) })).toThrow('digest mismatch')
expect(() => parseHistoricalPersistenceSnapshot({ ...snapshot, roots: snapshot.roots.map(root => ({ ...root, schema: { root: 0, nodes: [{ kind: 'array', element: 9 }] } })) })).toThrow('unknown schema node')
expect(() => parseHistoricalPersistenceSnapshot(tamper(snapshot, (value) => {
value.roots[0]!.digest = String(value.roots[0]!.digest).slice(0, 16) + 'f'.repeat(48)
}))).toThrow('digest mismatch')
expect(() => parseHistoricalPersistenceSnapshot(tamper(snapshot, replaceRootNode({ kind: 'array', element: '9'.repeat(16) })))).toThrow('unknown schema node')
})
})
@@ -312,10 +333,10 @@ describe('accepted persistence baseline', () => {
const current = inventory({ value: version === 4 ? 'number' : 'boolean' }, version)
if (version === 5) runPersistenceChanges(['--record', FUTURE_ID, '--prose', proseFile(root)], root, () => current)
const snapshotPath = join(root, `docs/persistence-changes/${FINALIZED_ID}.schema.json`)
const snapshot = JSON.parse(readFileSync(snapshotPath, 'utf8')) as PersistenceSchemaInventory
const snapshot = parsePersistenceSnapshot(readFileSync(snapshotPath, 'utf8'))
const replacement = typeRoot('event:example/value', { value: 'number', extra: 'string' })
const original = snapshot.roots.find(root => root.key === replacement.key)!
writeFileSync(snapshotPath, JSON.stringify({ ...snapshot,
writeFileSync(snapshotPath, encodePersistenceSnapshot({ ...snapshot,
roots: snapshot.roots.map(root => root.key === replacement.key ? replacement : root) }))
for (const suffix of ['.md', '.zh.md']) {
const path = join(root, `docs/persistence-changes/${FINALIZED_ID}${suffix}`)
@@ -375,7 +396,7 @@ function finishDocuments(root: string, id: string): void {
}
function commitCurrent(root: string, schema: PersistenceSchemaInventory): void {
writeFileSync(join(root, 'docs/persistence-schema.json'), JSON.stringify(schema, null, 2) + '\n')
writeFileSync(join(root, 'docs/persistence-schema.json'), encodePersistenceSnapshot(schema))
}
function baseline(root: string, schema = inventory()): void {
@@ -624,15 +645,12 @@ describe('persistence history verification', () => {
it('rejects malformed references, unknown schema variants, digest tampering, and extra snapshot roots', () => {
const schema = inventory()
const malformed: { roots: readonly { schema: { nodes: readonly unknown[] }; digest: string }[] } = structuredClone(schema)
malformed.roots[0]!.schema.nodes = [{ kind: 'array', element: 99 }]
expect(() => parsePersistenceSnapshot(malformed)).toThrow('unknown schema node')
malformed.roots[0]!.schema.nodes = [{ kind: 'future' }]
expect(() => parsePersistenceSnapshot(malformed)).toThrow('unknown schema node kind')
const tampered = structuredClone(schema)
Object.assign(tampered.roots[0]!, { digest: '0'.repeat(64) })
expect(() => parsePersistenceSnapshot(tampered)).toThrow('digest mismatch')
expect(() => parsePersistenceSnapshot({ ...schema, formatVersion: 3 })).toThrow('normalization version')
expect(() => parsePersistenceSnapshot(tamper(schema, replaceRootNode({ kind: 'array', element: '9'.repeat(16) })))).toThrow('unknown schema node')
expect(() => parsePersistenceSnapshot(tamper(schema, replaceRootNode({ kind: 'future' })))).toThrow('unknown schema node kind')
expect(() => parsePersistenceSnapshot(tamper(schema, (value) => {
value.roots[0]!.digest = String(value.roots[0]!.digest).slice(0, 16) + '0'.repeat(48)
}))).toThrow('digest mismatch')
expect(() => parsePersistenceSnapshot(tamper(schema, (value) => { value.formatVersion = 3 }))).toThrow('normalization version')
const next = entry(NEXT_ID, onlyEvent(inventory({ value: 'string', 'label?': 'string' })), BASE_ID)
expect(() => validatePersistenceHistory([entry(BASE_ID, schema, null, true), { ...next, snapshot: inventory() }])).toThrow('snapshot roots')
})
@@ -951,7 +969,7 @@ describe('recorded source compatibility policy', () => {
expect(after.digest).not.toBe(before.digest)
expect(classifyPersistenceChange(before, after)).toEqual([expect.objectContaining({ kind: 'attribution-kind-added', requiresCompatibilityReview: false })])
const snapshot: PersistenceSchemaInventory = { formatVersion: 2, roots: [after], types: [] }
expect(parsePersistenceSnapshot(JSON.parse(JSON.stringify(snapshot)))).toEqual(snapshot)
expect(parsePersistenceSnapshot(encodePersistenceSnapshot(snapshot))).toEqual(snapshot)
})
it('allows ordinary optional fields in an existing group alongside a qualified addition', () => {
@@ -1017,25 +1035,30 @@ describe('recorded source compatibility policy', () => {
it.each(['auto-review', 'compact-basic', 'ptc-mode'])('rejects saved qualification of reserved producer kind %s', (kind) => {
const root = attributedRoot([EXISTING_SOURCE, { kind }], { policy: attributionPolicy([kind]) })
const snapshot: PersistenceSchemaInventory = { formatVersion: 2, roots: [root], types: [] }
expect(() => parsePersistenceSnapshot(snapshot)).toThrow('invalid source compatibility')
expect(() => parsePersistenceSnapshot(encodePersistenceSnapshot(snapshot))).toThrow('invalid source compatibility')
})
it('rejects old-format policy fields, policy tampering, unsupported promises and invalid bindings', () => {
const root = attributedRoot([EXISTING_SOURCE])
const snapshot: PersistenceSchemaInventory = { formatVersion: 2, roots: [root], types: [] }
expect(() => parsePersistenceSnapshot({ ...snapshot, formatVersion: 1 })).toThrow('unknown field compatibility')
expect(() => parsePersistenceSnapshot(encodePersistenceSnapshot({ ...snapshot, formatVersion: 1 }))).toThrow('unknown field compatibility')
const patchPolicy = (patch: Record<string, unknown>) => (value: SnapshotText): void => {
const properties = Object.values(value.nodes).flatMap(node => node.kind === 'object' ? node.properties as Record<string, unknown>[] : [])
Object.assign(properties.find(property => property.compatibility !== undefined)!.compatibility as object, patch)
}
for (const patch of [{ version: 2 }, { policy: 'arbitrary' }, { discriminator: 'type' }, { unknownKinds: 'discard' }]) {
const tampered = JSON.parse(JSON.stringify(snapshot)) as PersistenceSchemaInventory
const property = tampered.roots[0]!.schema.nodes.flatMap(node => node.kind === 'object' ? node.properties : []).find(property => property.compatibility !== undefined)!
Object.assign(property.compatibility!, patch)
expect(() => parsePersistenceSnapshot(tampered)).toThrow('unsupported source compatibility')
expect(() => parsePersistenceSnapshot(tamper(snapshot, patchPolicy(patch)))).toThrow('unsupported source compatibility')
}
const wrongRole = attributedRoot([EXISTING_SOURCE], { policy: attributionPolicy([], 'developer') })
expect(() => parsePersistenceSnapshot({ ...snapshot, roots: [wrongRole] })).toThrow('invalid source compatibility')
const changedQualification = attributedRoot([EXISTING_SOURCE], { policy: attributionPolicy([EXISTING_SOURCE.kind]) })
expect(() => parsePersistenceSnapshot({ ...snapshot, roots: [{ ...changedQualification, digest: root.digest }] })).toThrow('digest mismatch')
expect(() => parsePersistenceSnapshot(encodePersistenceSnapshot({ ...snapshot, roots: [wrongRole] }))).toThrow('invalid source compatibility')
expect(() => parsePersistenceSnapshot(tamper(snapshot, patchPolicy({ attributionKinds: [EXISTING_SOURCE.kind] })))).toThrow('key does not match')
const qualified: PersistenceSchemaInventory = { formatVersion: 2, types: [],
roots: [attributedRoot([EXISTING_SOURCE, NEW_SOURCE, { kind: 'other-attribution' }], { policy: attributionPolicy([NEW_SOURCE.kind, 'other-attribution']) })] }
for (const kinds of [['other-attribution', NEW_SOURCE.kind], [NEW_SOURCE.kind, NEW_SOURCE.kind, 'other-attribution']]) {
expect(() => parsePersistenceSnapshot(tamper(qualified, patchPolicy({ attributionKinds: kinds })))).toThrow('node is not canonical')
}
const unknownKind = attributedRoot([EXISTING_SOURCE], { policy: attributionPolicy(['absent']) })
expect(() => parsePersistenceSnapshot({ ...snapshot, roots: [unknownKind] })).toThrow('invalid source compatibility')
expect(() => parsePersistenceSnapshot(encodePersistenceSnapshot({ ...snapshot, roots: [unknownKind] }))).toThrow('invalid source compatibility')
})
})
+139 -130
View File
@@ -4,9 +4,15 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from
import { basename, join, resolve } from 'node:path'
import { parseArgs } from 'node:util'
import { JSON_SCHEMA, load } from 'js-yaml'
import { canonicalizeSchema, reachableSchemaTypes, schemaDigest } from './persistence-schema-model.ts'
import {
SCHEMA_NODE_KEY_LENGTH, canonicalizeSchema, encodePersistenceSnapshot, renderPersistenceSnapshot,
schemaChildren, schemaDigest, schemaTableEntry, serializeSchemaNode,
} from './persistence-schema-model.ts'
import { matchingSourceCompatibility, sourceKindGroups, validSourceCompatibility } from './persistence-source-policy.ts'
import type { CanonicalSchema, PersistenceRoot, PersistenceSchemaInventory, SchemaNode, SchemaTupleElement } from './persistence-schema-model.ts'
import type {
CanonicalSchema, PersistenceRoot, PersistenceSchemaInventory, PersistenceType,
SchemaNode, SchemaProperty, SchemaTupleElement, SourceCompatibility,
} from './persistence-schema-model.ts'
import { extractPersistenceSchema } from './persistence-schema.ts'
import { persistenceCatalogArtifacts } from './gen-persistence-catalog.ts'
import { renderPersistencePair } from './persistence-artifacts.ts'
@@ -169,128 +175,135 @@ function digest(value: unknown, label: string): string {
return result
}
function reference(value: unknown, count: number, label: string): number {
if (!Number.isSafeInteger(value) || (value as number) < 0 || (value as number) >= count) throw new Error(`${label} references an unknown schema node`)
return value as number
function parseNode(value: unknown, label: string, formatVersion: 1 | 2, ref: (value: unknown) => number): SchemaNode {
const node = record(value, label)
switch (node.kind) {
case 'primitive':
keys(node, ['kind', 'type'], label)
if (!['null', 'boolean', 'number', 'string', 'never'].includes(String(node.type))) throw new Error(`${label}: invalid primitive`)
return { kind: 'primitive', type: node.type as Extract<SchemaNode, { kind: 'primitive' }>['type'] }
case 'literal':
keys(node, ['kind', 'value'], label)
if (!['string', 'boolean', 'number'].includes(typeof node.value)
|| typeof node.value === 'number' && !Number.isFinite(node.value)) throw new Error(`${label}: invalid literal`)
return { kind: 'literal', value: node.value as string | number | boolean }
case 'opaque':
keys(node, ['kind', 'reason'], label)
if (!['any', 'unknown'].includes(String(node.reason))) throw new Error(`${label}: invalid opaque reason`)
return { kind: 'opaque', reason: node.reason as 'any' | 'unknown' }
case 'array':
keys(node, ['kind', 'element'], label)
return { kind: 'array', element: ref(node.element) }
case 'tuple':
keys(node, ['kind', 'elements'], label)
return { kind: 'tuple', elements: array(node.elements, label).map((rawElement): SchemaTupleElement => {
const element = record(rawElement, label)
keys(element, ['type', 'optional', 'rest'], label)
return { type: ref(element.type), optional: bool(element.optional, label), rest: bool(element.rest, label) }
}) }
case 'object': {
keys(node, ['kind', 'properties', 'indices'], label)
const names = new Set<string>()
const properties = array(node.properties, label).map((rawProperty): SchemaProperty => {
const property = record(rawProperty, label)
keys(property, ['name', 'type', 'optional'], label, formatVersion === 2 ? ['compatibility'] : [])
if (typeof property.name !== 'string') throw new Error(`${label}: property name must be a string`)
const name = property.name
if (names.has(name)) throw new Error(`${label}: duplicate property ${name}`)
names.add(name)
return { name, type: ref(property.type), optional: bool(property.optional, label),
...(property.compatibility === undefined ? {} : { compatibility: parseCompatibility(property.compatibility, label) }) }
})
const indices = array(node.indices, label).map((rawIndex) => {
const entry = record(rawIndex, label)
keys(entry, ['key', 'value'], label)
return { key: ref(entry.key), value: ref(entry.value) }
})
return { kind: 'object', properties, indices }
}
case 'union':
keys(node, ['kind', 'types'], label)
if (array(node.types, label).length === 0) throw new Error(`${label}: empty union`)
return { kind: 'union', types: (node.types as unknown[]).map(ref) }
default:
throw new Error(`${label}: unknown schema node kind ${String(node.kind)}`)
}
}
function parseSchema(value: unknown, label: string, formatVersion: 1 | 2): CanonicalSchema {
const input = record(value, label)
keys(input, ['root', 'nodes'], label)
if (input.root !== 0) throw new Error(`${label}.root must be zero`)
const nodes = array(input.nodes, `${label}.nodes`)
if (nodes.length === 0) throw new Error(`${label}.nodes must not be empty`)
const ref = (value: unknown): number => reference(value, nodes.length, label)
for (const [index, raw] of nodes.entries()) {
const node = record(raw, `${label}.nodes[${index}]`)
switch (node.kind) {
case 'primitive':
keys(node, ['kind', 'type'], label)
if (!['null', 'boolean', 'number', 'string', 'never'].includes(String(node.type))) throw new Error(`${label}: invalid primitive`)
break
case 'literal':
keys(node, ['kind', 'value'], label)
if (!['string', 'boolean', 'number'].includes(typeof node.value)
|| typeof node.value === 'number' && !Number.isFinite(node.value)) throw new Error(`${label}: invalid literal`)
break
case 'opaque':
keys(node, ['kind', 'reason'], label)
if (!['any', 'unknown'].includes(String(node.reason))) throw new Error(`${label}: invalid opaque reason`)
break
case 'array':
keys(node, ['kind', 'element'], label)
ref(node.element)
break
case 'tuple':
keys(node, ['kind', 'elements'], label)
for (const rawElement of array(node.elements, label)) {
const element = record(rawElement, label)
keys(element, ['type', 'optional', 'rest'], label)
ref(element.type)
bool(element.optional, label)
bool(element.rest, label)
}
break
case 'object': {
keys(node, ['kind', 'properties', 'indices'], label)
const names = new Set<string>()
for (const rawProperty of array(node.properties, label)) {
const property = record(rawProperty, label)
keys(property, ['name', 'type', 'optional'], label, formatVersion === 2 ? ['compatibility'] : [])
if (property.compatibility !== undefined) {
const policy = record(property.compatibility, `${label} source compatibility`)
keys(policy, ['version', 'policy', 'binding', 'discriminator', 'unknownKinds', 'attributionKinds'], label)
if (policy.version !== 1 || policy.policy !== 'session-source-attribution'
|| !['session.user-message.source', 'session.developer-message.source'].includes(String(policy.binding))
|| policy.discriminator !== 'kind' || policy.unknownKinds !== 'preserve') {
throw new Error(`${label}: unsupported source compatibility policy`)
}
for (const kind of array(policy.attributionKinds, label)) textValue(kind, label)
}
if (typeof property.name !== 'string') throw new Error(`${label}: property name must be a string`)
const name = property.name
if (names.has(name)) throw new Error(`${label}: duplicate property ${name}`)
names.add(name)
ref(property.type)
bool(property.optional, label)
}
for (const rawIndex of array(node.indices, label)) {
const entry = record(rawIndex, label)
keys(entry, ['key', 'value'], label)
ref(entry.key)
ref(entry.value)
}
break
}
case 'union':
keys(node, ['kind', 'types'], label)
if (array(node.types, label).length === 0) throw new Error(`${label}: empty union`)
for (const item of node.types as unknown[]) ref(item)
break
default:
throw new Error(`${label}: unknown schema node kind ${String(node.kind)}`)
}
function parseCompatibility(value: unknown, label: string): SourceCompatibility {
const policy = record(value, `${label} source compatibility`)
keys(policy, ['version', 'policy', 'binding', 'discriminator', 'unknownKinds', 'attributionKinds'], label)
if (policy.version !== 1 || policy.policy !== 'session-source-attribution'
|| (policy.binding !== 'session.user-message.source' && policy.binding !== 'session.developer-message.source')
|| policy.discriminator !== 'kind' || policy.unknownKinds !== 'preserve') {
throw new Error(`${label}: unsupported source compatibility policy`)
}
const schema = input as unknown as CanonicalSchema
for (const node of schema.nodes) {
if (node.kind !== 'object') continue
for (const property of node.properties) {
if (property.compatibility !== undefined && !validSourceCompatibility(schema.nodes, node, property)) {
throw new Error(`${label}: invalid source compatibility binding or attribution kinds`)
}
}
}
const canonical = canonicalizeSchema(schema.nodes, schema.root)
if (JSON.stringify(canonical) !== JSON.stringify(schema)) throw new Error(`${label}: schema is not canonical`)
return schema
return { version: 1, policy: 'session-source-attribution', binding: policy.binding, discriminator: 'kind', unknownKinds: 'preserve',
attributionKinds: array(policy.attributionKinds, label).map(kind => textValue(kind, label)) }
}
/** Parse full or compact inventories, restoring type graphs and rejecting invalid digests.
* @param value - JSON read from the current inventory or an enforced acknowledgement snapshot.
* @returns the validated inventory.
/** Parse the committed current inventory or an enforced acknowledgement snapshot.
* @param text - JSON text in the layout written by encodePersistenceSnapshot.
* @returns the validated inventory with canonical graphs.
*/
export function parsePersistenceSnapshot(value: unknown): PersistenceSchemaInventory {
return parseSnapshot(value, false)
export function parsePersistenceSnapshot(text: string): PersistenceSchemaInventory {
return parseSnapshot(text, false)
}
/** Parse a historical inventory with path-only source references and optional surface operations.
* @param value - JSON captured from a historical source tree.
* @param text - JSON text captured from a historical source tree.
* @returns the validated inventory; current acknowledgements use the strict parser.
*/
export function parseHistoricalPersistenceSnapshot(value: unknown): PersistenceSchemaInventory {
return parseSnapshot(value, true)
export function parseHistoricalPersistenceSnapshot(text: string): PersistenceSchemaInventory {
return parseSnapshot(text, true)
}
function parseSnapshot(value: unknown, historical: boolean): PersistenceSchemaInventory {
const input = record(value, 'schema inventory')
keys(input, ['formatVersion', 'roots', 'types'], 'schema inventory')
function parseSnapshot(text: string, historical: boolean): PersistenceSchemaInventory {
const input = record(JSON.parse(text) as unknown, 'schema inventory')
keys(input, ['formatVersion', 'roots', 'types', 'nodes'], 'schema inventory')
const formatVersion = input.formatVersion
if (formatVersion !== 1 && formatVersion !== 2) throw new Error('unsupported persistence schema normalization version')
const table = record(input.nodes, 'schema nodes')
const nodeKeys = Object.keys(table)
const positions = new Map(nodeKeys.map((key, index) => [key, index]))
const position = (value: unknown, label: string): number => {
const found = typeof value === 'string' ? positions.get(value) : undefined
if (found === undefined) throw new Error(`${label} references an unknown schema node`)
return found
}
const graph = nodeKeys.map(key =>
parseNode(table[key], `schema node ${key}`, formatVersion, value => position(value, `schema node ${key}`)))
for (const [index, key] of nodeKeys.entries()) {
const node = graph[index] as SchemaNode
if (node.kind === 'object') {
for (const property of node.properties) {
if (property.compatibility !== undefined && !validSourceCompatibility(graph, node, property)) {
throw new Error(`schema node ${key}: invalid source compatibility binding or attribution kinds`)
}
}
}
const [canonicalKey, canonical] = schemaTableEntry(graph, index)
if (canonicalKey !== key) throw new Error(`schema node ${key}: key does not match its subgraph digest`)
if (JSON.stringify(canonical) !== JSON.stringify(table[key])) throw new Error(`schema node ${key}: node is not canonical`)
}
const used = new Set<number>()
const graphOf = (graphDigest: string, label: string): CanonicalSchema => {
const index = position(graphDigest.slice(0, SCHEMA_NODE_KEY_LENGTH), label)
const pending = [index]
while (pending.length > 0) {
const next = pending.pop() as number
if (used.has(next)) continue
used.add(next)
pending.push(...schemaChildren(graph[next] as SchemaNode))
}
const schema = canonicalizeSchema(graph, index)
if (graphDigest !== schemaDigest(schema)) throw new Error(`${label}: schema digest mismatch`)
return schema
}
const names = new Set<string>()
const schemas: CanonicalSchema[] = []
for (const rawRoot of array(input.roots, 'schema roots')) {
const roots = array(input.roots, 'schema roots').map((rawRoot): PersistenceRoot => {
const root = record(rawRoot, 'schema root')
keys(root, ['key', 'kind', 'digest', 'schema'], 'schema root', ['event', 'surface'])
keys(root, ['key', 'kind', 'digest'], 'schema root', ['event', 'surface'])
const key = textValue(root.key, 'root key')
if (names.has(key)) throw new Error(`duplicate schema root ${key}`)
names.add(key)
@@ -300,26 +313,17 @@ function parseSnapshot(value: unknown, historical: boolean): PersistenceSchemaIn
} else if ((root.kind !== 'header' || !['SessionHeader', 'JsonlHeaderLine'].includes(key))
&& (root.kind !== 'envelope' || key !== 'SessionEventEnvelope')) throw new Error(`invalid schema root ${key}`)
if (root.kind !== 'event' && (root.event !== undefined || root.surface !== undefined)) throw new Error(`${key}: non-event metadata`)
const schema = parseSchema(root.schema, key, formatVersion)
const schema = graphOf(digest(root.digest, `${key} digest`), key)
if (root.kind === 'event') validateEventMetadata(schema, String(root.event), root.surface === true, historical)
if (digest(root.digest, `${key} digest`) !== schemaDigest(schema)) throw new Error(`${key}: schema digest mismatch`)
schemas.push(schema)
}
let reachable: Map<string, CanonicalSchema> | undefined
const types = array(input.types, 'schema types').map((rawType) => {
return root.kind === 'event'
? { key, kind: 'event', event: root.event as string, surface: root.surface as boolean, digest: root.digest as string, schema }
: { key, kind: root.kind, digest: root.digest as string, schema }
})
const types = array(input.types, 'schema types').map((rawType): PersistenceType => {
const type = record(rawType, 'schema type')
keys(type, ['digest', 'names', 'sources'], 'schema type', ['schema'])
keys(type, ['digest', 'names', 'sources'], 'schema type')
const typeDigest = digest(type.digest, 'shared digest')
let schema: CanonicalSchema
if ('schema' in type) {
schema = parseSchema(type.schema, 'shared schema', formatVersion)
if (typeDigest !== schemaDigest(schema)) throw new Error('shared schema digest mismatch')
} else {
reachable ??= reachableSchemaTypes(schemas)
const found = reachable.get(typeDigest)
if (found === undefined) throw new Error(`shared schema digest ${typeDigest} is not reachable from roots`)
schema = found
}
const schema = graphOf(typeDigest, `shared schema ${typeDigest}`)
const typeNames = array(type.names, 'type names').map(name => textValue(name, 'type name'))
const sources = array(type.sources, 'type sources').map((source) => {
const location = textValue(source, 'type source')
@@ -330,7 +334,13 @@ function parseSnapshot(value: unknown, historical: boolean): PersistenceSchemaIn
})
return { digest: typeDigest, schema, names: typeNames, sources }
})
return { ...input as unknown as PersistenceSchemaInventory, types }
const orphan = nodeKeys.find((_, index) => !used.has(index))
if (orphan !== undefined) throw new Error(`schema node ${orphan} is not reachable from any root or type`)
const inventory: PersistenceSchemaInventory = { formatVersion, roots, types }
const serialized = new Map(nodeKeys.map((key, index) =>
[key, serializeSchemaNode(graph[index] as SchemaNode, child => nodeKeys[child] as string)]))
if (renderPersistenceSnapshot(inventory, serialized) !== text) throw new Error('schema inventory is not in canonical layout')
return inventory
}
function validateEventMetadata(schema: CanonicalSchema, event: string, surface: boolean, historical: boolean): void {
@@ -654,7 +664,7 @@ function readPersistenceEntries(root: string, allowIncompleteId?: string): Persi
const change = parseDocument(source, filename, allowIncomplete)
const snapshotName = `${change.id}.schema.json`
if (!snapshots.delete(snapshotName)) throw new Error(`${filename}: missing schema snapshot ${snapshotName}`)
const snapshot = parsePersistenceSnapshot(JSON.parse(readFileSync(join(directory, snapshotName), 'utf8')))
const snapshot = parsePersistenceSnapshot(readFileSync(join(directory, snapshotName), 'utf8'))
const translatedName = `${change.id}.zh.md`
if (!files.includes(translatedName)) throw new Error(`${filename}: missing Chinese counterpart`)
const translated = readFileSync(join(directory, translatedName), 'utf8').replaceAll('\r\n', '\n')
@@ -705,8 +715,7 @@ export function verifyPersistenceChanges(root: string, current: PersistenceSchem
if (!existsSync(committedPath)) {
throw new PersistenceChangeFailure(`${CURRENT_SCHEMA} is missing; regenerate the persistence catalog`, 'generated-artifact-missing', differences, transitions)
}
const committed = parsePersistenceSnapshot(JSON.parse(readFileSync(committedPath, 'utf8')))
if (JSON.stringify(committed) !== JSON.stringify(current)) {
if (readFileSync(committedPath, 'utf8') !== encodePersistenceSnapshot(current)) {
throw new PersistenceChangeFailure(`${CURRENT_SCHEMA} is stale; regenerate the persistence catalog`, 'stale-artifacts', differences, transitions)
}
if (differences.length !== 0) {
@@ -812,7 +821,7 @@ function executeCommand(
const operation = commandOperation(args)
if (operation === 'check' && values.prose !== undefined) throw new Error('--prose requires --baseline, --record, or --update')
const prose = values.prose === undefined ? undefined : parsePersistenceProse(JSON.parse(readFileSync(resolve(root, values.prose), 'utf8')))
const current = parsePersistenceSnapshot(extract(root))
const current = parsePersistenceSnapshot(encodePersistenceSnapshot(extract(root)))
if (operation === 'check') {
const history = verifyPersistenceChanges(root, current)
return { schemaVersion: 1, ok: true, operation,
@@ -867,7 +876,7 @@ function executeCommand(
parseDocument(chinese, `${id}.md`, prose === undefined && existing === undefined)
const recordFiles = [
...renderPersistencePair(root, `${HISTORY_DIRECTORY}/${id}.md`, english, chinese),
{ path: `${HISTORY_DIRECTORY}/${id}.schema.json`, content: JSON.stringify(snapshot, null, 2) + '\n' },
{ path: `${HISTORY_DIRECTORY}/${id}.schema.json`, content: encodePersistenceSnapshot(snapshot) },
]
if (!update && recordFiles.some(file => existsSync(join(root, file.path)))) throw new Error(`${id}: acknowledgement file already exists`)
const outputs = [...artifacts(root, current), ...recordFiles]
+1 -1
View File
@@ -9,7 +9,7 @@ import { canonicalizeSchema, schemaDigest } from './persistence-schema-model.ts'
import { classifyPersistenceChange, parsePersistenceSnapshot } from './persistence-changes.ts'
function toolAdditionSchema() {
const inventory = parsePersistenceSnapshot(JSON.parse(readFileSync(new URL('../docs/persistence-schema.json', import.meta.url), 'utf8')) as unknown)
const inventory = parsePersistenceSnapshot(readFileSync(new URL('../docs/persistence-schema.json', import.meta.url), 'utf8'))
const before = inventory.roots.find(root => root.key === 'event:developer/message')!
const nodes = [...structuredClone(before.schema.nodes)]
const blockIndex = nodes.findIndex(node => node.kind === 'object' && node.properties.some((property) => {
+19 -11
View File
@@ -7,7 +7,7 @@ import { dump } from 'js-yaml'
import { afterEach, describe, expect, it } from 'vitest'
import { loadPersistenceFormats, runPersistenceFormats } from './persistence-formats.ts'
import { parseHistoricalPersistenceSnapshot } from './persistence-changes.ts'
import { canonicalizeSchema, schemaDigest } from './persistence-schema-model.ts'
import { canonicalizeSchema, encodePersistenceSnapshot, schemaDigest } from './persistence-schema-model.ts'
import type { PersistenceRoot, PersistenceSchemaInventory, PersistenceType, SchemaNode } from './persistence-schema-model.ts'
const temporary: string[] = []
@@ -57,7 +57,12 @@ function schemaPath(version: number, currentVersion: number): string {
}
function replaceSnapshot(root: string, version: number, currentVersion: number, snapshot: PersistenceSchemaInventory): void {
write(root, schemaPath(version, currentVersion), JSON.stringify(snapshot))
write(root, schemaPath(version, currentVersion), encodePersistenceSnapshot(snapshot))
}
/** Keep a digest's node-table prefix while changing the full digest. */
function driftDigest(digest: string): string {
return digest.slice(0, 16) + '0'.repeat(48)
}
function saveReference(root: string, version: number, currentVersion: number, snapshot = inventory(version)): void {
@@ -125,14 +130,14 @@ describe('archive current persistence format', () => {
expect(runPersistenceFormats(['--archive', '3'], root)).toBe('Archived Session format v3 to docs/persistence-changes/historical-formats/v3.schema.json.')
const serialized = readFileSync(join(root, 'docs/persistence-changes/historical-formats/v3.schema.json'), 'utf8')
const archived = parseHistoricalPersistenceSnapshot(JSON.parse(serialized))
const archived = parseHistoricalPersistenceSnapshot(serialized)
expect(archived).toEqual({
...original,
types: types.map(type => ({ ...type, sources: [
'packages/example/src/types.ts', 'packages/other/src/types.ts', 'packages/plain/src/types.ts', 'packages/fragment/src/types.ts',
] })),
})
expect(serialized).toBe(JSON.stringify(archived, null, 2) + '\n')
expect(serialized).toBe(encodePersistenceSnapshot(archived))
expect(readFileSync(join(root, 'docs/persistence-schema.json'), 'utf8')).toBe(before)
expect(existsSync(join(root, 'docs/persistence-changes/historical-formats/v3.md'))).toBe(false)
@@ -146,7 +151,7 @@ describe('archive current persistence format', () => {
const root = fixture(0)
rmSync(join(root, 'docs/persistence-changes/historical-formats'), { recursive: true })
expect(runPersistenceFormats(['--root', root, '--archive', '0'])).toBe('Archived Session format v0 to docs/persistence-changes/historical-formats/v0.schema.json.')
expect(JSON.parse(readFileSync(join(root, 'docs/persistence-changes/historical-formats/v0.schema.json'), 'utf8'))).toEqual(inventory(0))
expect(parseHistoricalPersistenceSnapshot(readFileSync(join(root, 'docs/persistence-changes/historical-formats/v0.schema.json'), 'utf8'))).toEqual(inventory(0))
})
it.each(['-1', '1.5', '01', '3junk', '9007199254740992'])('rejects an invalid archive version %s', (version) => {
@@ -178,7 +183,7 @@ describe('archive current persistence format', () => {
const root = fixture()
const original = inventory(3)
if (variant === 'invalid-json') write(root, 'docs/persistence-schema.json', '{')
else if (variant === 'digest-drift') replaceSnapshot(root, 3, 3, { ...original, roots: [{ ...original.roots[0]!, digest: '0'.repeat(64) }, ...original.roots.slice(1)] })
else if (variant === 'digest-drift') replaceSnapshot(root, 3, 3, { ...original, roots: [{ ...original.roots[0]!, digest: driftDigest(original.roots[0]!.digest) }, ...original.roots.slice(1)] })
else if (variant === 'missing-type') replaceSnapshot(root, 3, 3, { ...original, types: original.types.slice(1) })
else if (variant === 'missing-source-metadata') edit(root, 'docs/persistence-schema.json', ',"sources":[]', '')
else replaceSnapshot(root, 3, 3, inventory(2))
@@ -378,11 +383,14 @@ describe('complete persistence format references', () => {
const root = fixture()
const original = inventory(1)
const first = original.roots[0]!
const changed = variant === 'duplicate-root' ? { ...original, roots: [...original.roots, first] }
: variant === 'invalid-digest' ? { ...original, roots: [{ ...first, digest: '0'.repeat(64) }, ...original.roots.slice(1)] }
: variant === 'invalid-graph' ? { ...original, roots: [{ ...first, schema: { root: 0, nodes: [{ kind: 'array', element: 99 }] } }, ...original.roots.slice(1)] }
: { ...original, extra: true }
write(root, schemaPath(1, 3), JSON.stringify(changed))
if (variant === 'duplicate-root') replaceSnapshot(root, 1, 3, { ...original, roots: [...original.roots, first] })
else if (variant === 'invalid-digest') replaceSnapshot(root, 1, 3, { ...original, roots: [{ ...first, digest: driftDigest(first.digest) }, ...original.roots.slice(1)] })
else {
const changed = JSON.parse(encodePersistenceSnapshot(original)) as { nodes: Record<string, unknown>; extra?: boolean }
if (variant === 'invalid-graph') changed.nodes[first.digest.slice(0, 16)] = { kind: 'array', element: '9'.repeat(16) }
else changed.extra = true
write(root, schemaPath(1, 3), JSON.stringify(changed))
}
expect(() => loadPersistenceFormats(root)).toThrow(variant === 'duplicate-root' ? 'duplicate schema root' : variant === 'invalid-digest' ? 'schema digest mismatch' : variant === 'invalid-graph' ? 'unknown schema node' : 'unknown field extra')
})
+4 -4
View File
@@ -7,7 +7,7 @@ import { JSON_SCHEMA, load } from 'js-yaml'
import { readCurrentSessionFormatVersion } from './gen-session-format-catalog.ts'
import { parseHistoricalPersistenceSnapshot, parsePersistenceSnapshot } from './persistence-changes.ts'
import { persistenceFormatFactArtifacts } from './persistence-format-facts.ts'
import { canonicalizeSchema, schemaDigest } from './persistence-schema-model.ts'
import { canonicalizeSchema, encodePersistenceSnapshot, schemaDigest } from './persistence-schema-model.ts'
import type { PersistenceSchemaInventory } from './persistence-schema-model.ts'
import { withoutPersistenceSourceLines } from './persistence-source-metadata.ts'
@@ -166,7 +166,7 @@ export function loadPersistenceFormats(root: string): PersistenceFormats {
if (block !== machineBlock(chinese, document.replace(/\.md$/u, '.zh.md'))) throw new Error(`v${version}: bilingual machine records differ`)
record = parseRecord(block, version)
}
const inventory = (current ? parsePersistenceSnapshot : parseHistoricalPersistenceSnapshot)(JSON.parse(read(schemaPath)))
const inventory = (current ? parsePersistenceSnapshot : parseHistoricalPersistenceSnapshot)(read(schemaPath))
validateInventory(inventory, version, current)
const recordedRoots = record?.roots
if (recordedRoots !== undefined && (recordedRoots.size !== inventory.roots.length
@@ -190,11 +190,11 @@ function archiveCurrentInventory(root: string, requestedVersion: string): string
if (version !== currentVersion) throw new Error(`Cannot archive v${version}: current writer is v${currentVersion}`)
const path = `${DIRECTORY}/v${version}.schema.json`
if (existsSync(join(root, path))) throw new Error(`Cannot archive v${version}: ${path} already exists`)
const inventory = parsePersistenceSnapshot(JSON.parse(readFileSync(join(root, CURRENT_SCHEMA), 'utf8')))
const inventory = parsePersistenceSnapshot(readFileSync(join(root, CURRENT_SCHEMA), 'utf8'))
const reachable = validateInventory(inventory, version, true)
const archive = withoutPersistenceSourceLines({ ...inventory, types: inventory.types.filter(type => reachable.has(type.digest)) })
mkdirSync(join(root, DIRECTORY), { recursive: true })
writeFileSync(join(root, path), JSON.stringify(archive, null, 2) + '\n', { flag: 'wx' })
writeFileSync(join(root, path), encodePersistenceSnapshot(archive), { flag: 'wx' })
return `Archived Session format v${version} to ${path}.`
}
+5 -5
View File
@@ -6,7 +6,7 @@ import { join } from 'node:path'
import { dump } from 'js-yaml'
import { afterEach, describe, expect, it } from 'vitest'
import { parsePersistenceSnapshot } from './persistence-changes.ts'
import { canonicalizeSchema, schemaDigest } from './persistence-schema-model.ts'
import { canonicalizeSchema, encodePersistenceSnapshot, schemaDigest } from './persistence-schema-model.ts'
import type { PersistenceRoot, PersistenceSchemaInventory, PersistenceType, SchemaNode } from './persistence-schema-model.ts'
import { loadPersistenceReleases, runPersistenceReleases } from './persistence-releases.ts'
import type { PersistenceReleaseManifest, PersistenceReleaseRecord } from './persistence-releases.ts'
@@ -108,14 +108,14 @@ function fixture(): Fixture {
writeFileSync(join(directory, 'manifest.json'), JSON.stringify(manifest))
for (const [index, record] of records.entries()) {
saveRecord(directory, record)
writeFileSync(join(directory, record.tag + '.schema.json'), JSON.stringify(snapshots[index]))
writeFileSync(join(directory, record.tag + '.schema.json'), encodePersistenceSnapshot(snapshots[index]!))
writeFileSync(join(directory, record.tag + '.i18n.yaml'), 'schemaVersion: 1\n')
}
return { root, directory, manifest, records, snapshots }
}
function replaceSnapshot(fixture: Fixture, index: number, snapshot: PersistenceSchemaInventory): void {
writeFileSync(join(fixture.directory, TAGS[index]! + '.schema.json'), JSON.stringify(snapshot))
writeFileSync(join(fixture.directory, TAGS[index]! + '.schema.json'), encodePersistenceSnapshot(snapshot))
}
describe('pinned persistence releases', () => {
@@ -148,7 +148,7 @@ describe('pinned persistence releases', () => {
expect(archive.entries[1]!.differences).toEqual([expect.objectContaining({ root: 'event:example/value', kind: 'type-changed', requiresCompatibilityReview: true })])
expect(archive.entries[2]!.roots).toEqual(archive.entries[1]!.roots)
expect(archive.entries[2]!.differences).toEqual([])
expect(() => parsePersistenceSnapshot(data.snapshots[1])).toThrow('surface metadata')
expect(() => parsePersistenceSnapshot(encodePersistenceSnapshot(data.snapshots[1]!))).toThrow('surface metadata')
})
it.each(['.md', '.zh.md', '.i18n.yaml', '.schema.json'])('rejects a missing manifest release companion %s', (suffix) => {
@@ -369,7 +369,7 @@ describe('release facts', () => {
const data = fixture()
const snapshotPath = join(data.directory, TAGS[2] + '.schema.json')
const snapshot = readFileSync(snapshotPath, 'utf8')
writeFileSync(snapshotPath, snapshot.replace('"formatVersion":1', '"formatVersion":3'))
writeFileSync(snapshotPath, snapshot.replace('"formatVersion": 1', '"formatVersion": 3'))
const corrupt = directoryBytes(data.directory)
expect(() => runPersistenceReleases(['--write'], data.root)).toThrow('normalization version')
expect(directoryBytes(data.directory)).toEqual(corrupt)
+1 -1
View File
@@ -206,7 +206,7 @@ export function loadPersistenceReleases(root: string): PersistenceReleases {
const chinese = machineBlock(read('.zh.md'), `${release.tag}.zh.md`)
if (english !== chinese) throw new Error(`${release.tag}: bilingual machine records differ`)
const record = parseRecord(english, release, entries.at(-1)?.release.tag ?? null)
const snapshot = parseHistoricalPersistenceSnapshot(JSON.parse(read('.schema.json')))
const snapshot = parseHistoricalPersistenceSnapshot(read('.schema.json'))
validateTypes(snapshot, release.tag)
const afterRoots = new Map(snapshot.roots.map(root => [root.key, root]))
const expectedRoots = record.changes.filter(change => change.after !== null).map(change => change.root).sort()
+3 -3
View File
@@ -5,7 +5,7 @@ import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
import { classifyPersistenceChange } from './persistence-changes.ts'
import { canonicalizeSchema, schemaDigest, type PersistenceSchemaInventory, type SchemaNode, type SchemaProperty } from './persistence-schema-model.ts'
import { canonicalizeSchema, encodePersistenceSnapshot, schemaDigest, type PersistenceSchemaInventory, type SchemaNode, type SchemaProperty } from './persistence-schema-model.ts'
import { renderPersistenceReview, reviewPersistenceSchemas, runPersistenceReview } from './persistence-review.ts'
function fixture(options: {
@@ -116,8 +116,8 @@ describe('persistence review', () => {
try {
const before = join(directory, 'before.json')
const after = join(directory, 'after.json')
const oldText = JSON.stringify(fixture())
const newText = JSON.stringify(fixture({ extraKind: true }))
const oldText = encodePersistenceSnapshot(fixture())
const newText = encodePersistenceSnapshot(fixture({ extraKind: true }))
writeFileSync(before, oldText)
writeFileSync(after, newText)
const args = ['--before', before, '--after', after]
+1 -1
View File
@@ -215,7 +215,7 @@ export function renderPersistenceReview(report: PersistenceReview): string {
export function runPersistenceReview(args: readonly string[]): string {
const { values } = parseArgs({ args: [...args], options: { before: { type: 'string' }, after: { type: 'string' }, json: { type: 'boolean' } }, strict: true, allowPositionals: false })
if (values.before === undefined || values.after === undefined) throw new Error('persistence review: --before and --after inventory paths are required')
const read = (path: string): PersistenceSchemaInventory => parsePersistenceSnapshot(JSON.parse(readFileSync(path, 'utf8')))
const read = (path: string): PersistenceSchemaInventory => parsePersistenceSnapshot(readFileSync(path, 'utf8'))
const report = reviewPersistenceSchemas(read(values.before), read(values.after))
return values.json ? JSON.stringify(report, null, 2) + '\n' : renderPersistenceReview(report)
}
+93 -36
View File
@@ -1,10 +1,10 @@
/**
* Source-independent JSON type graphs and canonical persistence fingerprints.
* Numeric references are local to one graph; names and source locations are metadata.
* Numeric references are local to one in-memory graph; serialized snapshots reference nodes by digest prefix.
* Names and source locations are metadata.
*/
import { createHash } from 'node:crypto'
import { isDeepStrictEqual } from 'node:util'
/** Recorded reader promise for explicitly attributed source additions. */
export interface SourceCompatibility {
@@ -72,51 +72,108 @@ export interface PersistenceSchemaInventory {
readonly types: readonly PersistenceType[]
}
/** Type metadata with an explicit graph when roots cannot reconstruct it exactly. */
export interface PersistenceTypeIndex extends Omit<PersistenceType, 'schema'> {
readonly schema?: CanonicalSchema
}
/** Serialized node: the node fields with every reference replaced by a node key. */
export type SerializedSchemaNode =
| Extract<SchemaNode, { kind: 'primitive' | 'literal' | 'opaque' }>
| { readonly kind: 'array'; readonly element: string }
| { readonly kind: 'tuple'; readonly elements: readonly (Omit<SchemaTupleElement, 'type'> & { readonly type: string })[] }
| {
readonly kind: 'object'
readonly properties: readonly (Omit<SchemaProperty, 'type'> & { readonly type: string })[]
readonly indices: readonly { readonly key: string; readonly value: string }[]
}
| { readonly kind: 'union'; readonly types: readonly string[] }
/** Compact inventory representation; formatVersion still pins normalization. */
export interface PersistenceSchemaSnapshot {
readonly formatVersion: PersistenceSchemaInventory['formatVersion']
readonly roots: readonly PersistenceRoot[]
readonly types: readonly PersistenceTypeIndex[]
/** Length of the digest prefix that names a node in a serialized node table. */
export const SCHEMA_NODE_KEY_LENGTH = 16
/**
* Name a node by the digest prefix of the canonical subgraph it roots.
* @param nodes - graph containing the node.
* @param index - node to name.
* @returns key shared by every bisimilar node in any graph.
*/
export function schemaNodeKey(nodes: readonly SchemaNode[], index: number): string {
return schemaDigest(canonicalizeSchema(nodes, index)).slice(0, SCHEMA_NODE_KEY_LENGTH)
}
/**
* Index every normalized type reachable from the supplied graphs.
* @param schemas - complete canonical root graphs.
* @returns canonical subgraphs keyed by their structural digest.
* Produce the node-table entry for one node from its canonical subgraph.
* @param nodes - graph containing the node; its node may be non-canonical.
* @param index - node to serialize.
* @returns the node key and canonical serialized node.
*/
export function reachableSchemaTypes(schemas: readonly CanonicalSchema[]): Map<string, CanonicalSchema> {
const types = new Map<string, CanonicalSchema>()
for (const schema of schemas) {
for (const [index] of schema.nodes.entries()) {
const type = canonicalizeSchema(schema.nodes, index)
types.set(schemaDigest(type), type)
export function schemaTableEntry(nodes: readonly SchemaNode[], index: number): readonly [string, SerializedSchemaNode] {
const canonical = canonicalizeSchema(nodes, index)
const node = serializeSchemaNode(canonical.nodes[0] as SchemaNode, child => schemaNodeKey(canonical.nodes, child))
return [schemaDigest(canonical).slice(0, SCHEMA_NODE_KEY_LENGTH), node]
}
/**
* Replace node references with node keys, ordering unordered references by key.
* @param node - graph node.
* @param key - key of a referenced node index.
* @returns the serialized node.
*/
export function serializeSchemaNode(node: SchemaNode, key: (index: number) => string): SerializedSchemaNode {
switch (node.kind) {
case 'array': return { kind: 'array', element: key(node.element) }
case 'tuple': return { kind: 'tuple', elements: node.elements.map(element => ({ type: key(element.type), optional: element.optional, rest: element.rest })) }
case 'object': return {
kind: 'object',
properties: node.properties.map(property => ({ name: property.name, type: key(property.type), optional: property.optional,
...(property.compatibility === undefined ? {} : { compatibility: property.compatibility }) })),
indices: node.indices.map(index => ({ key: key(index.key), value: key(index.value) }))
.sort((left, right) => compare(left.key, right.key) || compare(left.value, right.value)),
}
case 'union': return { kind: 'union', types: node.types.map(key).sort(compare) }
case 'primitive':
case 'literal':
case 'opaque': return node
default: return assertNever(node)
}
}
/**
* Serialize an inventory with one node table keyed by subgraph digest prefix.
* Roots and types locate their graph through their digest prefix,
* so a type change rewrites only the changed node, its ancestors, and their digests.
* @param inventory - validated inventory with canonical graphs.
* @returns JSON text with one root, type, or node per line.
*/
export function encodePersistenceSnapshot(inventory: PersistenceSchemaInventory): string {
const table = new Map<string, SerializedSchemaNode>()
for (const { schema } of [...inventory.roots, ...inventory.types]) {
for (const index of schema.nodes.keys()) {
const [key, node] = schemaTableEntry(schema.nodes, index)
const existing = table.get(key)
if (existing !== undefined && JSON.stringify(existing) !== JSON.stringify(node)) {
throw new Error(`persistence schema: node key collision ${key}`)
}
table.set(key, node)
}
}
return types
return renderPersistenceSnapshot(inventory, table)
}
/**
* Omit type graphs exactly reconstructable from roots, retaining all other graphs.
* @param inventory - expanded inventory, including types erased from roots by normalization.
* @returns a lossless snapshot preserving root graphs, type order, and declaration metadata.
* Render inventory metadata and a node table in the committed text layout.
* @param inventory - root and type metadata; their graphs are not read.
* @param table - every node reachable from the roots and types.
* @returns JSON text with keys sorted and one root, type, or node per line.
*/
export function persistenceSchemaSnapshot(inventory: PersistenceSchemaInventory): PersistenceSchemaSnapshot {
const reachable = reachableSchemaTypes(inventory.roots.map(root => root.schema))
return {
formatVersion: inventory.formatVersion,
roots: inventory.roots,
types: inventory.types.map(({ digest, schema, names, sources }) => ({
digest,
...(isDeepStrictEqual(schema, reachable.get(digest)) ? {} : { schema }),
names,
sources,
})),
}
export function renderPersistenceSnapshot(inventory: PersistenceSchemaInventory, table: ReadonlyMap<string, SerializedSchemaNode>): string {
const lines = (values: readonly string[], open: string, close: string): string =>
values.length === 0 ? open + close : `${open}\n${values.map(value => ` ${value}`).join(',\n')}\n ${close}`
const roots = inventory.roots.map(root => JSON.stringify({ key: root.key, kind: root.kind,
...(root.event === undefined ? {} : { event: root.event }),
...(root.surface === undefined ? {} : { surface: root.surface }), digest: root.digest }))
const types = inventory.types.map(type => JSON.stringify({ digest: type.digest, names: type.names, sources: type.sources }))
const nodes = [...table].sort(([left], [right]) => compare(left, right)).map(([key, node]) => `${JSON.stringify(key)}: ${JSON.stringify(node)}`)
return `{\n "formatVersion": ${String(inventory.formatVersion)},\n`
+ ` "roots": ${lines(roots, '[', ']')},\n`
+ ` "types": ${lines(types, '[', ']')},\n`
+ ` "nodes": ${lines(nodes, '{', '}')}\n}\n`
}
/**
+84 -44
View File
@@ -1,10 +1,10 @@
/** Compact inventories retain root fingerprints and restore shared recursive type graphs. */
/** Snapshot text stores one node table keyed by subgraph digest and restores every root and type graph. */
import { describe, expect, it } from 'vitest'
import { parseHistoricalPersistenceSnapshot, parsePersistenceSnapshot } from './persistence-changes.ts'
import { canonicalizeSchema, persistenceSchemaSnapshot, schemaDigest, type PersistenceSchemaInventory } from './persistence-schema-model.ts'
import { canonicalizeSchema, encodePersistenceSnapshot, schemaDigest, type PersistenceSchemaInventory, type SchemaNode } from './persistence-schema-model.ts'
import { reviewPersistenceSchemas } from './persistence-review.ts'
import { renderPersistenceSchemaDefinitions, renderPersistenceSchemaIndex } from './render-persistence-schema.ts'
import { renderPersistenceSchemaDefinitions } from './render-persistence-schema.ts'
function inventory(): PersistenceSchemaInventory {
const schema = canonicalizeSchema([
@@ -22,22 +22,51 @@ function inventory(): PersistenceSchemaInventory {
}
}
describe('compact persistence snapshots', () => {
it('stores only root graphs and restores shared recursive definitions without changing reports', () => {
interface SnapshotJson {
roots: Record<string, unknown>[]
types: Record<string, unknown>[]
nodes: Record<string, Record<string, unknown>>
}
function edit(text: string, change: (value: SnapshotJson) => void): string {
const value = JSON.parse(text) as SnapshotJson
change(value)
return JSON.stringify(value)
}
describe('persistence snapshot text', () => {
it('stores each shared node once and restores graphs without changing reports', () => {
const original = inventory()
const snapshot = persistenceSchemaSnapshot(original)
expect(snapshot.roots).toEqual(original.roots)
expect(snapshot.types).toEqual(original.types.map(({ digest, names, sources }) => ({ digest, names, sources })))
const restored = parsePersistenceSnapshot(JSON.parse(JSON.stringify(snapshot)))
const text = encodePersistenceSnapshot(original)
const parsed = JSON.parse(text) as SnapshotJson
expect(Object.keys(parsed.nodes)).toHaveLength(3)
expect(parsed.roots[0]).toEqual({ key: 'SessionHeader', kind: 'header', digest: original.roots[0]!.digest })
expect(text.split('\n').every(line => !/"type": \d/u.test(line))).toBe(true)
const restored = parsePersistenceSnapshot(text)
expect(restored).toEqual(original)
expect(reviewPersistenceSchemas(original, restored)).toEqual(reviewPersistenceSchemas(original, original))
for (const rendering of ['current', 'historical'] as const) {
expect(renderPersistenceSchemaIndex(restored, 'en', undefined, 2, rendering))
.toBe(renderPersistenceSchemaIndex(original, 'en', undefined, 2, rendering))
expect(renderPersistenceSchemaDefinitions(restored, 'en', undefined, 2, rendering))
.toBe(renderPersistenceSchemaDefinitions(original, 'en', undefined, 2, rendering))
expect(parseHistoricalPersistenceSnapshot(text)).toEqual(original)
expect(encodePersistenceSnapshot(restored)).toBe(text)
})
it('changes only the new node and its ancestors when a union gains a variant', () => {
const graph = (variants: readonly string[]): PersistenceSchemaInventory => {
const nodes: SchemaNode[] = [
{ kind: 'object', properties: [{ name: 'other', type: 1, optional: false }, { name: 'source', type: 2, optional: false }], indices: [] },
{ kind: 'object', properties: [{ name: 'value', type: 3, optional: false }], indices: [] },
{ kind: 'union', types: variants.map((_, index) => 4 + index) },
{ kind: 'primitive', type: 'number' },
...variants.map((value): SchemaNode => ({ kind: 'literal', value })),
]
const schema = canonicalizeSchema(nodes, 0)
return { formatVersion: 1, roots: [{ key: 'SessionHeader', kind: 'header', digest: schemaDigest(schema), schema }], types: [] }
}
expect(parseHistoricalPersistenceSnapshot(snapshot)).toEqual(original)
const keys = (value: PersistenceSchemaInventory): string[] =>
Object.keys((JSON.parse(encodePersistenceSnapshot(value)) as SnapshotJson).nodes)
const before = new Set(keys(graph(['a', 'c'])))
const after = keys(graph(['a', 'b', 'c']))
expect(after.filter(key => !before.has(key))).toHaveLength(3)
expect([...before].filter(key => !after.includes(key))).toHaveLength(2)
})
it.each([true, false])('retains independent recursive types with root graphs=%s', (withRoots) => {
@@ -48,45 +77,56 @@ describe('compact persistence snapshots', () => {
], 0)
const independent = { digest: schemaDigest(schema), schema, names: ['example.ts#Independent', 'example.ts#Alias'], sources: ['example.ts'] }
const original = { ...base, roots: withRoots ? base.roots : [], types: [independent, ...base.types] }
const snapshot = persistenceSchemaSnapshot(original)
expect(snapshot.types[0]).toEqual(independent)
if (withRoots) expect(snapshot.types.slice(1).every(type => type.schema === undefined)).toBe(true)
else expect(snapshot.types).toEqual(original.types)
const restored = parsePersistenceSnapshot(JSON.parse(JSON.stringify(snapshot)))
const restored = parsePersistenceSnapshot(encodePersistenceSnapshot(original))
expect(restored).toEqual(original)
expect(parseHistoricalPersistenceSnapshot(snapshot)).toEqual(original)
expect(persistenceSchemaSnapshot(restored)).toEqual(snapshot)
expect(renderPersistenceSchemaDefinitions(restored)).toBe(renderPersistenceSchemaDefinitions(original))
})
it('reads legacy full graphs and keeps partial acknowledgement inventories empty', () => {
const original = inventory()
expect(parsePersistenceSnapshot(JSON.parse(JSON.stringify(original)))).toEqual(original)
const partial = { ...original, types: [] }
expect(parsePersistenceSnapshot(persistenceSchemaSnapshot(partial))).toEqual(partial)
it('rejects tampered node keys, unknown references, orphan nodes, and digest mismatches', () => {
const text = encodePersistenceSnapshot(inventory())
const [first, second] = Object.keys((JSON.parse(text) as SnapshotJson).nodes) as [string, string]
expect(() => parsePersistenceSnapshot(edit(text, (value) => {
value.nodes = Object.fromEntries(Object.entries(value.nodes).map(([key, node]) => [key === first ? 'f'.repeat(16) : key, node]))
}))).toThrow(/key does not match|unknown schema node/u)
expect(() => parsePersistenceSnapshot(edit(text, (value) => { value.nodes[first] = { kind: 'array', element: '0'.repeat(16) } })))
.toThrow('unknown schema node')
expect(() => parsePersistenceSnapshot(edit(text, (value) => { value.nodes[second] = { kind: 'primitive', type: 'number' } })))
.toThrow('key does not match')
expect(() => parsePersistenceSnapshot(edit(encodePersistenceSnapshot({ ...inventory(), roots: [] }), (value) => { value.types = [] })))
.toThrow('not reachable from any root or type')
expect(() => parsePersistenceSnapshot(edit(text, (value) => { value.roots[0]!.digest = 'f'.repeat(64) }))).toThrow('unknown schema node')
})
it('rejects compact digests that do not identify a root subgraph', () => {
const snapshot = persistenceSchemaSnapshot(inventory())
const schema = canonicalizeSchema([{ kind: 'literal', value: 'unreachable' }], 0)
expect(() => parsePersistenceSnapshot({ ...snapshot, types: [{ digest: schemaDigest(schema), names: [], sources: [] }] }))
.toThrow('is not reachable from roots')
it('rejects node entries whose properties or union members are not in canonical order', () => {
const text = encodePersistenceSnapshot(inventory())
const nodes = (JSON.parse(text) as SnapshotJson).nodes
const [objectKey] = Object.entries(nodes).find(([, node]) => ((node.properties as unknown[] | undefined)?.length ?? 0) > 1)!
expect(() => parsePersistenceSnapshot(edit(text, (value) => {
(value.nodes[objectKey]!.properties as unknown[]).reverse()
}))).toThrow('node is not canonical')
const unionSchema = canonicalizeSchema([
{ kind: 'union', types: [1, 2] }, { kind: 'literal', value: 'a' }, { kind: 'literal', value: 'b' },
], 0)
const union = encodePersistenceSnapshot({ formatVersion: 1, roots: [],
types: [{ digest: schemaDigest(unionSchema), schema: unionSchema, names: [], sources: [] }] })
const [unionKey] = Object.entries((JSON.parse(union) as SnapshotJson).nodes).find(([, node]) => node.kind === 'union')!
expect(() => parsePersistenceSnapshot(edit(union, (value) => {
const types = value.nodes[unionKey]!.types as string[]
value.nodes[unionKey]!.types = [types[0], ...types]
}))).toThrow('node is not canonical')
})
it('still validates explicit legacy graphs instead of replacing them with root graphs', () => {
const original = inventory()
const schema = canonicalizeSchema([{ kind: 'primitive', type: 'number' }], 0)
expect(() => parsePersistenceSnapshot({ ...original, types: original.types.map(type => ({ ...type, schema })) }))
.toThrow('shared schema digest mismatch')
it('rejects valid content outside the canonical text layout', () => {
const text = encodePersistenceSnapshot(inventory())
expect(() => parsePersistenceSnapshot(JSON.stringify(JSON.parse(text), null, 2) + '\n')).toThrow('canonical layout')
})
it('rejects invalid metadata and malformed explicit schemas in compact inventories', () => {
const snapshot = persistenceSchemaSnapshot(inventory())
const type = snapshot.types[0]!
expect(() => parsePersistenceSnapshot({ ...snapshot, types: [{ ...type, names: [3] }] })).toThrow('type name')
expect(() => parsePersistenceSnapshot({ ...snapshot, types: [{ ...type, sources: [3] }] })).toThrow('type source')
expect(() => parsePersistenceSnapshot({ ...snapshot, types: [{ ...type, schema: null }] })).toThrow('shared schema')
expect(() => parseHistoricalPersistenceSnapshot({ ...snapshot, types: [{ ...type, sources: ['types.ts:3'] }] }))
it('rejects invalid type metadata', () => {
const text = encodePersistenceSnapshot(inventory())
expect(() => parsePersistenceSnapshot(edit(text, (value) => { value.types[0]!.names = [3] }))).toThrow('type name')
expect(() => parsePersistenceSnapshot(edit(text, (value) => { value.types[0]!.sources = [3] }))).toThrow('type source')
expect(() => parsePersistenceSnapshot(edit(text, (value) => { value.types[0]!.schema = {} }))).toThrow('unknown field schema')
expect(() => parseHistoricalPersistenceSnapshot(edit(text, (value) => { value.types[0]!.sources = ['types.ts:3'] })))
.toThrow('must omit line numbers')
})
})
+3 -3
View File
@@ -5,7 +5,7 @@ import { afterEach, describe, expect, it } from 'vitest'
import { collectLogEvents } from './gen-persistence-catalog.ts'
import { extractPersistenceSchema } from './persistence-schema.ts'
import { classifyPersistenceChange, parsePersistenceSnapshot } from './persistence-changes.ts'
import { canonicalizeSchema, isArbitraryJsonSchema, schemaDigest, type PersistenceSchemaInventory } from './persistence-schema-model.ts'
import { canonicalizeSchema, encodePersistenceSnapshot, isArbitraryJsonSchema, schemaDigest, type PersistenceSchemaInventory } from './persistence-schema-model.ts'
const roots: string[] = []
afterEach(() => {
@@ -63,7 +63,7 @@ describe('persistent source type extraction', () => {
expect(inventory.types.some(type => type.names.includes('packages/domain/payload/src/types.ts#Section'))).toBe(true)
expect(inventory.types.some(type => type.names.length === 0 && type.sources.includes('packages/domain/payload/src/types.ts:2'))).toBe(true)
expect(inventory.types.some(type => type.schema.nodes[0]?.kind === 'array' && type.names.length === 0)).toBe(true)
expect(parsePersistenceSnapshot(inventory)).toEqual(inventory)
expect(parsePersistenceSnapshot(encodePersistenceSnapshot(inventory))).toEqual(inventory)
})
it('ignores alias names, files, documentation, readonly, brands and property order', () => {
@@ -421,7 +421,7 @@ describe('source compatibility authoring', () => {
discriminator: 'kind', unknownKinds: 'preserve', attributionKinds: ['attribution'] })
expect(classifyPersistenceChange(before.roots.find(root => root.kind === 'event')!, root))
.toEqual([expect.objectContaining({ kind: 'attribution-kind-added', requiresCompatibilityReview: false })])
expect(parsePersistenceSnapshot({ ...after, types: [] })).toEqual({ ...after, types: [] })
expect(parsePersistenceSnapshot(encodePersistenceSnapshot({ ...after, types: [] }))).toEqual({ ...after, types: [] })
})
it('binds inherited source declarations only on explicitly eligible required roles', () => {