1use serde_json::{json, Value as JsonValue};
2
3use super::{
4 SessionBundleError, SESSION_BUNDLE_SCHEMA_ID, SESSION_BUNDLE_SCHEMA_VERSION,
5 SESSION_BUNDLE_TYPE,
6};
7
8pub fn session_bundle_schema() -> JsonValue {
9 json!({
10 "$schema": "https://json-schema.org/draft/2020-12/schema",
11 "$id": SESSION_BUNDLE_SCHEMA_ID,
12 "title": "Harn Session Bundle v1",
13 "type": "object",
14 "additionalProperties": true,
15 "required": [
16 "_type",
17 "schema_version",
18 "bundle_id",
19 "created_at",
20 "producer",
21 "source",
22 "runtime",
23 "transcript",
24 "tools",
25 "permissions",
26 "replay",
27 "redaction",
28 "attachments"
29 ],
30 "properties": {
31 "_type": {"const": SESSION_BUNDLE_TYPE},
32 "schema_version": {"type": "integer", "minimum": 1, "maximum": SESSION_BUNDLE_SCHEMA_VERSION},
33 "bundle_id": {"type": "string", "minLength": 1},
34 "created_at": {"type": "string", "minLength": 1},
35 "producer": {
36 "type": "object",
37 "required": ["name", "version", "schema_id"],
38 "additionalProperties": true,
39 "properties": {
40 "name": {"type": "string"},
41 "version": {"type": "string"},
42 "schema_id": {"type": "string"}
43 }
44 },
45 "source": {
46 "type": "object",
47 "required": ["kind", "run_record_id", "workflow_id", "task", "status"],
48 "additionalProperties": true,
49 "properties": {
50 "kind": {"type": "string"},
51 "run_record_id": {"type": "string"},
52 "workflow_id": {"type": "string"},
53 "workflow_name": {"type": ["string", "null"]},
54 "task": {"type": "string"},
55 "status": {"type": "string"},
56 "started_at": {"type": "string"},
57 "finished_at": {"type": ["string", "null"]},
58 "persisted_path": {"type": ["string", "null"]},
59 "root_run_id": {"type": ["string", "null"]},
60 "parent_run_id": {"type": ["string", "null"]},
61 "child_run_count": {"type": "integer", "minimum": 0}
62 }
63 },
64 "runtime": {
65 "type": "object",
66 "required": ["harn_version", "provider_models"],
67 "additionalProperties": true,
68 "properties": {
69 "harn_version": {"type": "string"},
70 "provider_models": {"type": "array", "items": {"type": "string"}},
71 "usage": {"type": ["object", "null"]},
72 "metadata": {"type": "object"}
73 }
74 },
75 "workspace": {"type": ["object", "null"]},
76 "transcript": {
77 "type": "object",
78 "required": ["sections"],
79 "additionalProperties": true,
80 "properties": {
81 "sections": {
82 "type": "array",
83 "items": {"$ref": "#/$defs/transcript_section"}
84 }
85 }
86 },
87 "tools": {
88 "type": "object",
89 "required": ["schemas", "calls"],
90 "additionalProperties": true,
91 "properties": {
92 "schemas": {"type": "array", "items": {"$ref": "#/$defs/json_entry"}},
93 "calls": {"type": "array", "items": {"type": "object"}}
94 }
95 },
96 "permissions": {"type": "array", "items": {"type": "object"}},
97 "replay": {
98 "type": "object",
99 "required": ["event_log_pointers", "transitions", "checkpoints", "trace_spans", "deterministic_events"],
100 "additionalProperties": true,
101 "properties": {
102 "replay_fixture": {"type": ["object", "null"]},
103 "run_record": {"type": ["object", "null"]},
104 "event_log_pointers": {"type": "array", "items": {"type": "object"}},
105 "transitions": {"type": "array", "items": {"type": "object"}},
106 "checkpoints": {"type": "array", "items": {"type": "object"}},
107 "trace_spans": {"type": "array", "items": {"type": "object"}},
108 "deterministic_events": {"type": "array", "items": {"$ref": "#/$defs/json_entry"}}
109 }
110 },
111 "redaction": {
112 "type": "object",
113 "required": ["mode", "policy", "placeholder", "entries", "unsafe_secret_markers_rejected"],
114 "additionalProperties": true,
115 "properties": {
116 "mode": {"enum": ["local", "sanitized", "replay_only"]},
117 "policy": {"type": "string"},
118 "placeholder": {"type": "string"},
119 "entries": {"type": "array", "items": {"type": "object"}},
120 "unsafe_secret_markers_rejected": {"type": "boolean"}
121 }
122 },
123 "attachments": {"type": "array", "items": {"type": "object"}},
124 "metadata": {"type": "object"}
125 },
126 "$defs": {
127 "json_entry": {
128 "type": "object",
129 "required": ["source", "index", "value"],
130 "additionalProperties": true,
131 "properties": {
132 "source": {"type": "string"},
133 "index": {"type": "integer", "minimum": 0},
134 "value": true
135 }
136 },
137 "transcript_section": {
138 "type": "object",
139 "required": ["id", "label", "scope", "location", "messages", "events", "assets", "metadata"],
140 "additionalProperties": true,
141 "properties": {
142 "id": {"type": "string"},
143 "label": {"type": "string"},
144 "scope": {"type": "string"},
145 "location": {"type": "string"},
146 "summary": {"type": ["string", "null"]},
147 "messages": {"type": "array", "items": true},
148 "events": {"type": "array", "items": true},
149 "assets": {"type": "array", "items": true},
150 "metadata": {"type": "object"}
151 }
152 }
153 }
154 })
155}
156
157pub fn session_bundle_schema_pretty() -> Result<String, SessionBundleError> {
158 serde_json::to_string_pretty(&session_bundle_schema())
159 .map(|schema| format!("{schema}\n"))
160 .map_err(|error| SessionBundleError::Encode(error.to_string()))
161}