1use std::{collections::BTreeMap, sync::OnceLock};
18
19use anyhow::{Result, anyhow};
20use schemars::{JsonSchema, schema_for};
21use serde::Serialize;
22use serde_json::Value;
23
24use super::{RecoveryAdvice, command_catalog};
25use crate::cli::{Cli, should_output_json};
26
27static SCHEMA_VERBS: OnceLock<Vec<&'static str>> = OnceLock::new();
28static DOCUMENTED_SCHEMA_VERBS: OnceLock<Vec<&'static str>> = OnceLock::new();
29static OPAQUE_SCHEMA_VERBS: OnceLock<Vec<&'static str>> = OnceLock::new();
30
31macro_rules! schema_registry {
32 ($(($verbs:expr, $schema:ty)),+ $(,)?) => {
33 fn schema_for_registered_verb(verb: &str) -> Option<Value> {
34 $(
35 if $verbs.contains(&verb) {
36 let root = schema_for!($schema);
37 return serde_json::to_value(&root).ok();
38 }
39 )+
40 None
41 }
42
43 #[cfg(test)]
44 fn schema_implementation_verbs() -> Vec<&'static str> {
45 let mut verbs = Vec::new();
46 $(
47 for verb in $verbs {
48 if !verbs.contains(verb) {
49 verbs.push(*verb);
50 }
51 }
52 )+
53 verbs
54 }
55 };
56}
57
58schema_registry! {
59 (&["init"], InitSchema),
60 (&["status"], StatusSchema),
61 (&["verify"], VerifySchema),
62 (&["adopt"], AdoptSchema),
63 (&["capture"], CaptureSchema),
64 (&["commit"], CommitSchema),
65 (&["checkpoint"], CheckpointSchema),
66 (&["undo", "undo --redo"], UndoSchema),
67 (&["undo --list"], UndoListSchema),
68 (&["clean"], CleanSchema),
69 (&["diff"], DiffSchema),
70 (&["switch"], SwitchCheckoutSchema),
71 (&["merge --preview"], MergePreviewSchema),
72 (&["ready"], ReadySchema),
73 (&["land"], LandSchema),
74 (&["sync"], SyncSchema),
75 (&["continue", "abort"], OperatorCommandSchema),
76 (&["start"], ThreadStartSchema),
77 (&["thread create", "thread switch", "thread rename"], ThreadStartSchema),
78 (&["thread current"], ThreadCurrentSchema),
79 (&["thread captures"], ThreadCapturesSchema),
80 (&["thread refresh", "thread drop"], ThreadCommandSchema),
81 (&["thread promote"], ThreadCommandSchema),
82 (&["thread move"], ThreadMoveSchema),
83 (&["thread absorb"], ThreadAbsorbSchema),
84 (&["thread resolve"], ThreadResolveSchema),
85 (&["thread approve"], ThreadApprovalSchema),
86 (&["thread approvals"], ThreadApprovalListSchema),
87 (&["thread revoke-approval"], ThreadRevokeApprovalSchema),
88 (&["thread check-merge"], ThreadMergeEligibilitySchema),
89 (&["thread cleanup"], ThreadCleanupSchema),
90 (&["thread marker list"], ThreadMarkerListSchema),
91 (&["thread marker create", "thread marker delete", "thread marker show"], ThreadMarkerOpSchema),
92 (&["thread show"], ThreadShowSchema),
93 (&["clone"], CloneSchema),
94 (&["remote list"], RemoteListSchema),
95 (&["remote show"], RemoteInfoSchema),
96 (&["remote add", "remote remove", "remote set-default"], RemoteMutationSchema),
97 (&["fetch"], FetchSchema),
98 (&["pull"], PullSchema),
99 (&["push"], PushSchema),
100 (&["bridge git status"], BridgeGitStatusSchema),
101 (&["expand"], ExpandSchema),
102 (&["log"], LogSchema),
103 (&["log --reflog"], LogReflogSchema),
104 (&["log --timeline"], TimelineLogSchema),
105 (&["timeline fork", "timeline reset", "timeline recover"], TimelineActionSchema),
106 (&["show"], ShowSchema),
107 (&["thread list"], ThreadListSchema),
108 (&["schemas"], SchemasListSchema),
109 (&["review show"], ReviewShowSchema),
110 (&["review sign"], ReviewSignSchema),
111 (&["review next"], ReviewNextSchema),
112 (&["review health"], ReviewHealthSchema),
113 (&["retro"], RetroSchema),
114 (&["discuss open", "discuss append", "discuss resolve", "discuss show"], DiscussionEnvelopeSchema),
115 (&["discuss list"], DiscussionListSchema),
116 (&["query"], QuerySchema),
117 (&["query --attribution"], BlameSchema),
118 (&["transaction commit"], TransactionCommitSchema),
119 (&["bridge git init"], BridgeInitSchema),
120 (&["bridge git export"], BridgeExportSchema),
121 (&["bridge git import"], BridgeImportSchema),
122 (&["bridge git sync"], BridgeSyncSchema),
123 (&["bridge git reconcile"], BridgeGitReconcileSchema),
124 (&["bridge git push"], BridgePushSchema),
125 (&["bridge git pull"], BridgePullSchema),
126 (&["stash push", "stash pop", "stash apply", "stash drop", "stash clear"], StashMutationSchema),
127 (&["stash list"], StashListSchema),
128 (&["stash show"], StashShowSchema),
129 (&["revert"], RevertSchema),
130 (&["doctor"], DiagnoseSchema),
131 (&["doctor docs"], DoctorDocsSchema),
132 (&["doctor schemas"], DoctorSchemasSchema),
133 (&["actor spawn", "actor show"], ActorSingleSchema),
134 (&["actor list"], ActorListSchema),
135 (&["actor done"], ActorDoneSchema),
136 (&["actor explain"], ActorExplainSchema),
137 (&["agent serve"], AgentServeSchema),
138 (&["agent status"], AgentDaemonStatusSchema),
139 (&["agent stop"], AgentStopSchema),
140 (&["agent reserve", "agent heartbeat", "agent release"], AgentReservationEnvelopeSchema),
141 (&["agent capture"], CaptureSchema),
142 (&["agent ready"], ReadySchema),
143 (&["agent list"], AgentReservationListSchema),
144 (&["session start", "session end", "session show"], SessionEnvelopeSchema),
145 (&["session segment"], SessionSegmentEnvelopeSchema),
146 (&["session list"], SessionListSchema),
147 (&["git-overlay"], GitOverlayGuideSchema),
148 (&["watch"], WatchLineSchema),
149 (&["integration list", "integration doctor"], IntegrationStatusListSchema),
150 (&["try"], TrySchema),
151 (&["fsck"], FsckSchema),
152 (&["resolve"], ResolveSchema),
153 (&["maintenance index"], IndexSchema),
154 (&["error"], ErrorEnvelopeSchema),
155}
156
157pub fn schema_verbs() -> &'static [&'static str] {
160 SCHEMA_VERBS
161 .get_or_init(command_catalog::schema_verbs)
162 .as_slice()
163}
164
165pub fn documented_schema_verbs() -> &'static [&'static str] {
168 DOCUMENTED_SCHEMA_VERBS
169 .get_or_init(command_catalog::documented_schema_verbs)
170 .as_slice()
171}
172
173pub(crate) fn opaque_schema_verbs() -> &'static [&'static str] {
177 OPAQUE_SCHEMA_VERBS
178 .get_or_init(command_catalog::opaque_schema_verbs)
179 .as_slice()
180}
181
182pub fn schema_for_verb(verb: &str) -> Option<Value> {
184 let verb = resolve_schema_verb(verb)?;
185 if !schema_verbs().contains(&verb) {
186 return None;
187 }
188 let mut schema = schema_for_registered_verb(verb).or_else(|| {
189 opaque_schema_verbs()
190 .contains(&verb)
191 .then(|| serde_json::to_value(schema_for!(GenericJsonObjectSchema)).ok())
192 .flatten()
193 })?;
194 add_op_id_replay_fields_if_supported(verb, &mut schema);
195 add_json_discriminator_if_advertised(verb, &mut schema);
196 Some(schema)
197}
198
199fn resolve_schema_verb(verb: &str) -> Option<&'static str> {
200 let verb = verb.trim();
201 if let Some(registered) = schema_verbs()
202 .iter()
203 .copied()
204 .find(|registered| *registered == verb)
205 {
206 return Some(registered);
207 }
208
209 let matches = matching_schema_verbs(verb, schema_verbs());
210 if matches.len() == 1 {
211 matches.first().copied()
212 } else {
213 None
214 }
215}
216
217#[cfg(test)]
218const OP_ID_REPLAY_FIELD_NAMES: &[&str] = &[
219 "op_id",
220 "operation_record",
221 "idempotency_status",
222 "replayed",
223];
224
225fn add_op_id_replay_fields_if_supported(verb: &str, schema: &mut Value) {
226 if !schema_verb_supports_op_id(verb) {
227 return;
228 }
229
230 let Some(object) = schema.as_object_mut() else {
231 return;
232 };
233 let properties = object
234 .entry("properties".to_string())
235 .or_insert_with(|| serde_json::json!({}));
236 let Some(properties) = properties.as_object_mut() else {
237 return;
238 };
239
240 properties
241 .entry("op_id".to_string())
242 .or_insert_with(|| serde_json::json!({ "type": ["string", "null"] }));
243 properties
244 .entry("idempotency_status".to_string())
245 .or_insert_with(|| serde_json::json!({ "type": ["string", "null"] }));
246 properties
247 .entry("replayed".to_string())
248 .or_insert_with(|| serde_json::json!({ "type": ["boolean", "null"] }));
249 properties
250 .entry("operation_record".to_string())
251 .or_insert_with(|| {
252 serde_json::json!({
253 "anyOf": [
254 {
255 "type": "object",
256 "properties": {
257 "op_id": { "type": "string" },
258 "command": { "type": "string" },
259 "idempotency_status": { "type": "string" },
260 "replayed": { "type": "boolean" }
261 },
262 "required": [
263 "command",
264 "idempotency_status",
265 "op_id",
266 "replayed"
267 ]
268 },
269 { "type": "null" }
270 ]
271 })
272 });
273}
274
275fn add_json_discriminator_if_advertised(verb: &str, schema: &mut Value) {
276 let mut discriminators = command_catalog::command_json_discriminators_for_schema_verb(verb);
277 if schema.get("anyOf").is_some() {
278 for discriminator in command_catalog::command_json_discriminators()
279 .into_iter()
280 .filter(|discriminator| {
281 discriminator.display == verb && discriminator.schema_verb.as_deref() != Some(verb)
282 })
283 {
284 discriminators.push(discriminator);
285 }
286 }
287 discriminators.sort_by(|left, right| {
288 (&left.field, &left.value, &left.display).cmp(&(&right.field, &right.value, &right.display))
289 });
290 discriminators.dedup_by(|left, right| left.field == right.field && left.value == right.value);
291
292 if discriminators.is_empty() {
293 return;
294 };
295
296 if add_json_discriminators_to_union_branches(verb, schema, &discriminators) {
297 return;
298 }
299
300 let field = discriminators[0].field.as_str();
301 let values = discriminators
302 .iter()
303 .filter(|discriminator| discriminator.field == field)
304 .map(|discriminator| discriminator.value.as_str())
305 .collect::<Vec<_>>();
306 add_json_discriminator_to_schema_object(schema, field, &values);
307}
308
309fn add_json_discriminators_to_union_branches(
310 verb: &str,
311 schema: &mut Value,
312 discriminators: &[command_catalog::CommandJsonDiscriminator],
313) -> bool {
314 let Some(branches) = schema
315 .get_mut("anyOf")
316 .and_then(|value| value.as_array_mut())
317 else {
318 return false;
319 };
320
321 let mut injected = 0usize;
322 for branch in branches {
323 let Some(branch_ref) = branch
324 .get("$ref")
325 .and_then(|value| value.as_str())
326 .map(str::to_string)
327 else {
328 continue;
329 };
330 let Some(discriminator) = discriminator_for_union_branch(verb, &branch_ref, discriminators)
331 else {
332 continue;
333 };
334 let original_branch = branch.clone();
335 let mut discriminator_schema = serde_json::json!({ "type": "object" });
336 add_json_discriminator_to_schema_object(
337 &mut discriminator_schema,
338 &discriminator.field,
339 &[&discriminator.value],
340 );
341 *branch = serde_json::json!({
342 "allOf": [original_branch, discriminator_schema],
343 });
344 injected += 1;
345 }
346
347 injected > 0
348}
349
350fn discriminator_for_union_branch<'a>(
351 verb: &str,
352 branch_ref: &str,
353 discriminators: &'a [command_catalog::CommandJsonDiscriminator],
354) -> Option<&'a command_catalog::CommandJsonDiscriminator> {
355 if discriminators.len() == 1 {
356 return discriminators.first();
357 }
358
359 let def_name = schema_ref_name(branch_ref)?;
360 if verb == "inspect" {
361 let value = match def_name {
362 "ShowSchema" => "inspect_state",
363 "ThreadShowSchema" => "thread_show",
364 _ => return None,
365 };
366 return discriminators
367 .iter()
368 .find(|discriminator| discriminator.value == value);
369 }
370
371 None
372}
373
374fn schema_ref_name(reference: &str) -> Option<&str> {
375 reference
376 .strip_prefix("#/$defs/")
377 .or_else(|| reference.strip_prefix("#/definitions/"))
378}
379
380fn add_json_discriminator_to_schema_object(schema: &mut Value, field: &str, values: &[&str]) {
381 let enum_values = values
382 .iter()
383 .map(|value| Value::String((*value).to_string()))
384 .collect::<Vec<_>>();
385
386 let Some(object) = schema.as_object_mut() else {
387 return;
388 };
389 let properties = object
390 .entry("properties".to_string())
391 .or_insert_with(|| serde_json::json!({}));
392 let Some(properties) = properties.as_object_mut() else {
393 return;
394 };
395 properties.insert(
396 field.to_string(),
397 serde_json::json!({
398 "type": "string",
399 "enum": enum_values,
400 }),
401 );
402
403 let required = object
404 .entry("required".to_string())
405 .or_insert_with(|| serde_json::json!([]));
406 let Some(required) = required.as_array_mut() else {
407 return;
408 };
409 if !required
410 .iter()
411 .any(|required_field| required_field.as_str() == Some(field))
412 {
413 required.push(Value::String(field.to_string()));
414 }
415}
416
417fn schema_verb_supports_op_id(verb: &str) -> bool {
418 command_catalog::command_runtime_contract_for_schema_verb(verb)
419 .is_some_and(|contract| contract.supports_op_id)
420}
421
422pub fn cmd_schemas(cli: &Cli, verb_parts: &[String]) -> Result<()> {
429 let verb_parts = normalize_schema_verb_parts(verb_parts)?;
430 if verb_parts.is_empty() {
431 let out = serde_json::json!({
432 "output_kind": "schemas",
433 "status": "completed",
434 "schema_verbs": schema_verbs(),
435 "documented_schema_verbs": documented_schema_verbs(),
436 });
437 return render_schema_json(&out);
438 }
439
440 let verb = verb_parts.join(" ");
441 let schema = schema_for_verb(&verb)
442 .ok_or_else(|| anyhow!(schema_not_registered_advice(&verb, schema_verbs())))?;
443
444 let _json = should_output_json(cli, None);
446 render_schema_json(&schema)
447}
448
449fn render_schema_json(value: &serde_json::Value) -> Result<()> {
450 println!("{}", serde_json::to_string_pretty(value)?);
451 Ok(())
452}
453
454fn schema_not_registered_advice(verb: &str, known_verbs: &[&str]) -> RecoveryAdvice {
455 let matches = suggested_schema_verbs(verb, known_verbs);
456 let primary_command = matches
457 .first()
458 .map(|matched| format!("heddle schemas {matched}"))
459 .unwrap_or_else(|| "heddle schemas".to_string());
460 let hint = if matches.is_empty() {
461 "Run `heddle schemas` to list schema-backed verbs, or inspect the command catalog with `heddle help --output json`.".to_string()
462 } else {
463 format!(
464 "`{verb}` is not exact; available schema verb{}: {}.",
465 if matches.len() == 1 { "" } else { "s" },
466 matches
467 .iter()
468 .map(|matched| format!("`{matched}`"))
469 .collect::<Vec<_>>()
470 .join(", ")
471 )
472 };
473 let mut recovery_commands = Vec::new();
474 push_unique_command(&mut recovery_commands, primary_command.clone());
475 push_unique_command(&mut recovery_commands, "heddle schemas".to_string());
476 push_unique_command(
477 &mut recovery_commands,
478 "heddle help --output json".to_string(),
479 );
480 for matched in matches.iter().skip(1) {
481 push_unique_command(&mut recovery_commands, format!("heddle schemas {matched}"));
482 }
483
484 RecoveryAdvice::safety_refusal(
485 "schema_not_registered",
486 format!("No JSON schema is registered for `{verb}`"),
487 hint,
488 format!("`{verb}` is not in the runtime schema registry"),
489 "schema lookup does not change repository state; retrying the same unknown verb will fail until a schema is registered",
490 "no repository objects, refs, metadata, or worktree files were changed",
491 primary_command,
492 recovery_commands,
493 )
494}
495
496fn suggested_schema_verbs<'a>(verb: &str, known_verbs: &'a [&'a str]) -> Vec<&'a str> {
497 let exactish = matching_schema_verbs(verb, known_verbs);
498 if !exactish.is_empty() {
499 return exactish;
500 }
501
502 let normalized = verb.trim();
503 if normalized.is_empty() {
504 return Vec::new();
505 }
506 let bare = command_catalog::schema_verb_without_flags(normalized);
507 if bare.is_empty() {
508 return Vec::new();
509 }
510
511 known_verbs
512 .iter()
513 .copied()
514 .filter(|known| {
515 known.starts_with(normalized)
516 || command_catalog::schema_verb_without_flags(known).starts_with(&bare)
517 })
518 .take(5)
519 .collect()
520}
521
522fn push_unique_command(commands: &mut Vec<String>, command: String) {
523 if !commands.iter().any(|existing| existing == &command) {
524 commands.push(command);
525 }
526}
527
528fn matching_schema_verbs<'a>(verb: &str, known_verbs: &'a [&'a str]) -> Vec<&'a str> {
529 let normalized = verb.trim();
530 if normalized.is_empty() {
531 return Vec::new();
532 }
533 let bare = command_catalog::schema_verb_without_flags(normalized);
534 let prefix = format!("{bare} ");
535 known_verbs
536 .iter()
537 .copied()
538 .filter(|known| {
539 *known != normalized
540 && (*known == bare
541 || known.starts_with(&prefix)
542 || command_catalog::schema_verb_without_flags(known) == bare)
543 })
544 .collect()
545}
546
547fn normalize_schema_verb_parts(parts: &[String]) -> Result<Vec<String>> {
548 let mut normalized = Vec::new();
549 let mut iter = parts.iter();
550 while let Some(part) = iter.next() {
551 match part.as_str() {
552 "--no-color" | "-q" | "--quiet" | "-v" | "--verbose" => {}
553 "--output" | "--repo" => {
554 iter.next()
555 .ok_or_else(|| anyhow!("missing value for `{part}`"))?;
556 }
557 _ if part.starts_with("--output=") || part.starts_with("--repo=") => {}
558 _ => normalized.push(part.clone()),
559 }
560 }
561 Ok(normalized)
562}
563
564#[allow(dead_code)]
584#[derive(Debug, Serialize, JsonSchema)]
585#[serde(rename_all = "snake_case")]
586pub enum ThreadModeSchema {
587 Materialized,
588 Virtualized,
589 Solid,
590}
591
592#[allow(dead_code)]
593#[derive(Debug, Serialize, JsonSchema)]
594#[serde(rename_all = "snake_case")]
595pub enum ThreadStateSchema {
596 Draft,
597 Active,
598 Ready,
599 Blocked,
600 Merged,
601 Abandoned,
602 Promoted,
603}
604
605#[allow(dead_code)]
606#[derive(Debug, Serialize, JsonSchema)]
607#[serde(rename_all = "snake_case")]
608pub enum ThreadFreshnessSchema {
609 Current,
610 Stale,
611 Unknown,
612}
613
614#[allow(dead_code)]
615#[derive(Debug, Serialize, JsonSchema)]
616#[serde(rename_all = "snake_case")]
617pub enum ThreadImpactCategorySchema {
618 DependencyGraph,
619 BuildRuntimeConfig,
620 GeneratedOutputs,
621 RepoWideRefactor,
622 PublicApiSurface,
623}
624
625#[allow(dead_code)]
626#[derive(Debug, Serialize, JsonSchema)]
627#[serde(rename_all = "kebab-case")]
628pub enum CoordinationStatusSchema {
629 Clean,
630 Ahead,
631 Diverged,
632 Blocked,
633 MergeReady,
634}
635
636#[derive(Debug, Serialize, JsonSchema)]
637pub struct ActorInfoSchema {
638 pub provider: Option<String>,
639 pub model: Option<String>,
640}
641
642#[derive(Debug, Serialize, JsonSchema)]
643pub struct StateInfoSchema {
644 pub change_id: String,
645 pub content_hash: String,
646 pub intent: Option<String>,
647}
648
649#[derive(Debug, Serialize, JsonSchema)]
650pub struct GitCheckpointInfoSchema {
651 pub git_commit: String,
652 pub committed_at: String,
653}
654
655#[derive(Debug, Serialize, JsonSchema)]
656pub struct ChangesInfoSchema {
657 pub modified: Vec<String>,
658 pub added: Vec<String>,
659 pub deleted: Vec<String>,
660}
661
662#[derive(Debug, Serialize, JsonSchema)]
663pub struct GitIndexInfoSchema {
664 pub commit_mode: String,
665 pub has_staged_changes: bool,
666 pub staged_paths: Vec<String>,
667 pub unstaged_paths: Vec<String>,
668 pub untracked_paths: Vec<String>,
669 pub will_commit: Vec<String>,
670 pub preserved_after_commit: Vec<String>,
671}
672
673#[derive(Debug, Serialize, JsonSchema)]
674pub struct GenericJsonObjectSchema {
675 #[serde(flatten)]
676 pub fields: BTreeMap<String, Value>,
677}
678
679#[derive(Debug, Serialize, JsonSchema)]
680pub struct IntegrationStatusListSchema(pub Vec<IntegrationStatusSchema>);
681
682#[derive(Debug, Serialize, JsonSchema)]
683pub struct IntegrationStatusSchema {
684 pub harness: String,
685 pub scope: String,
686 pub method: String,
687 pub status: String,
688 pub healthy: bool,
689 pub paths: Vec<String>,
690 pub capabilities: Vec<String>,
691 pub capability_paths: Vec<String>,
692 pub path_mode: String,
693}
694
695#[derive(Debug, Serialize, JsonSchema)]
696pub struct IndexSchema {
697 pub output_kind: String,
698 pub present: bool,
699 pub path: String,
700 pub file_entries: usize,
701 pub directory_entries: usize,
702 pub untracked_directory_entries: usize,
703 pub snapshot_bytes: u64,
704 pub journal_bytes: u64,
705 pub journal_ops: usize,
706 pub journal_replay_ms: u128,
707 pub dump: Option<String>,
708}
709
710#[derive(Debug, Serialize, JsonSchema)]
711pub struct BlameSchema {
712 pub output_kind: Option<String>,
713 pub status: Option<String>,
714 pub file: String,
715 pub context: Vec<BlameContextSnippetSchema>,
716 pub lines: Vec<BlameLineSchema>,
717}
718
719#[derive(Debug, Serialize, JsonSchema)]
720pub struct BlameLineSchema {
721 pub line_number: usize,
722 pub content: String,
723 pub change_id: String,
724 pub principal: BlamePrincipalSchema,
725 pub agent: Option<BlameAgentSchema>,
726 pub timestamp: String,
727 pub origins: Option<Vec<BlameOriginSchema>>,
728}
729
730#[derive(Debug, Serialize, JsonSchema)]
731pub struct BlameOriginSchema {
732 pub change_id: String,
733 pub principal: BlamePrincipalSchema,
734 pub agent: Option<BlameAgentSchema>,
735 pub timestamp: String,
736}
737
738#[derive(Debug, Serialize, JsonSchema)]
739pub struct BlamePrincipalSchema {
740 pub name: String,
741 pub email: String,
742}
743
744#[derive(Debug, Serialize, JsonSchema)]
745pub struct BlameAgentSchema {
746 pub provider: String,
747 pub model: String,
748 #[serde(default, skip_serializing_if = "Option::is_none")]
749 pub session_id: Option<String>,
750 #[serde(default, skip_serializing_if = "Option::is_none")]
751 pub policy_id: Option<String>,
752}
753
754#[derive(Debug, Serialize, JsonSchema)]
755pub struct BlameContextSnippetSchema {
756 pub annotation_id: String,
757 pub kind: String,
758 pub content: String,
759 pub revision_count: usize,
760}
761
762#[derive(Debug, Serialize, JsonSchema)]
763pub struct FsckSchema {
764 pub valid: bool,
765 pub errors: Vec<FsckErrorSchema>,
766 pub warnings: Vec<String>,
767 pub objects_checked: usize,
768 pub bridge_checked: bool,
769}
770
771#[derive(Debug, Serialize, JsonSchema)]
772pub struct FsckErrorSchema {
773 pub kind: String,
774 pub message: String,
775 pub object: Option<String>,
776}
777
778#[derive(Debug, Serialize, JsonSchema)]
779pub struct ResolveSchema {
780 pub output_kind: String,
781 pub message: Option<String>,
782 pub resolved: Option<Vec<String>>,
783 pub remaining: Option<Vec<String>>,
784 pub conflicts: Option<Vec<String>>,
785 pub continued: Option<bool>,
786 pub continuation_status: Option<String>,
787 pub continuation_message: Option<String>,
788 pub next_action: Option<String>,
789 pub recommended_action: Option<String>,
790}
791
792#[allow(dead_code, clippy::large_enum_variant)]
793#[derive(Debug, Serialize, JsonSchema)]
794#[serde(untagged)]
795pub enum InspectSchema {
796 #[allow(dead_code)]
797 State(ShowSchema),
798 #[allow(dead_code)]
799 Thread(ThreadShowSchema),
800}
801
802#[derive(Debug, Serialize, JsonSchema)]
803pub struct RetroSchema {
804 pub since: Option<String>,
805 pub until: Option<String>,
806 pub duration_secs: Option<i64>,
807 pub states_captured: Vec<RetroStateEntrySchema>,
808 pub agents_active: Vec<RetroAgentEntrySchema>,
809 pub markers_created: Vec<RetroMarkerEntrySchema>,
810 pub context_annotations: Vec<RetroContextAnnotationEntrySchema>,
811 pub verify_signals: Vec<RetroVerifySignalSchema>,
812 pub merges: Vec<RetroOperationEntrySchema>,
813 pub undos: Vec<RetroOperationEntrySchema>,
814}
815
816#[derive(Debug, Serialize, JsonSchema)]
817pub struct RetroStateEntrySchema {
818 pub change_id: String,
819 pub intent: Option<String>,
820 pub confidence: Option<f32>,
821 pub agent: Option<String>,
822 pub principal: String,
823 pub timestamp: String,
824}
825
826#[derive(Debug, Serialize, JsonSchema)]
827pub struct RetroAgentEntrySchema {
828 pub session_id: String,
829 pub provider: Option<String>,
830 pub model: Option<String>,
831 pub status: String,
832 pub started_at: String,
833 pub completed_at: Option<String>,
834 pub tokens: RetroAgentTokensSchema,
835}
836
837#[derive(Debug, Serialize, JsonSchema)]
838pub struct RetroAgentTokensSchema {
839 pub input: Option<u64>,
840 pub output: Option<u64>,
841 pub reasoning: Option<u64>,
842 pub tool_calls: Option<u32>,
843}
844
845#[derive(Debug, Serialize, JsonSchema)]
846pub struct RetroMarkerEntrySchema {
847 pub name: String,
848 pub state: String,
849 pub timestamp: String,
850}
851
852#[derive(Debug, Serialize, JsonSchema)]
853pub struct RetroContextAnnotationEntrySchema {
854 pub path: String,
855 pub scope: String,
856 pub kind: String,
857 pub content_excerpt: String,
858 pub attribution: String,
859 pub created_at: String,
860}
861
862#[derive(Debug, Serialize, JsonSchema)]
863pub struct RetroVerifySignalSchema {
864 pub kind: String,
865 pub label: String,
866 pub timestamp: String,
867}
868
869#[derive(Debug, Serialize, JsonSchema)]
870pub struct RetroOperationEntrySchema {
871 pub description: String,
872 pub timestamp: String,
873}
874
875#[derive(Debug, Serialize, JsonSchema)]
876pub struct DiscussionSchema {
877 pub id: String,
878 pub file: String,
879 pub symbol: String,
880 pub opened_against_state: String,
881 pub opened_at_secs: i64,
882 pub visibility: String,
883 pub body_changed_since_open: bool,
884 pub orphaned: bool,
885 pub resolution: DiscussionResolutionSchema,
886 pub turns: Vec<DiscussionTurnSchema>,
887 pub resolved_annotation_id: Option<String>,
888}
889
890#[derive(Debug, Serialize, JsonSchema)]
898pub struct DiscussionEnvelopeSchema {
899 pub output_kind: String,
900 #[serde(flatten)]
901 pub discussion: DiscussionSchema,
902}
903
904#[derive(Debug, Serialize, JsonSchema)]
905pub struct DiscussionResolutionSchema {
906 pub kind: String,
907 pub annotation_id: Option<String>,
908 pub state_id: Option<String>,
909 pub reason: Option<String>,
910}
911
912#[derive(Debug, Serialize, JsonSchema)]
913pub struct DiscussionTurnSchema {
914 pub author_name: String,
915 pub author_email: String,
916 pub body: String,
917 pub posted_at_secs: i64,
918}
919
920#[derive(Debug, Serialize, JsonSchema)]
921pub struct DiscussionListSchema {
922 pub output_kind: String,
923 pub discussions: Vec<DiscussionSchema>,
924}
925
926#[derive(Debug, Serialize, JsonSchema)]
927pub struct QuerySchema {
928 pub output_kind: String,
929 pub hits: Vec<QueryHitSchema>,
930}
931
932#[derive(Debug, Serialize, JsonSchema)]
933pub struct QueryHitSchema {
934 pub seq: u64,
935 pub timestamp_secs: i64,
936 pub verb: String,
937 pub actor_email: String,
938 pub operation_id: Option<String>,
939 pub thread: Option<String>,
940 pub symbols: Vec<String>,
941 pub signal_kinds: Vec<String>,
942 pub change_id: Option<String>,
943}
944
945#[derive(Debug, Serialize, JsonSchema)]
946pub struct OperationRecordSchema {
947 pub op_id: String,
948 pub command: String,
949 pub idempotency_status: String,
950 pub replayed: bool,
951}
952
953#[derive(Debug, Serialize, JsonSchema)]
956pub struct InitSchema {
957 pub output_kind: String,
958 pub status: String,
959 pub action: String,
960 pub path: String,
961 pub repository_mode: String,
962 pub git_detected: bool,
963 pub heddle_initialized: bool,
964 pub installed_heddleignore: bool,
965 pub principal_configured: bool,
966 pub principal_status: String,
967 pub principal_source: Option<String>,
968 pub principal: Option<InitPrincipalSchema>,
969 pub principal_recommended_action: Option<String>,
970 pub side_effects: Vec<String>,
971 pub message: String,
972 pub next_action: Option<String>,
973 pub recommended_action: Option<String>,
974}
975
976#[derive(Debug, Serialize, JsonSchema)]
977pub struct InitPrincipalSchema {
978 pub name: String,
979 pub email: String,
980}
981
982#[derive(Debug, Serialize, JsonSchema)]
983pub struct CaptureSchema {
984 pub output_kind: Option<String>,
985 pub status: String,
986 pub action: String,
987 pub change_id: String,
988 pub content_hash: String,
989 pub intent: Option<String>,
990 pub confidence: Option<f32>,
991 pub principal: CommitPrincipalSchema,
992 pub agent: Option<CommitAgentSchema>,
993 pub promotion_suggested: bool,
994 pub heavy_impact_paths: Vec<String>,
995 pub signed: bool,
996 pub message: String,
997 pub next_action: Option<String>,
998 pub next_action_template: Option<ActionTemplateSchema>,
999 pub recommended_action: Option<String>,
1000 pub recommended_action_template: Option<ActionTemplateSchema>,
1001}
1002
1003#[derive(Debug, Serialize, JsonSchema)]
1004pub struct CommitSchema {
1005 pub output_kind: Option<String>,
1006 pub status: String,
1007 pub action: String,
1008 pub change_id: String,
1009 pub git_commit: Option<String>,
1010 pub git_previous_commit: Option<String>,
1011 pub summary: String,
1012 pub confidence: Option<f32>,
1013 pub git_index: Option<GitIndexInfoSchema>,
1014 pub included_pending_capture: Option<String>,
1015 pub principal: CommitPrincipalSchema,
1016 pub agent: Option<CommitAgentSchema>,
1017 pub next_action: Option<String>,
1018 pub next_action_template: Option<ActionTemplateSchema>,
1019 pub recommended_action: Option<String>,
1020 pub recommended_action_template: Option<ActionTemplateSchema>,
1021 pub op_id: Option<String>,
1022 pub operation_record: Option<OperationRecordSchema>,
1023 pub idempotency_status: Option<String>,
1024 pub replayed: Option<bool>,
1025}
1026
1027#[derive(Debug, Serialize, JsonSchema)]
1028pub struct CommitPrincipalSchema {
1029 pub name: String,
1030 pub email: String,
1031}
1032
1033#[derive(Debug, Serialize, JsonSchema)]
1034pub struct CommitAgentSchema {
1035 pub provider: String,
1036 pub model: String,
1037 #[serde(default, skip_serializing_if = "Option::is_none")]
1038 pub session_id: Option<String>,
1039 #[serde(default, skip_serializing_if = "Option::is_none")]
1040 pub segment_id: Option<String>,
1041 #[serde(default, skip_serializing_if = "Option::is_none")]
1042 pub policy_id: Option<String>,
1043}
1044
1045#[derive(Debug, Serialize, JsonSchema)]
1046pub struct CheckpointSchema {
1047 pub output_kind: Option<String>,
1048 pub status: String,
1049 pub action: String,
1050 pub change_id: String,
1051 pub git_commit: String,
1052 pub summary: String,
1053 pub capability: String,
1054 pub storage_model: String,
1055 pub committed_at: String,
1056 pub next_action: Option<String>,
1057 pub next_action_template: Option<ActionTemplateSchema>,
1058 pub recommended_action: Option<String>,
1059 pub recommended_action_template: Option<ActionTemplateSchema>,
1060}
1061
1062#[derive(Debug, Serialize, JsonSchema)]
1063pub struct OperatorCommandSchema {
1064 pub output_kind: Option<String>,
1065 pub status: String,
1066 pub action: String,
1067 pub message: String,
1068 pub blockers: Vec<String>,
1069 pub warnings: Vec<String>,
1070 pub next_action: Option<String>,
1071 pub next_action_template: Option<ActionTemplateSchema>,
1072 pub recommended_action: Option<String>,
1073 pub recommended_action_template: Option<ActionTemplateSchema>,
1074}
1075
1076#[derive(Debug, Serialize, JsonSchema)]
1077pub struct UndoSchema {
1078 pub output_kind: Option<String>,
1079 pub status: Option<String>,
1080 pub action: String,
1081 pub message: String,
1082 pub batches: Vec<Value>,
1083 pub next_action: Option<String>,
1084 pub next_action_template: Option<ActionTemplateSchema>,
1085 pub recommended_action: Option<String>,
1086 pub recommended_action_template: Option<ActionTemplateSchema>,
1087 #[serde(default, skip_serializing_if = "Option::is_none")]
1090 pub recovery_state: Option<String>,
1091 #[serde(default, skip_serializing_if = "Option::is_none")]
1092 pub recovery_marker: Option<String>,
1093}
1094
1095#[derive(Debug, Serialize, JsonSchema)]
1100pub struct UndoListSchema {
1101 pub output_kind: Option<String>,
1102 pub batches: Vec<Value>,
1103}
1104
1105#[derive(Debug, Serialize, JsonSchema)]
1106pub struct CleanSchema {
1107 pub output_kind: String,
1108 pub removed: Vec<String>,
1109 pub dry_run: bool,
1110}
1111
1112#[derive(Debug, Serialize, JsonSchema)]
1113pub struct DiffSchema {
1114 pub output_kind: Option<String>,
1115 pub status: Option<String>,
1116 pub from_state: Option<String>,
1117 pub to_state: Option<String>,
1118 pub changed_path_count: usize,
1119 pub stats: DiffStatsSchema,
1120 pub changes: DiffChangesSchema,
1126 pub semantic_changes: Option<Vec<Value>>,
1127 pub context: Option<Vec<Value>>,
1128 pub broader_guidance: Option<Vec<Value>>,
1129 pub patch: Option<String>,
1133}
1134
1135#[derive(Debug, Serialize, JsonSchema)]
1141#[serde(untagged)]
1142#[allow(dead_code)]
1143pub enum DiffChangesSchema {
1144 Grouped(DiffChangesGroupedSchema),
1150 Flat(Vec<Value>),
1152}
1153
1154#[derive(Debug, Serialize, JsonSchema)]
1155pub struct DiffChangesGroupedSchema {
1156 pub modified: Vec<Value>,
1157 pub added: Vec<Value>,
1158 pub deleted: Vec<Value>,
1159}
1160
1161#[derive(Debug, Serialize, JsonSchema)]
1162pub struct DiffStatsSchema {
1163 pub files_changed: usize,
1164 pub additions: usize,
1165 pub modifications: usize,
1166 pub deletions: usize,
1167 pub renames: usize,
1168}
1169
1170#[derive(Debug, Serialize, JsonSchema)]
1171pub struct SwitchCheckoutSchema {
1172 pub output_kind: Option<String>,
1173 pub status: Option<String>,
1174 pub action: Option<String>,
1175 pub name: Option<String>,
1176 pub message: String,
1177 pub thread: Option<ThreadSummarySchema>,
1178 pub path: Option<String>,
1179 pub execution_path: Option<String>,
1180 pub target: Option<String>,
1181 pub intent: Option<String>,
1182 pub next_action: Option<String>,
1183 pub next_action_template: Option<ActionTemplateSchema>,
1184 pub recommended_action: Option<String>,
1185 pub recommended_action_template: Option<ActionTemplateSchema>,
1186}
1187
1188#[derive(Debug, Serialize, JsonSchema)]
1189pub struct MergePreviewSchema {
1190 pub output_kind: Option<String>,
1191 pub status: Option<String>,
1192 pub action: Option<String>,
1193 pub message: Option<String>,
1194 pub would_merge: bool,
1195 pub applied: bool,
1196 pub blockers: Option<Vec<String>>,
1197 pub warnings: Option<Vec<String>>,
1198 pub next_action: Option<String>,
1199 pub next_action_template: Option<ActionTemplateSchema>,
1200 pub recommended_action: Option<String>,
1201 pub recommended_action_template: Option<ActionTemplateSchema>,
1202 pub fast_forward: Option<bool>,
1203 pub preview_only: Option<bool>,
1204 pub merge_state: Option<String>,
1205 pub conflicts: Option<Vec<String>>,
1206 pub preview_summary: Option<Vec<String>>,
1207 pub thread_state: Option<String>,
1208 pub freshness: Option<String>,
1209 pub changed_paths: Option<Vec<String>>,
1210 pub changed_path_count: Option<usize>,
1211 pub impact_categories: Option<Vec<String>>,
1212 pub promotion_suggested: Option<bool>,
1213 pub heavy_impact_paths: Option<Vec<String>>,
1214 pub merge_relation: Option<String>,
1215 pub conflict_count: Option<usize>,
1216 pub thread_health: Option<String>,
1217 pub diff: Option<Value>,
1218}
1219
1220#[derive(Debug, Serialize, JsonSchema)]
1221pub struct ReadySchema {
1222 pub output_kind: Option<String>,
1223 pub status: String,
1224 pub action: String,
1225 pub message: String,
1226 pub blockers: Vec<String>,
1227 pub warnings: Vec<String>,
1228 pub next_action: Option<String>,
1229 pub next_action_template: Option<ActionTemplateSchema>,
1230 pub recommended_action: Option<String>,
1231 pub recommended_action_template: Option<ActionTemplateSchema>,
1232 pub captured: bool,
1233 pub captured_state: Option<String>,
1234 pub thread_state: Option<String>,
1235 pub readiness: ReadyReadinessSchema,
1236 pub report: Value,
1237 #[serde(rename = "verification")]
1238 pub verification: RepositoryVerificationStateSchema,
1239}
1240
1241#[derive(Debug, Serialize, JsonSchema)]
1242pub struct ReadyReadinessSchema {
1243 pub status: String,
1244 pub captured: bool,
1245 pub captured_state: Option<String>,
1246 pub checks: ReadyChecksSchema,
1247 pub integration: String,
1248 pub freshness: String,
1249 pub merge_type: String,
1250 pub changed_path_count: usize,
1251 pub changed_paths: Vec<String>,
1252 pub conflict_count: usize,
1253 pub conflicts: Vec<String>,
1254 pub impact: String,
1255 pub impact_categories: Vec<String>,
1256 pub blockers: Vec<String>,
1257}
1258
1259#[derive(Debug, Serialize, JsonSchema)]
1260pub struct ReadyChecksSchema {
1261 pub status: String,
1262 pub reason: String,
1263}
1264
1265#[derive(Debug, Serialize, JsonSchema)]
1266pub struct SyncSchema {
1267 #[serde(flatten)]
1268 pub operator: OperatorCommandSchema,
1269 pub thread: Option<String>,
1270 pub current_state: Option<String>,
1271 pub chosen_path: Option<String>,
1272}
1273
1274#[derive(Debug, Serialize, JsonSchema)]
1275pub struct LandSchema {
1276 pub output_kind: Option<String>,
1277 pub status: String,
1278 pub action: String,
1279 pub message: String,
1280 pub blockers: Option<Vec<String>>,
1281 pub warnings: Option<Vec<String>>,
1282 pub next_action: Option<String>,
1283 pub next_action_template: Option<ActionTemplateSchema>,
1284 pub recommended_action: Option<String>,
1285 pub recommended_action_template: Option<ActionTemplateSchema>,
1286 pub thread: String,
1287 pub captured: bool,
1288 pub checkpointed: bool,
1289 pub git_commit: Option<String>,
1290 pub synced: bool,
1291 pub integrated: bool,
1292 pub pushed: bool,
1293 pub pushed_remote: Option<String>,
1294 pub performed_steps: Vec<String>,
1295 pub skipped_steps: Vec<String>,
1296 pub merge_state: Option<String>,
1297 pub chosen_path: String,
1298}
1299
1300#[derive(Debug, Serialize, JsonSchema)]
1301pub struct ThreadStartSchema {
1302 pub output_kind: Option<String>,
1303 pub status: Option<String>,
1304 pub action: Option<String>,
1305 pub name: String,
1306 pub message: String,
1307 pub next_action: Option<String>,
1308 pub next_action_template: Option<ActionTemplateSchema>,
1309 pub recommended_action: Option<String>,
1310 pub recommended_action_template: Option<ActionTemplateSchema>,
1311 pub thread: Option<ThreadSummarySchema>,
1312 pub path: Option<String>,
1313 pub execution_path: Option<String>,
1314 pub fskit_readiness: Option<FsKitReadinessSchema>,
1315}
1316
1317#[derive(Debug, Serialize, JsonSchema)]
1318pub struct FsKitReadinessSchema {
1319 pub state: String,
1320 pub backend: String,
1321 pub action: String,
1322 pub settings_url: Option<String>,
1323}
1324
1325#[derive(Debug, Serialize, JsonSchema)]
1326pub struct ThreadCurrentSchema {
1327 pub thread: String,
1328}
1329
1330#[derive(Debug, Serialize, JsonSchema)]
1331#[serde(transparent)]
1332pub struct ThreadCapturesSchema(pub Vec<ThreadCaptureEntrySchema>);
1333
1334#[derive(Debug, Serialize, JsonSchema)]
1335pub struct ThreadCaptureEntrySchema {
1336 pub change_id: String,
1337 pub created_at: String,
1338 pub intent: Option<String>,
1339 pub confidence: Option<f32>,
1340 pub agent: Option<String>,
1341 pub message: String,
1342 pub summary: Option<ThreadCaptureSummarySchema>,
1343}
1344
1345#[derive(Debug, Serialize, JsonSchema)]
1346pub struct ThreadCaptureSummarySchema {
1347 pub added: usize,
1348 pub modified: usize,
1349 pub deleted: usize,
1350 pub total: usize,
1351}
1352
1353#[derive(Debug, Serialize, JsonSchema)]
1354pub struct ThreadCommandSchema {
1355 pub output_kind: String,
1356 pub status: String,
1357 pub action: String,
1358 pub name: String,
1359 pub message: String,
1360 pub next_action: Option<String>,
1361 pub next_action_template: Option<ActionTemplateSchema>,
1362 pub recommended_action: Option<String>,
1363 pub recommended_action_template: Option<ActionTemplateSchema>,
1364 pub thread: Option<ThreadSummarySchema>,
1365 pub path: Option<String>,
1366 pub execution_path: Option<String>,
1367}
1368
1369#[derive(Debug, Serialize, JsonSchema)]
1370pub struct ThreadMoveSchema {
1371 pub from_thread: String,
1372 pub to_thread: String,
1373 pub moved_paths: Vec<String>,
1374 pub source_change_id: Option<String>,
1375 pub target_change_id: String,
1376 pub message: String,
1377}
1378
1379#[derive(Debug, Serialize, JsonSchema)]
1380pub struct ThreadAbsorbSchema {
1381 pub thread: String,
1382 pub into: String,
1383 pub preview_only: bool,
1384 pub conflicts: Vec<String>,
1385 pub merge_state: Option<String>,
1386 pub message: String,
1387}
1388
1389#[derive(Debug, Serialize, JsonSchema)]
1390pub struct ThreadResolveSchema {
1391 #[serde(flatten)]
1392 pub operator: OperatorCommandSchema,
1393 pub thread: String,
1394}
1395
1396#[derive(Debug, Serialize, JsonSchema)]
1397pub struct ThreadApprovalSchema {
1398 pub id: String,
1399 pub repo_path: String,
1400 pub source_thread: String,
1401 pub target_thread: String,
1402 pub source_state: String,
1403 pub approver_user_id: String,
1404 pub note: String,
1405 pub approved_at: u64,
1406 pub expires_at: u64,
1407}
1408
1409#[derive(Debug, Serialize, JsonSchema)]
1410#[serde(transparent)]
1411pub struct ThreadApprovalListSchema(pub Vec<ThreadApprovalSchema>);
1412
1413#[derive(Debug, Serialize, JsonSchema)]
1414pub struct ThreadRevokeApprovalSchema {
1415 pub output_kind: String,
1416 pub deleted: bool,
1417 pub id: String,
1418}
1419
1420#[derive(Debug, Serialize, JsonSchema)]
1421pub struct ThreadMergeEligibilitySchema {
1422 pub allowed: bool,
1423 pub unmet: Vec<ThreadMergeRequirementSchema>,
1424 pub valid_approvals: Vec<ThreadApprovalSchema>,
1425}
1426
1427#[derive(Debug, Serialize, JsonSchema)]
1428pub struct ThreadMergeRequirementSchema {
1429 pub policy_id: String,
1430 pub kind: String,
1431 pub group_id: String,
1432 pub reason: String,
1433 pub needed: u32,
1434 pub have: u32,
1435}
1436
1437#[derive(Debug, Serialize, JsonSchema)]
1438pub struct ThreadCleanupSchema {
1439 #[serde(flatten)]
1440 pub operator: OperatorCommandSchema,
1441 pub dry_run: bool,
1442 pub merged: Vec<ThreadDroppedSchema>,
1443 pub auto: Vec<ThreadDroppedSchema>,
1444 pub reclaimed_bytes: u64,
1445 pub would_reclaim_bytes: u64,
1446 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1447 pub skipped: Vec<ThreadCleanupSkippedSchema>,
1448}
1449
1450#[derive(Debug, Serialize, JsonSchema)]
1451pub struct ThreadDroppedSchema {
1452 pub thread: String,
1453 pub id: String,
1454 pub reason: String,
1455 pub age_seconds: i64,
1456 pub bytes: u64,
1457 pub execution_path: Option<String>,
1458}
1459
1460#[derive(Debug, Serialize, JsonSchema)]
1461pub struct ThreadCleanupSkippedSchema {
1462 pub thread: String,
1463 pub id: String,
1464 pub reason: String,
1465 pub note: String,
1466}
1467
1468#[derive(Debug, Serialize, JsonSchema)]
1469pub struct ThreadMarkerListSchema {
1470 pub output_kind: String,
1471 pub markers: Vec<ThreadMarkerEntrySchema>,
1472}
1473
1474#[derive(Debug, Serialize, JsonSchema)]
1475pub struct ThreadMarkerEntrySchema {
1476 pub name: String,
1477 pub change_id: String,
1478}
1479
1480#[derive(Debug, Serialize, JsonSchema)]
1481pub struct ThreadMarkerOpSchema {
1482 pub output_kind: String,
1483 pub name: Option<String>,
1484 pub change_id: Option<String>,
1485 pub deleted: Option<Vec<ThreadMarkerEntrySchema>>,
1486 pub count: Option<usize>,
1487 pub message: String,
1488}
1489
1490#[derive(Debug, Serialize, JsonSchema)]
1491pub struct ThreadShowSchema {
1492 pub output_kind: Option<String>,
1493 pub repository_label: String,
1494 pub repository_context: Option<RepositoryContextInfoSchema>,
1495 #[serde(flatten)]
1496 pub summary: ThreadSummarySchema,
1497 pub next_action: Option<String>,
1498 pub next_action_template: Option<ActionTemplateSchema>,
1499 pub recommended_action: Option<String>,
1500 pub recommended_action_template: Option<ActionTemplateSchema>,
1501 #[serde(rename = "verification")]
1502 pub trust: RepositoryVerificationStateSchema,
1503 pub recovery_commands: Vec<String>,
1504}
1505
1506#[derive(Debug, Serialize, JsonSchema)]
1507pub struct ThreadSummarySchema {
1508 pub name: String,
1509 pub operation: OpaqueObject,
1510 pub remote_tracking: OpaqueObject,
1511 pub base_state: Option<String>,
1512 pub base_root: Option<String>,
1513 pub current_state: Option<String>,
1514 pub path: Option<String>,
1515 pub execution_path: Option<String>,
1516 #[serde(default, skip_serializing_if = "Option::is_none")]
1517 pub session_id: Option<String>,
1518 #[serde(default, skip_serializing_if = "Option::is_none")]
1519 pub heddle_session_id: Option<String>,
1520 pub actor: Option<ActorInfoSchema>,
1521 pub harness: Option<String>,
1522 pub thinking_level: Option<String>,
1523 #[serde(default, skip_serializing_if = "Option::is_none")]
1524 pub native_actor_key: Option<String>,
1525 #[serde(default, skip_serializing_if = "Option::is_none")]
1526 pub native_parent_actor_key: Option<String>,
1527 #[serde(default, skip_serializing_if = "Option::is_none")]
1528 pub probe_source: Option<String>,
1529 #[serde(default, skip_serializing_if = "Option::is_none")]
1530 pub probe_confidence: Option<f32>,
1531 pub usage_summary: OpaqueObject,
1532 pub last_progress_at: Option<String>,
1533 pub last_activity_at: Option<String>,
1534 pub report_flush_state: Option<String>,
1535 pub attach_reason: Option<String>,
1536 pub thread_mode: Option<ThreadModeSchema>,
1537 pub thread_state: Option<ThreadStateSchema>,
1538 pub freshness: Option<ThreadFreshnessSchema>,
1539 pub visibility: String,
1540 pub target_thread: Option<String>,
1541 pub parent_thread: Option<String>,
1542 pub child_threads: Vec<String>,
1543 pub sibling_threads: Vec<String>,
1544 pub stack_depth: usize,
1545 pub stale_from_parent: bool,
1546 pub task: Option<String>,
1547 pub changed_paths: Vec<String>,
1548 pub promotion_suggested: bool,
1549 pub impact_categories: Vec<ThreadImpactCategorySchema>,
1550 pub heavy_impact_paths: Vec<String>,
1551 pub verification_summary: Value,
1552 pub confidence_summary: Value,
1553 pub integration_policy_result: Value,
1554 pub coordination_status: CoordinationStatusSchema,
1555 pub is_current: bool,
1556 pub is_isolated: bool,
1557 pub thread_health: String,
1558 pub blockers: Vec<String>,
1559 pub recommended_action: Option<String>,
1563 pub recommended_action_template: Option<ActionTemplateSchema>,
1564 pub git_branch_tip: Option<String>,
1565 pub history_imported: bool,
1566 pub auto: bool,
1567 pub shared_target_dir: Option<String>,
1568}
1569
1570#[derive(Debug, Serialize, JsonSchema)]
1571pub struct CloneSchema {
1572 pub output_kind: Option<String>,
1573 pub action: Option<String>,
1574 pub status: Option<String>,
1575 pub success: Option<bool>,
1576 pub cloned: Option<bool>,
1577 pub transport: Option<String>,
1578 pub remote: Option<String>,
1579 pub local: Option<String>,
1580 pub branch: Option<String>,
1581 pub repository_capability: Option<String>,
1582 pub commits_imported: Option<u64>,
1583 pub states_created: Option<u64>,
1584 pub objects: Option<usize>,
1585 pub state: Option<String>,
1586}
1587
1588#[derive(Debug, Serialize, JsonSchema)]
1589pub struct AdoptSchema {
1590 pub output_kind: Option<String>,
1591 pub status: Option<String>,
1592 pub action: Option<String>,
1593 pub adopted: bool,
1594 pub initialized: bool,
1595 pub path: String,
1596 pub refs: Vec<String>,
1597 pub commits_imported: usize,
1598 pub states_created: usize,
1599 pub branches_synced: usize,
1600 pub tags_synced: usize,
1601 pub skipped_non_commit_refs: usize,
1602 pub already_in_sync: bool,
1603 pub recommended_action: Option<String>,
1604 pub recommended_action_template: Option<ActionTemplateSchema>,
1605 #[serde(rename = "verification")]
1606 pub trust: RepositoryVerificationStateSchema,
1607}
1608
1609#[derive(Debug, Serialize, JsonSchema)]
1610pub struct RemoteListSchema {
1611 pub output_kind: Option<String>,
1612 pub remotes: Vec<RemoteInfoSchema>,
1613}
1614
1615#[derive(Debug, Serialize, JsonSchema)]
1616pub struct RemoteInfoSchema {
1617 pub output_kind: Option<String>,
1618 pub name: String,
1619 pub url: String,
1620 pub source: String,
1621 pub is_default: bool,
1622}
1623
1624#[derive(Debug, Serialize, JsonSchema)]
1625pub struct RemoteMutationSchema {
1626 pub output_kind: Option<String>,
1627 pub status: String,
1628 pub action: String,
1629 pub name: String,
1630 pub url: Option<String>,
1631 pub default: Option<String>,
1632 pub message: String,
1633}
1634
1635#[derive(Debug, Serialize, JsonSchema)]
1636pub struct ActorSingleSchema {
1637 pub output_kind: String,
1638 pub actor: ActorEntrySchema,
1639 #[serde(rename = "verification")]
1640 pub trust: RepositoryVerificationStateSchema,
1641}
1642
1643#[derive(Debug, Serialize, JsonSchema)]
1644pub struct ActorListSchema {
1645 pub output_kind: String,
1646 pub actors: Vec<ActorEntrySchema>,
1647 pub active_only: bool,
1648 #[serde(rename = "verification")]
1649 pub trust: RepositoryVerificationStateSchema,
1650}
1651
1652#[derive(Debug, Serialize, JsonSchema)]
1653pub struct ActorDoneSchema {
1654 pub output_kind: String,
1655 pub session_id: String,
1656 pub status: String,
1657 pub thread: String,
1658 #[serde(default, skip_serializing_if = "Option::is_none")]
1659 pub coordination_status: Option<String>,
1660 #[serde(default, skip_serializing_if = "Option::is_none")]
1661 pub recommended_action: Option<String>,
1662 #[serde(default, skip_serializing_if = "Option::is_none")]
1663 pub recommended_action_template: Option<ActionTemplateSchema>,
1664 #[serde(rename = "verification")]
1665 pub trust: RepositoryVerificationStateSchema,
1666}
1667
1668#[derive(Debug, Serialize, JsonSchema)]
1669pub struct ActorExplainSchema {
1670 pub output_kind: String,
1671 #[serde(default, skip_serializing_if = "Option::is_none")]
1672 pub attached: Option<bool>,
1673 #[serde(default, skip_serializing_if = "Option::is_none")]
1674 pub active_actor: Option<Value>,
1675 #[serde(default, skip_serializing_if = "Option::is_none")]
1676 pub reason: Option<String>,
1677 #[serde(default, skip_serializing_if = "Option::is_none")]
1678 pub repository: Option<String>,
1679 #[serde(default, skip_serializing_if = "Option::is_none")]
1680 pub detected: Option<Value>,
1681 #[serde(default, skip_serializing_if = "Option::is_none")]
1682 pub environment: Option<Value>,
1683 #[serde(default, skip_serializing_if = "Option::is_none")]
1684 pub recommended_action: Option<String>,
1685 #[serde(default, skip_serializing_if = "Option::is_none")]
1686 pub recommended_action_template: Option<ActionTemplateSchema>,
1687 #[serde(default, skip_serializing_if = "Option::is_none")]
1688 pub session_id: Option<String>,
1689 #[serde(default, skip_serializing_if = "Option::is_none")]
1690 pub thread: Option<String>,
1691 #[serde(default, skip_serializing_if = "Option::is_none")]
1692 pub heddle_session_id: Option<String>,
1693 #[serde(default, skip_serializing_if = "Option::is_none")]
1694 pub client_instance_id: Option<String>,
1695 #[serde(default, skip_serializing_if = "Option::is_none")]
1696 pub native_actor_key: Option<String>,
1697 #[serde(default, skip_serializing_if = "Option::is_none")]
1698 pub native_parent_actor_key: Option<String>,
1699 #[serde(default, skip_serializing_if = "Option::is_none")]
1700 pub native_instance_key: Option<String>,
1701 #[serde(default, skip_serializing_if = "Option::is_none")]
1702 pub probe_source: Option<String>,
1703 #[serde(default, skip_serializing_if = "Option::is_none")]
1704 pub probe_confidence: Option<f32>,
1705 #[serde(default, skip_serializing_if = "Option::is_none")]
1706 pub attach_reason: Option<String>,
1707 #[serde(default, skip_serializing_if = "Option::is_none")]
1708 pub attach_precedence: Option<Vec<String>>,
1709 #[serde(default, skip_serializing_if = "Option::is_none")]
1710 pub winning_rule: Option<String>,
1711 #[serde(rename = "verification")]
1712 pub trust: RepositoryVerificationStateSchema,
1713}
1714
1715#[derive(Debug, Serialize, JsonSchema)]
1716pub struct ActorEntrySchema {
1717 pub session_id: String,
1718 #[serde(default, skip_serializing_if = "Option::is_none")]
1719 pub client_instance_id: Option<String>,
1720 #[serde(default, skip_serializing_if = "Option::is_none")]
1721 pub native_actor_key: Option<String>,
1722 #[serde(default, skip_serializing_if = "Option::is_none")]
1723 pub native_parent_actor_key: Option<String>,
1724 #[serde(default, skip_serializing_if = "Option::is_none")]
1725 pub native_instance_key: Option<String>,
1726 #[serde(default, skip_serializing_if = "Option::is_none")]
1727 pub heddle_session_id: Option<String>,
1728 pub thread: String,
1729 #[serde(default, skip_serializing_if = "Option::is_none")]
1730 pub thread_id: Option<String>,
1731 pub base_state: String,
1732 #[serde(default, skip_serializing_if = "Option::is_none")]
1733 pub path: Option<String>,
1734 #[serde(default, skip_serializing_if = "Option::is_none")]
1735 pub provider: Option<String>,
1736 #[serde(default, skip_serializing_if = "Option::is_none")]
1737 pub model: Option<String>,
1738 #[serde(default, skip_serializing_if = "Option::is_none")]
1739 pub harness: Option<String>,
1740 #[serde(default, skip_serializing_if = "Option::is_none")]
1741 pub thinking_level: Option<String>,
1742 pub usage_summary: Value,
1743 #[serde(default, skip_serializing_if = "Option::is_none")]
1744 pub last_progress_at: Option<String>,
1745 #[serde(default, skip_serializing_if = "Option::is_none")]
1746 pub report_flush_state: Option<String>,
1747 #[serde(default, skip_serializing_if = "Option::is_none")]
1748 pub attach_reason: Option<String>,
1749 pub attach_precedence: Vec<String>,
1750 #[serde(default, skip_serializing_if = "Option::is_none")]
1751 pub winning_attach_rule: Option<String>,
1752 #[serde(default, skip_serializing_if = "Option::is_none")]
1753 pub probe_source: Option<String>,
1754 #[serde(default, skip_serializing_if = "Option::is_none")]
1755 pub probe_confidence: Option<f32>,
1756 pub status: String,
1757 pub started_at: String,
1758 pub actor_chain: Vec<ActorChainEntrySchema>,
1759}
1760
1761#[derive(Debug, Serialize, JsonSchema)]
1762pub struct ActorChainEntrySchema {
1763 pub session_id: String,
1764 #[serde(default, skip_serializing_if = "Option::is_none")]
1765 pub native_actor_key: Option<String>,
1766 #[serde(default, skip_serializing_if = "Option::is_none")]
1767 pub native_parent_actor_key: Option<String>,
1768 pub thread: String,
1769 pub status: String,
1770 #[serde(default, skip_serializing_if = "Option::is_none")]
1771 pub provider: Option<String>,
1772 #[serde(default, skip_serializing_if = "Option::is_none")]
1773 pub model: Option<String>,
1774 #[serde(default, skip_serializing_if = "Option::is_none")]
1775 pub harness: Option<String>,
1776}
1777
1778#[derive(Debug, Serialize, JsonSchema)]
1779pub struct AgentServeSchema {
1780 pub output_kind: String,
1781 pub status: String,
1782 pub socket_path: String,
1783 pub pid_path: String,
1784}
1785
1786#[derive(Debug, Serialize, JsonSchema)]
1787pub struct AgentDaemonStatusSchema {
1788 pub output_kind: String,
1789 pub running: bool,
1790 pub pid: Option<u32>,
1791 pub socket_path: String,
1792 pub pid_path: String,
1793 #[serde(rename = "verification")]
1794 pub trust: RepositoryVerificationStateSchema,
1795}
1796
1797#[derive(Debug, Serialize, JsonSchema)]
1798pub struct AgentStopSchema {
1799 pub output_kind: String,
1800 pub stopped: bool,
1801 pub swept_stale: bool,
1802 pub pid: Option<i32>,
1803 pub reason: Option<String>,
1804}
1805
1806#[derive(Debug, Serialize, JsonSchema)]
1807pub struct AgentReservationEnvelopeSchema {
1808 pub reservation: AgentReservationSchema,
1809}
1810
1811#[derive(Debug, Serialize, JsonSchema)]
1812pub struct AgentReservationListSchema {
1813 pub reservations: Vec<AgentReservationSchema>,
1814 pub alive_only: bool,
1815 pub thread: Option<String>,
1816 #[serde(rename = "verification")]
1817 pub trust: RepositoryVerificationStateSchema,
1818}
1819
1820#[derive(Debug, Serialize, JsonSchema)]
1821pub struct AgentReservationSchema {
1822 pub session_id: String,
1823 pub reservation_token: Option<String>,
1824 pub thread: String,
1825 pub anchor_state: Option<String>,
1826 pub anchor_root: Option<String>,
1827 pub status: String,
1828 pub path: Option<String>,
1829 pub task: Option<String>,
1830 pub provider: Option<String>,
1831 pub model: Option<String>,
1832 pub harness: Option<String>,
1833 pub thinking_level: Option<String>,
1834 pub probe_source: Option<String>,
1835 pub probe_confidence: Option<f32>,
1836}
1837
1838#[derive(Debug, Serialize, JsonSchema)]
1839pub struct SessionEnvelopeSchema {
1840 pub session: SessionEntrySchema,
1841}
1842
1843#[derive(Debug, Serialize, JsonSchema)]
1844pub struct SessionSegmentEnvelopeSchema {
1845 pub segment: SessionSegmentSchema,
1846}
1847
1848#[derive(Debug, Serialize, JsonSchema)]
1849pub struct SessionListSchema {
1850 pub sessions: Vec<SessionEntrySchema>,
1851 pub active_only: bool,
1852 #[serde(rename = "verification")]
1853 pub trust: RepositoryVerificationStateSchema,
1854}
1855
1856#[derive(Debug, Serialize, JsonSchema)]
1857pub struct SessionEntrySchema {
1858 pub id: String,
1859 pub principal: String,
1860 pub created_at: String,
1861 #[serde(default, skip_serializing_if = "Option::is_none")]
1862 pub ended_at: Option<String>,
1863 pub active: bool,
1864 pub segments: Vec<SessionSegmentSchema>,
1865}
1866
1867#[derive(Debug, Serialize, JsonSchema)]
1868pub struct SessionSegmentSchema {
1869 pub id: String,
1870 pub provider: String,
1871 pub model: String,
1872 pub started_at: String,
1873 #[serde(default, skip_serializing_if = "Option::is_none")]
1874 pub policy_id: Option<String>,
1875}
1876
1877#[derive(Debug, Serialize, JsonSchema)]
1878pub struct FetchSchema {
1879 pub output_kind: Option<String>,
1880 pub remote: String,
1881 #[serde(default, skip_serializing_if = "Option::is_none")]
1882 pub ref_scope: Option<String>,
1883 #[serde(default, skip_serializing_if = "Option::is_none")]
1884 pub tags_included: Option<bool>,
1885 pub refs_fetched: usize,
1886 pub objects_fetched: usize,
1887}
1888
1889#[derive(Debug, Serialize, JsonSchema)]
1890pub struct PullSchema {
1891 pub output_kind: Option<String>,
1892 pub action: Option<String>,
1893 pub status: Option<String>,
1894 pub pulled: Option<bool>,
1895 pub changed: Option<bool>,
1896 pub success: Option<bool>,
1897 pub transport: Option<String>,
1898 pub remote: Option<String>,
1899 #[serde(default, skip_serializing_if = "Option::is_none")]
1900 pub branch: Option<String>,
1901 #[serde(default, skip_serializing_if = "Option::is_none")]
1902 pub old_git_head: Option<String>,
1903 #[serde(default, skip_serializing_if = "Option::is_none")]
1904 pub new_git_head: Option<String>,
1905 #[serde(default, skip_serializing_if = "Option::is_none")]
1906 pub old_state: Option<String>,
1907 #[serde(default, skip_serializing_if = "Option::is_none")]
1908 pub new_state: Option<String>,
1909 #[serde(default, skip_serializing_if = "Option::is_none")]
1910 pub ref_scope: Option<String>,
1911 #[serde(default, skip_serializing_if = "Option::is_none")]
1912 pub thread: Option<String>,
1913 pub state: Option<String>,
1914 pub objects: Option<usize>,
1915 pub states_created: Option<usize>,
1916 pub commits_seen: Option<usize>,
1917 #[serde(default, skip_serializing_if = "Option::is_none")]
1918 pub commits_seen_scope: Option<String>,
1919 pub materialized_checkout: Option<bool>,
1920 #[serde(default, skip_serializing_if = "Option::is_none")]
1921 pub changed_path_count: Option<usize>,
1922 #[serde(default, skip_serializing_if = "Option::is_none")]
1923 pub changed_paths: Option<Vec<String>>,
1924}
1925
1926#[derive(Debug, Serialize, JsonSchema)]
1927pub struct PushSchema {
1928 pub output_kind: String,
1929 pub action: String,
1930 pub status: String,
1931 pub pushed: bool,
1932 pub changed: bool,
1933 pub success: bool,
1934 pub transport: String,
1935 pub remote: Option<String>,
1936 #[serde(default, skip_serializing_if = "Option::is_none")]
1937 pub push_scope: Option<String>,
1938 #[serde(default, skip_serializing_if = "Option::is_none")]
1939 pub ref_scope: Option<String>,
1940 #[serde(default, skip_serializing_if = "Option::is_none")]
1941 pub git_notes_ref: Option<String>,
1942 #[serde(default, skip_serializing_if = "Option::is_none")]
1946 pub refs_written: Option<Vec<String>>,
1947 #[serde(default, skip_serializing_if = "Option::is_none")]
1948 pub git_notes_visibility_warning: Option<String>,
1949 #[serde(default, skip_serializing_if = "Option::is_none")]
1950 pub git_tracking_remote: Option<String>,
1951 #[serde(default, skip_serializing_if = "Option::is_none")]
1952 pub git_remote_configured: Option<GitRemoteConfiguredSchema>,
1953 #[serde(default, skip_serializing_if = "Option::is_none")]
1954 pub git_upstream_configured: Option<GitUpstreamConfiguredSchema>,
1955 #[serde(default, skip_serializing_if = "Option::is_none")]
1956 pub tags_included: Option<bool>,
1957 #[serde(default, skip_serializing_if = "Option::is_none")]
1958 pub force: Option<bool>,
1959 #[serde(default, skip_serializing_if = "Option::is_none")]
1960 pub force_discard_warning: Option<String>,
1961 #[serde(default, skip_serializing_if = "Option::is_none")]
1962 pub thread: Option<String>,
1963 pub state: Option<String>,
1964 pub objects: Option<usize>,
1965 pub next_action: NullableStringSchema,
1970 pub next_action_template: NullableActionTemplateSchema,
1971 pub recommended_action: NullableStringSchema,
1972 pub recommended_action_template: NullableActionTemplateSchema,
1973}
1974
1975#[derive(Debug, Serialize, JsonSchema)]
1976pub struct GitRemoteConfiguredSchema {
1977 pub name: String,
1978 pub url: String,
1979}
1980
1981#[derive(Debug, Serialize, JsonSchema)]
1982pub struct GitUpstreamConfiguredSchema {
1983 pub branch: String,
1984 pub remote: String,
1985}
1986
1987#[derive(Debug, Serialize, JsonSchema)]
1988pub struct ParallelThreadInfoSchema {
1989 pub name: String,
1990 pub coordination_status: CoordinationStatusSchema,
1991 pub current_state: Option<String>,
1992}
1993
1994type OpaqueObject = Option<Value>;
1998
1999#[derive(Debug, Serialize, JsonSchema)]
2000pub struct RepositoryContextInfoSchema {
2001 pub kind: String,
2002 pub parent_repository: Option<String>,
2003 pub target_thread: Option<String>,
2004 pub parent_thread: Option<String>,
2005}
2006
2007#[derive(Debug, Serialize, JsonSchema)]
2010pub struct StatusSchema {
2011 pub output_kind: Option<String>,
2012 pub repository_capability: String,
2013 pub repository_label: String,
2014 pub repository_context: Option<RepositoryContextInfoSchema>,
2015 pub storage_model: String,
2016 pub hosted_enabled: bool,
2017 pub operation: OpaqueObject,
2018 pub remote_tracking: OpaqueObject,
2019 pub git_overlay_health: GitOverlayHealthSchema,
2020 #[serde(rename = "verification")]
2021 pub trust: RepositoryVerificationStateSchema,
2022 pub thread: Option<String>,
2023 pub base_state: Option<String>,
2024 pub base_root: Option<String>,
2025 pub current_state: Option<String>,
2026 pub path: Option<String>,
2027 pub execution_path: Option<String>,
2028 #[serde(default, skip_serializing_if = "Option::is_none")]
2029 pub session_id: Option<String>,
2030 #[serde(default, skip_serializing_if = "Option::is_none")]
2031 pub heddle_session_id: Option<String>,
2032 #[serde(default, skip_serializing_if = "Option::is_none")]
2033 pub actor: Option<ActorInfoSchema>,
2034 #[serde(default, skip_serializing_if = "Option::is_none")]
2035 pub harness: Option<String>,
2036 #[serde(default, skip_serializing_if = "Option::is_none")]
2037 pub thinking_level: Option<String>,
2038 #[serde(default, skip_serializing_if = "Option::is_none")]
2039 pub usage_summary: OpaqueObject,
2040 #[serde(default, skip_serializing_if = "Option::is_none")]
2041 pub last_progress_at: Option<String>,
2042 #[serde(default, skip_serializing_if = "Option::is_none")]
2043 pub report_flush_state: Option<String>,
2044 #[serde(default, skip_serializing_if = "Option::is_none")]
2045 pub attach_reason: Option<String>,
2046 pub thread_mode: Option<ThreadModeSchema>,
2047 pub thread_state: Option<ThreadStateSchema>,
2048 pub freshness: Option<ThreadFreshnessSchema>,
2049 #[serde(default, skip_serializing_if = "Option::is_none")]
2050 pub target_thread: Option<String>,
2051 #[serde(default, skip_serializing_if = "Option::is_none")]
2052 pub parent_thread: Option<String>,
2053 pub child_threads: Vec<String>,
2054 #[serde(default, skip_serializing_if = "Option::is_none")]
2055 pub task: Option<String>,
2056 pub promotion_suggested: bool,
2057 pub impact_categories: Vec<ThreadImpactCategorySchema>,
2058 pub heavy_impact_paths: Vec<String>,
2059 pub changed_path_count: usize,
2060 pub worktree_changed_path_count: usize,
2061 pub thread_changed_path_count: usize,
2062 pub blockers: Vec<String>,
2063 pub recommended_action: NullableStringSchema,
2064 pub recommended_action_template: Option<ActionTemplateSchema>,
2065 pub recovery_commands: Vec<String>,
2066 pub recovery_action_templates: Vec<ActionTemplateSchema>,
2067 pub thread_health: String,
2068 pub coordination_status: CoordinationStatusSchema,
2069 pub is_isolated: bool,
2070 pub parallel_threads: Vec<ParallelThreadInfoSchema>,
2071 pub state: Option<StateInfoSchema>,
2072 pub git_checkpoint: Option<GitCheckpointInfoSchema>,
2073 pub changes: ChangesInfoSchema,
2074 pub git_index: Option<GitIndexInfoSchema>,
2075}
2076
2077#[derive(Debug, Serialize, JsonSchema)]
2080pub struct VerifySchema {
2081 pub output_kind: String,
2082 pub clean: bool,
2083 pub repository_label: String,
2084 pub repository_context: Option<RepositoryContextInfoSchema>,
2085 #[serde(flatten)]
2086 pub verification: RepositoryVerificationStateSchema,
2087}
2088
2089#[derive(Debug, Serialize, JsonSchema)]
2090pub struct RepositoryVerificationStateSchema {
2091 #[serde(rename = "verified")]
2092 pub verified: bool,
2093 pub status: String,
2094 pub repository_mode: String,
2095 pub heddle_initialized: bool,
2096 pub git_branch: Option<String>,
2097 pub heddle_thread: Option<String>,
2098 pub worktree_dirty: bool,
2099 pub worktree_state: String,
2100 pub import_state: String,
2101 pub mapping_state: String,
2102 pub remote_drift: String,
2103 pub active_operation: Option<String>,
2104 pub default_remote: Option<String>,
2105 pub clone_verification: String,
2106 pub machine_contract: String,
2107 #[serde(default, skip_serializing_if = "Option::is_none")]
2108 pub machine_contract_coverage: Option<MachineContractCoverageSchema>,
2109 pub workflow_status: String,
2110 pub workflow_summary: String,
2111 pub summary: String,
2112 pub recommended_action: Option<String>,
2113 pub recommended_action_template: Option<ActionTemplateSchema>,
2114 pub recovery_commands: Vec<String>,
2115 pub recovery_action_templates: Vec<ActionTemplateSchema>,
2116 pub checks: Vec<VerificationCheckSchema>,
2117}
2118
2119#[derive(Debug, Serialize, JsonSchema)]
2120pub struct MachineContractCoverageSchema {
2121 pub status: String,
2122 #[serde(rename = "verified_scope")]
2123 pub verified_scope: String,
2124 pub advanced_scope: String,
2125 pub summary: String,
2126 pub catalog_commands_total: usize,
2127 pub catalog_mutating_commands_total: usize,
2128 pub json_commands_total: usize,
2129 pub json_mutating_commands_total: usize,
2130 pub json_commands_with_schema: usize,
2131 pub json_commands_with_accepted_opaque_schema: usize,
2132 pub json_commands_without_schema: usize,
2133 #[serde(rename = "verified_scope_json_commands_total")]
2134 pub verified_scope_json_commands_total: usize,
2135 #[serde(rename = "verified_scope_json_commands_with_schema")]
2136 pub verified_scope_json_commands_with_schema: usize,
2137 #[serde(rename = "verified_scope_json_commands_with_accepted_opaque_schema")]
2138 pub verified_scope_json_commands_with_accepted_opaque_schema: usize,
2139 #[serde(rename = "verified_scope_json_commands_without_schema")]
2140 pub verified_scope_json_commands_without_schema: usize,
2141 pub advanced_scope_json_commands_total: usize,
2142 pub advanced_scope_json_commands_with_accepted_opaque_schema: usize,
2143 pub mutating_commands_total: usize,
2144 pub mutating_commands_with_schema: usize,
2145 pub mutating_commands_with_accepted_opaque_schema: usize,
2146 pub mutating_commands_without_schema: usize,
2147 #[serde(rename = "verified_scope_mutating_commands_total")]
2148 pub verified_scope_mutating_commands_total: usize,
2149 #[serde(rename = "verified_scope_mutating_commands_with_schema")]
2150 pub verified_scope_mutating_commands_with_schema: usize,
2151 #[serde(rename = "verified_scope_mutating_commands_with_accepted_opaque_schema")]
2152 pub verified_scope_mutating_commands_with_accepted_opaque_schema: usize,
2153 #[serde(rename = "verified_scope_mutating_commands_without_schema")]
2154 pub verified_scope_mutating_commands_without_schema: usize,
2155 pub advanced_scope_mutating_commands_total: usize,
2156 pub advanced_scope_mutating_commands_with_accepted_opaque_schema: usize,
2157 pub schema_verbs_total: usize,
2158 pub documented_schema_verbs_total: usize,
2159 pub undocumented_schema_verbs_total: usize,
2160 pub opaque_schema_verbs_total: usize,
2161 pub accepted_opaque_schema_verbs_total: usize,
2162 pub unaccepted_opaque_schema_verbs_total: usize,
2163 pub supports_op_id_total: usize,
2164 pub jsonl_commands_total: usize,
2165 pub missing_schema_examples: Vec<String>,
2166 pub missing_mutating_schema_examples: Vec<String>,
2167 #[serde(rename = "verified_scope_missing_schema_examples")]
2168 pub verified_scope_missing_schema_examples: Vec<String>,
2169 #[serde(rename = "verified_scope_accepted_opaque_schema_examples")]
2170 pub verified_scope_accepted_opaque_schema_examples: Vec<String>,
2171 pub advanced_scope_accepted_opaque_schema_examples: Vec<String>,
2172 pub accepted_opaque_schema_examples: Vec<String>,
2173 pub unaccepted_opaque_schema_examples: Vec<String>,
2174 pub undocumented_schema_examples: Vec<String>,
2175}
2176
2177#[derive(Debug, Serialize, JsonSchema)]
2178pub struct VerificationCheckSchema {
2179 pub name: String,
2180 pub status: String,
2181 pub clean: bool,
2182 pub summary: String,
2183 pub recommended_action: Option<String>,
2184 pub recommended_action_template: Option<ActionTemplateSchema>,
2185 pub recovery_commands: Vec<String>,
2186 pub recovery_action_templates: Vec<ActionTemplateSchema>,
2187 pub details: std::collections::BTreeMap<String, String>,
2188}
2189
2190#[derive(Debug, Serialize, JsonSchema)]
2191pub struct ActionTemplateSchema {
2192 pub action: String,
2193 pub argv_template: Vec<String>,
2194 pub required_inputs: Vec<String>,
2195 pub agent_may_fill: bool,
2202}
2203
2204#[derive(Debug, Serialize, JsonSchema)]
2207pub struct BridgeGitStatusSchema {
2208 pub output_kind: Option<String>,
2209 pub repository_capability: String,
2210 pub storage_model: String,
2211 pub mirror_path: Option<String>,
2212 pub mirror_initialized: bool,
2213 pub git_overlay_import_hint: Option<GitOverlayImportHintSchema>,
2214 pub git_overlay_health: GitOverlayHealthSchema,
2215 pub recommended_action: Option<String>,
2216 pub recommended_action_template: Option<ActionTemplateSchema>,
2217 pub recovery_commands: Vec<String>,
2218 #[serde(rename = "verification")]
2219 pub trust: RepositoryVerificationStateSchema,
2220}
2221
2222#[derive(Debug, Serialize, JsonSchema)]
2223pub struct GitOverlayImportHintSchema {
2224 pub current_branch: String,
2225 pub missing_branch_count: usize,
2226 pub missing_branches: Vec<String>,
2227 pub recommended_command: String,
2228}
2229
2230#[derive(Debug, Serialize, JsonSchema)]
2231pub struct GitOverlayHealthSchema {
2232 pub status: String,
2233 pub clean: bool,
2234 pub summary: String,
2235 pub recovery_commands: Vec<String>,
2236 pub checks: Vec<GitOverlayHealthCheckSchema>,
2237}
2238
2239#[derive(Debug, Serialize, JsonSchema)]
2240pub struct GitOverlayHealthCheckSchema {
2241 pub name: String,
2242 pub status: String,
2243 pub summary: String,
2244}
2245
2246#[derive(Debug, Serialize, JsonSchema)]
2249pub struct LogSchema {
2250 pub output_kind: Option<String>,
2251 pub status: Option<String>,
2252 pub repository_capability: String,
2253 pub storage_model: String,
2254 pub states: Vec<StateEntrySchema>,
2255}
2256
2257#[derive(Debug, Serialize, JsonSchema)]
2258pub struct StateEntrySchema {
2259 pub change_id: String,
2260 pub content_hash: String,
2261 pub intent: Option<String>,
2262 pub principal: String,
2263 pub agent: Option<String>,
2264 pub confidence: Option<f32>,
2265 pub created_at: String,
2266 pub parents: Vec<String>,
2267 pub git_checkpoint: Option<String>,
2268 pub collapsed: Option<CollapsedEntrySchema>,
2269}
2270
2271#[derive(Debug, Serialize, JsonSchema)]
2272pub struct CollapsedEntrySchema {
2273 pub expandable: bool,
2274 pub source_count: usize,
2275}
2276
2277#[derive(Debug, Serialize, JsonSchema)]
2278pub struct TimelineLogSchema {
2279 pub output_kind: String,
2280 pub status: String,
2281 pub repository_capability: String,
2282 pub storage_model: String,
2283 pub thread: String,
2284 pub cursor: TimelineCursorSchema,
2285 pub branches: Vec<TimelineBranchSchema>,
2286 pub steps: Vec<TimelineStepSchema>,
2287 pub active_branch_path: Vec<String>,
2288 pub actions: TimelineActionsSchema,
2289 pub recovery: Option<TimelineRecoverySchema>,
2290}
2291
2292#[derive(Debug, Serialize, JsonSchema)]
2293pub struct TimelineCursorSchema {
2294 pub branch_id: Option<String>,
2295 pub step_id: Option<String>,
2296 pub state: Option<String>,
2297 pub state_full: Option<String>,
2298}
2299
2300#[derive(Debug, Serialize, JsonSchema)]
2301pub struct TimelineBranchSchema {
2302 pub branch_id: String,
2303 pub parent_branch_id: Option<String>,
2304 pub forked_from_step_id: Option<String>,
2305 pub forked_from_state: Option<String>,
2306 pub reason: Option<String>,
2307 pub created_at_ms: Option<i64>,
2308 pub step_ids: Vec<String>,
2309 pub is_active: bool,
2310 pub is_on_active_path: bool,
2311}
2312
2313#[derive(Debug, Serialize, JsonSchema)]
2314pub struct TimelineStepSchema {
2315 pub step_id: String,
2316 pub branch_id: String,
2317 pub parent_step_id: Option<String>,
2318 pub native: Option<TimelineNativeSchema>,
2319 pub tool_name: Option<String>,
2320 pub status: Option<String>,
2321 pub changed: Option<bool>,
2322 pub touched_paths: Vec<String>,
2323 pub labels: Vec<String>,
2324 pub before_state: Option<String>,
2325 pub after_state: Option<String>,
2326 pub capture_state: Option<String>,
2327 pub cursor_state: Option<String>,
2328 pub cursor_state_full: Option<String>,
2329 pub payload_summary: Option<String>,
2330 pub payload_hash: Option<String>,
2331 pub capture_oplog_batch_id: Option<u64>,
2332 pub started_at_ms: Option<i64>,
2333 pub finished_at_ms: Option<i64>,
2334 pub operation_ids: Vec<String>,
2335 pub is_current: bool,
2336 pub is_on_active_branch_path: bool,
2337 pub can_seek: bool,
2338 pub can_fork: bool,
2339 pub can_reset: bool,
2340 pub can_materialize: bool,
2341 pub has_boundary_warning: bool,
2342}
2343
2344#[derive(Debug, Serialize, JsonSchema)]
2345pub struct TimelineNativeSchema {
2346 pub harness: String,
2347 pub session_id: Option<String>,
2348 pub message_id: Option<String>,
2349 pub tool_call_id: String,
2350}
2351
2352#[derive(Debug, Serialize, JsonSchema)]
2353pub struct TimelineActionsSchema {
2354 pub can_undo: bool,
2355 pub can_redo: bool,
2356}
2357
2358#[derive(Debug, Serialize, JsonSchema)]
2359pub struct TimelineRecoverySchema {
2360 pub status: String,
2361 pub branch_id: String,
2362 pub from_step_id: Option<String>,
2363 pub to_step_id: Option<String>,
2364 pub from_state: String,
2365 pub to_state: String,
2366 pub reason: String,
2367 pub moved_at_ms: i64,
2368 pub checkout_state: Option<String>,
2369}
2370
2371#[derive(Debug, Serialize, JsonSchema)]
2372pub struct TimelineActionSchema {
2373 pub output_kind: String,
2374 pub status: String,
2375 pub action: String,
2376 pub thread: String,
2377 pub branch_id: Option<String>,
2378 pub parent_branch_id: Option<String>,
2379 pub from_step_id: Option<String>,
2380 pub cursor_branch_id: Option<String>,
2381 pub cursor_step_id: Option<String>,
2382 pub operation_id: Option<String>,
2383 pub recovered_operation_id: Option<String>,
2384 pub materialized: Option<bool>,
2385 pub materialization_status: Option<String>,
2386 pub recovery_status: Option<String>,
2387 pub blocker_count: usize,
2388 pub branch_count: usize,
2389 pub step_count: usize,
2390}
2391
2392#[derive(Debug, Serialize, JsonSchema)]
2393pub struct ExpandSchema {
2394 pub output_kind: String,
2395 pub status: String,
2396 pub requested: String,
2397 pub collapsed: ExpandedCollapseSchema,
2398 pub captures: Vec<ExpandedCaptureSchema>,
2399}
2400
2401#[derive(Debug, Serialize, JsonSchema)]
2402pub struct ExpandedCollapseSchema {
2403 pub change_id: String,
2404 pub change_id_full: String,
2405 pub git_commit: Option<String>,
2406 pub thread: Option<String>,
2407 pub source_count: usize,
2408}
2409
2410#[derive(Debug, Serialize, JsonSchema)]
2411pub struct ExpandedCaptureSchema {
2412 pub change_id: String,
2413 pub change_id_full: String,
2414 pub content_hash: String,
2415 pub intent: Option<String>,
2416 pub principal: String,
2417 pub agent: Option<String>,
2418 pub confidence: Option<f32>,
2419 pub created_at: String,
2420 pub parents: Vec<String>,
2421}
2422
2423#[derive(Debug, Serialize, JsonSchema)]
2424pub struct LogReflogSchema {
2425 pub output_kind: Option<String>,
2426 pub status: Option<String>,
2427 pub repository_capability: String,
2428 pub storage_model: String,
2429 pub entries: Vec<ReflogEntrySchema>,
2430}
2431
2432#[derive(Debug, Serialize, JsonSchema)]
2433pub struct ReflogEntrySchema {
2434 pub source: String,
2435 pub reference: String,
2436 pub old_oid: String,
2437 pub new_oid: String,
2438 pub actor: String,
2439 pub timestamp: Option<String>,
2440 pub message: String,
2441}
2442
2443#[derive(Debug, Serialize, JsonSchema)]
2446pub struct ShowSchema {
2447 pub output_kind: String,
2448 pub repository_capability: String,
2449 pub storage_model: String,
2450 pub change_id: String,
2451 pub change_id_full: String,
2452 pub content_hash: String,
2453 pub tree: String,
2454 pub parents: Vec<String>,
2455 pub intent: Option<String>,
2456 pub confidence: Option<f32>,
2457 pub principal: ShowPrincipalSchema,
2458 pub agent: Option<ShowAgentSchema>,
2459 pub created_at: String,
2460 pub status: String,
2461 pub verification: OpaqueObject,
2462 pub git_checkpoint: Option<String>,
2463}
2464
2465#[derive(Debug, Serialize, JsonSchema)]
2466pub struct ShowPrincipalSchema {
2467 pub name: String,
2468 pub email: String,
2469}
2470
2471#[derive(Debug, Serialize, JsonSchema)]
2472pub struct ShowAgentSchema {
2473 pub provider: Option<String>,
2474 pub model: Option<String>,
2475 #[serde(default, skip_serializing_if = "Option::is_none")]
2476 pub session_id: Option<String>,
2477 #[serde(default, skip_serializing_if = "Option::is_none")]
2478 pub policy_id: Option<String>,
2479}
2480
2481#[derive(Debug, Serialize, JsonSchema)]
2484pub struct ThreadListSchema {
2485 pub output_kind: Option<String>,
2486 pub repository_capability: String,
2487 pub repository_label: String,
2488 pub repository_context: Option<RepositoryContextInfoSchema>,
2489 pub storage_model: String,
2490 pub hosted_enabled: bool,
2491 pub threads: Vec<ThreadSummarySchema>,
2492 pub available_git_refs: Vec<AvailableGitRefSchema>,
2493 pub current: Option<String>,
2494 #[serde(rename = "verification")]
2495 pub trust: RepositoryVerificationStateSchema,
2496 pub recommended_action: Option<String>,
2497 pub recommended_action_template: Option<ActionTemplateSchema>,
2498 pub recovery_commands: Vec<String>,
2499 pub recovery_action_templates: Vec<ActionTemplateSchema>,
2500}
2501
2502#[derive(Debug, Serialize, JsonSchema)]
2503pub struct AvailableGitRefSchema {
2504 pub name: String,
2505 pub git_commit: String,
2506 pub recommended_action: Option<String>,
2507 pub recommended_action_template: Option<ActionTemplateSchema>,
2508}
2509
2510#[derive(Debug, Serialize, JsonSchema)]
2513pub struct ReviewShowSchema {
2514 pub output_kind: String,
2515 pub change_id: String,
2516 pub headline: String,
2517 pub agent_narrative: Option<String>,
2518 pub files_changed: usize,
2519 pub in_budget_signals: Vec<Value>,
2520 pub all_signals: Vec<Value>,
2521 pub discussions: Vec<Value>,
2522 pub signing_kinds: Vec<String>,
2523 pub signatures: Vec<Value>,
2524}
2525
2526#[derive(Debug, Serialize, JsonSchema)]
2527pub struct ReviewSignSchema {
2528 pub output_kind: String,
2529 pub signature_id: String,
2530 pub change_id: String,
2531}
2532
2533#[derive(Debug, Serialize, JsonSchema)]
2541pub struct ReviewNextSchema {
2542 pub output_kind: String,
2543 pub change_id: Option<String>,
2544 pub headline: Option<String>,
2545 pub existing_signatures: Option<u32>,
2546 pub next: RequiredNullableNextState,
2547}
2548
2549#[derive(Debug, Serialize)]
2557#[serde(transparent)]
2558pub struct RequiredNullableNextState(pub Option<ReviewNextStateSchema>);
2559
2560impl JsonSchema for RequiredNullableNextState {
2561 fn schema_name() -> std::borrow::Cow<'static, str> {
2562 std::borrow::Cow::Borrowed("RequiredNullableNextState")
2563 }
2564
2565 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
2566 <Option<ReviewNextStateSchema> as JsonSchema>::json_schema(generator)
2567 }
2568}
2569
2570#[derive(Debug, Serialize, JsonSchema)]
2572pub struct ReviewNextStateSchema {
2573 pub change_id: String,
2574 pub headline: String,
2575 pub existing_signatures: u32,
2576}
2577
2578#[derive(Debug, Serialize, JsonSchema)]
2579pub struct ReviewHealthSchema {
2580 pub output_kind: String,
2581 pub entries: Vec<ReviewHealthEntrySchema>,
2582 pub window_states: usize,
2583}
2584
2585#[derive(Debug, Serialize, JsonSchema)]
2586pub struct ReviewHealthEntrySchema {
2587 pub module_id: String,
2588 pub fire_rate: f64,
2589 pub warn: bool,
2590}
2591
2592#[derive(Debug, Serialize, JsonSchema)]
2595pub struct TransactionCommitSchema {
2596 pub change_id: String,
2597 pub op_count: u32,
2598}
2599
2600#[derive(Debug, Serialize, JsonSchema)]
2603pub struct SchemasListSchema {
2604 pub output_kind: Option<String>,
2605 pub status: Option<String>,
2606 pub schema_verbs: Vec<String>,
2607 pub documented_schema_verbs: Vec<String>,
2608}
2609
2610#[derive(Debug, Serialize, JsonSchema)]
2611pub struct DoctorDocsSchema {
2612 pub output_kind: String,
2613 pub status: String,
2614 #[serde(rename = "verified")]
2615 pub verified: bool,
2616 pub recommended_action: Option<String>,
2617 pub files_scanned: usize,
2618 pub issues: Vec<DoctorDocsIssueSchema>,
2619}
2620
2621#[derive(Debug, Serialize, JsonSchema)]
2622pub struct DoctorDocsIssueSchema {
2623 pub file: String,
2624 pub line: usize,
2625 pub invocation: String,
2626 pub kind: String,
2627 pub detail: String,
2628 pub suggestion: Option<String>,
2629}
2630
2631#[derive(Debug, Serialize, JsonSchema)]
2632pub struct DoctorSchemasSchema {
2633 pub output_kind: String,
2634 pub status: String,
2635 #[serde(rename = "verified")]
2636 pub verified: bool,
2637 pub summary: String,
2638 pub recommended_action: Option<String>,
2639 pub recovery_commands: Vec<String>,
2640 pub registered_verbs: Vec<String>,
2641 pub documented_verbs: Vec<String>,
2642 pub undocumented_verbs: Vec<String>,
2643 pub unmatched_verbs: Vec<String>,
2644 pub passing_verbs: Vec<String>,
2645 pub issues: Vec<DoctorSchemaIssueSchema>,
2646 pub command_contract_schema_coverage: CommandContractSchemaCoverageSchema,
2647 pub doc_path: String,
2648}
2649
2650#[derive(Debug, Serialize, JsonSchema)]
2651pub struct DoctorSchemaIssueSchema {
2652 pub verb: String,
2653 pub line: usize,
2654 pub unknown_key: String,
2655 pub detail: String,
2656}
2657
2658#[derive(Debug, Serialize, JsonSchema)]
2659pub struct CommandContractSchemaCoverageSchema {
2660 pub status: String,
2661 #[serde(rename = "verified_scope")]
2662 pub verified_scope: String,
2663 pub advanced_scope: String,
2664 pub summary: String,
2665 pub catalog_commands_total: usize,
2666 pub catalog_mutating_commands_total: usize,
2667 pub json_commands_total: usize,
2668 pub json_mutating_commands_total: usize,
2669 pub json_commands_with_schema: usize,
2670 pub json_commands_with_accepted_opaque_schema: usize,
2671 pub json_commands_without_schema: usize,
2672 #[serde(rename = "verified_scope_json_commands_total")]
2673 pub verified_scope_json_commands_total: usize,
2674 #[serde(rename = "verified_scope_json_commands_with_schema")]
2675 pub verified_scope_json_commands_with_schema: usize,
2676 #[serde(rename = "verified_scope_json_commands_with_accepted_opaque_schema")]
2677 pub verified_scope_json_commands_with_accepted_opaque_schema: usize,
2678 #[serde(rename = "verified_scope_json_commands_without_schema")]
2679 pub verified_scope_json_commands_without_schema: usize,
2680 pub advanced_scope_json_commands_total: usize,
2681 pub advanced_scope_json_commands_with_accepted_opaque_schema: usize,
2682 pub mutating_commands_total: usize,
2683 pub mutating_commands_with_schema: usize,
2684 pub mutating_commands_with_accepted_opaque_schema: usize,
2685 pub mutating_commands_without_schema: usize,
2686 #[serde(rename = "verified_scope_mutating_commands_total")]
2687 pub verified_scope_mutating_commands_total: usize,
2688 #[serde(rename = "verified_scope_mutating_commands_with_schema")]
2689 pub verified_scope_mutating_commands_with_schema: usize,
2690 #[serde(rename = "verified_scope_mutating_commands_with_accepted_opaque_schema")]
2691 pub verified_scope_mutating_commands_with_accepted_opaque_schema: usize,
2692 #[serde(rename = "verified_scope_mutating_commands_without_schema")]
2693 pub verified_scope_mutating_commands_without_schema: usize,
2694 pub advanced_scope_mutating_commands_total: usize,
2695 pub advanced_scope_mutating_commands_with_accepted_opaque_schema: usize,
2696 pub undocumented_schema_verbs_total: usize,
2697 pub opaque_schema_verbs_total: usize,
2698 pub accepted_opaque_schema_verbs_total: usize,
2699 pub unaccepted_opaque_schema_verbs_total: usize,
2700 pub missing_schema_examples: Vec<String>,
2701 pub missing_mutating_schema_examples: Vec<String>,
2702 #[serde(rename = "verified_scope_missing_schema_examples")]
2703 pub verified_scope_missing_schema_examples: Vec<String>,
2704 #[serde(rename = "verified_scope_accepted_opaque_schema_examples")]
2705 pub verified_scope_accepted_opaque_schema_examples: Vec<String>,
2706 pub advanced_scope_accepted_opaque_schema_examples: Vec<String>,
2707 pub accepted_opaque_schema_examples: Vec<String>,
2708 pub unaccepted_opaque_schema_examples: Vec<String>,
2709 pub undocumented_schema_examples: Vec<String>,
2710}
2711
2712#[derive(Debug, Serialize, JsonSchema)]
2713pub struct GitOverlayGuideSchema {
2714 pub topic: String,
2715 pub summary: String,
2716 pub steps: Vec<String>,
2717}
2718
2719#[derive(Debug, Serialize, JsonSchema)]
2720pub struct WatchLineSchema {
2721 pub ts: String,
2722 pub thread: Option<String>,
2723 pub kind: String,
2724 pub change_id: Option<String>,
2725 pub intent: Option<String>,
2726 pub confidence: Option<f32>,
2727 pub actor: Option<ActorInfoSchema>,
2728 pub id: u64,
2729}
2730
2731#[derive(Debug, Serialize, JsonSchema)]
2732pub struct TrySchema {
2733 pub status: String,
2734 pub action: String,
2735 pub message: String,
2736 pub thread: String,
2737 pub thread_dropped: bool,
2738 pub cleanup_error: Option<String>,
2739 pub exit_code: Option<i32>,
2740 pub duration_ms: u128,
2741 pub captured_state: Option<String>,
2742 pub merge_state: Option<String>,
2743 pub next_action: Option<String>,
2744 pub next_action_template: Option<ActionTemplateSchema>,
2745 pub recommended_action: Option<String>,
2746 pub recommended_action_template: Option<ActionTemplateSchema>,
2747 pub recovery_commands: Vec<String>,
2748 pub recovery_action_templates: Vec<ActionTemplateSchema>,
2749}
2750
2751#[derive(Debug, Serialize, JsonSchema)]
2754pub struct BridgeInitSchema {
2755 pub initialized: bool,
2756 pub path: String,
2757}
2758
2759#[derive(Debug, Serialize, JsonSchema)]
2760pub struct ExportedRefSchema {
2761 pub name: String,
2762 pub tip: String,
2763}
2764
2765#[derive(Debug, Serialize, JsonSchema)]
2766pub struct BridgeExportSchema {
2767 pub states_exported: u64,
2768 pub commits_total: u64,
2769 pub threads_synced: u64,
2770 pub markers_synced: u64,
2771 pub branches: Vec<ExportedRefSchema>,
2772 pub tags: Vec<ExportedRefSchema>,
2773 pub destination: String,
2774}
2775
2776#[derive(Debug, Serialize, JsonSchema)]
2777pub struct BridgeImportSchema {
2778 pub output_kind: Option<String>,
2779 pub status: String,
2780 pub action: Option<String>,
2781 pub summary: String,
2782 pub commits_imported: u64,
2783 pub states_created: u64,
2784 pub branches_synced: u64,
2785 pub tags_synced: u64,
2786 pub skipped_non_commit_refs: u64,
2787 pub lossy_entries: Vec<LossyImportEntrySchema>,
2788 pub already_in_sync: bool,
2789 pub recommended_action: Option<String>,
2790 pub recommended_action_template: Option<ActionTemplateSchema>,
2791 pub recovery_commands: Vec<String>,
2792}
2793
2794#[derive(Debug, Serialize, JsonSchema)]
2795pub struct LossyImportEntrySchema {
2796 pub path: String,
2797 pub action: String,
2798 pub reason: String,
2799 pub git_object: Option<String>,
2800}
2801
2802#[derive(Debug, Serialize, JsonSchema)]
2803pub struct BridgeSyncSchema {
2804 pub output_kind: Option<String>,
2805 pub status: String,
2806 pub action: Option<String>,
2807 pub summary: String,
2808 pub states_exported: u64,
2809 pub commits_exported_total: u64,
2810 pub commits_imported: u64,
2811 pub threads_synced: u64,
2812 pub markers_synced: u64,
2813 pub recommended_action: Option<String>,
2814 pub recommended_action_template: Option<ActionTemplateSchema>,
2815 pub recovery_commands: Vec<String>,
2816}
2817
2818#[derive(Debug, Serialize, JsonSchema)]
2819pub struct BridgeGitReconcileSchema {
2820 pub output_kind: Option<String>,
2821 pub status: String,
2822 pub action: Option<String>,
2823 pub prefer: Option<String>,
2824 pub ref_name: String,
2825 pub preview: bool,
2826 pub summary: String,
2827 pub recommended_action: Option<String>,
2828 pub recommended_action_template: Option<ActionTemplateSchema>,
2829 pub recovery_commands: Vec<String>,
2830}
2831
2832#[derive(Debug, Serialize, JsonSchema)]
2833pub struct BridgePushSchema {
2834 pub output_kind: Option<String>,
2835 pub action: Option<String>,
2836 pub status: Option<String>,
2837 pub success: Option<bool>,
2838 pub pushed: bool,
2839 pub changed: Option<bool>,
2840 pub transport: Option<String>,
2841 pub remote: String,
2842}
2843
2844#[derive(Debug, Serialize, JsonSchema)]
2845pub struct BridgePullSchema {
2846 pub output_kind: Option<String>,
2847 pub action: Option<String>,
2848 pub status: Option<String>,
2849 pub success: Option<bool>,
2850 pub pulled: bool,
2851 pub changed: Option<bool>,
2852 pub transport: Option<String>,
2853 pub remote: String,
2854}
2855
2856#[derive(Debug, Serialize, JsonSchema)]
2859pub struct StashMutationSchema {
2860 pub message: String,
2861 pub stash_index: Option<usize>,
2862}
2863
2864#[derive(Debug, Serialize, JsonSchema)]
2865pub struct StashListSchema {
2866 pub output_kind: String,
2867 pub stashes: Vec<StashListEntrySchema>,
2868}
2869
2870#[derive(Debug, Serialize, JsonSchema)]
2871pub struct StashListEntrySchema {
2872 pub index: usize,
2873 pub message: Option<String>,
2874 pub created_at: String,
2875}
2876
2877#[derive(Debug, Serialize, JsonSchema)]
2878pub struct StashShowSchema {
2879 pub output_kind: String,
2880 pub modified: Vec<String>,
2881 pub added: Vec<String>,
2882 pub deleted: Vec<String>,
2883}
2884
2885#[derive(Debug, Serialize, JsonSchema)]
2886pub struct RevertSchema {
2887 pub output_kind: String,
2888 pub change_id: Option<String>,
2889 pub reverted_state: String,
2890 pub files_affected: Vec<String>,
2891 pub message: String,
2892}
2893
2894#[derive(Debug, Serialize, JsonSchema)]
2897pub struct DiagnoseSchema {
2898 pub output_kind: Option<String>,
2899 pub repository: String,
2900 pub repository_capability: String,
2901 pub storage_model: String,
2902 pub hosted_enabled: bool,
2903 pub git_overlay_import_hint: Option<GitOverlayImportHintSchema>,
2904 pub git_overlay_health: GitOverlayHealthSchema,
2905 #[serde(rename = "verification")]
2906 pub trust: RepositoryVerificationStateSchema,
2907 pub operation: OpaqueObject,
2908 pub remote_tracking: OpaqueObject,
2909 pub thread: Option<Value>,
2910 pub state: Option<Value>,
2911 pub changes: Value,
2912 pub workspace: Value,
2913 pub health: Value,
2914 pub recommended_action: Option<String>,
2915 pub recommended_action_template: Option<ActionTemplateSchema>,
2916 pub recovery_commands: Vec<String>,
2917 pub profile: Option<Value>,
2918}
2919
2920#[derive(Debug, Serialize, JsonSchema)]
2954pub struct ErrorEnvelopeSchema {
2955 pub error: String,
2956 pub exit_code: u8,
2957 pub hint: String,
2958 pub kind: String,
2959 pub op_id: Option<String>,
2960 pub idempotency_status: Option<String>,
2961 pub replayed: Option<bool>,
2962 pub unsafe_condition: String,
2963 pub would_change: String,
2964 pub preserved: String,
2965 pub primary_command: String,
2966 pub primary_command_template: NullableActionTemplateSchema,
2967 pub recovery_commands: Vec<String>,
2968 pub recovery_action_templates: Vec<ActionTemplateSchema>,
2969}
2970
2971#[derive(Debug, Serialize, JsonSchema)]
2972#[serde(untagged)]
2973#[allow(dead_code)]
2974pub enum NullableActionTemplateSchema {
2975 Template(ActionTemplateSchema),
2976 Null(()),
2977}
2978
2979#[derive(Debug, Serialize, JsonSchema)]
2980#[serde(untagged)]
2981#[allow(dead_code)]
2982pub enum NullableStringSchema {
2983 Value(String),
2984 Null(()),
2985}
2986
2987#[cfg(test)]
2992mod tests {
2993 use super::*;
2994
2995 fn required_fields(schema: &Value) -> Vec<&str> {
2996 schema
2997 .get("required")
2998 .and_then(|value| value.as_array())
2999 .expect("schema has required fields")
3000 .iter()
3001 .map(|value| value.as_str().expect("required field is a string"))
3002 .collect()
3003 }
3004
3005 fn property_schema<'a>(schema: &'a Value, property: &str) -> &'a Value {
3006 schema
3007 .get("properties")
3008 .and_then(|p| p.as_object())
3009 .and_then(|properties| properties.get(property))
3010 .unwrap_or_else(|| panic!("schema has `{property}` property"))
3011 }
3012
3013 fn resolve_schema_ref<'a>(root: &'a Value, reference: &str) -> &'a Value {
3014 reference
3015 .strip_prefix("#/$defs/")
3016 .or_else(|| reference.strip_prefix("#/definitions/"))
3017 .and_then(|name| {
3018 root.get("$defs")
3019 .or_else(|| root.get("definitions"))
3020 .and_then(|defs| defs.get(name))
3021 })
3022 .unwrap_or_else(|| panic!("schema reference `{reference}` resolves"))
3023 }
3024
3025 fn schema_declares_property(root: &Value, schema: &Value, property: &str) -> bool {
3026 if let Some(reference) = schema.get("$ref").and_then(|value| value.as_str()) {
3027 return schema_declares_property(root, resolve_schema_ref(root, reference), property);
3028 }
3029
3030 if schema
3031 .get("properties")
3032 .and_then(|properties| properties.get(property))
3033 .is_some()
3034 {
3035 return true;
3036 }
3037
3038 for combinator in ["anyOf", "oneOf"] {
3039 if let Some(schemas) = schema.get(combinator).and_then(|value| value.as_array()) {
3040 return !schemas.is_empty()
3041 && schemas
3042 .iter()
3043 .all(|schema| schema_declares_property(root, schema, property));
3044 }
3045 }
3046
3047 schema
3048 .get("allOf")
3049 .and_then(|value| value.as_array())
3050 .is_some_and(|schemas| {
3051 schemas
3052 .iter()
3053 .any(|schema| schema_declares_property(root, schema, property))
3054 })
3055 }
3056
3057 fn schema_allows_null(root: &Value, schema: &Value) -> bool {
3058 if let Some(reference) = schema.get("$ref").and_then(|value| value.as_str()) {
3059 return schema_allows_null(root, resolve_schema_ref(root, reference));
3060 }
3061
3062 if schema.get("type") == Some(&Value::String("null".to_string())) {
3063 return true;
3064 }
3065 if schema
3066 .get("type")
3067 .and_then(|value| value.as_array())
3068 .is_some_and(|types| types.contains(&Value::String("null".to_string())))
3069 {
3070 return true;
3071 }
3072
3073 ["anyOf", "oneOf", "allOf"].iter().any(|combinator| {
3074 schema
3075 .get(*combinator)
3076 .and_then(|value| value.as_array())
3077 .is_some_and(|schemas| {
3078 schemas
3079 .iter()
3080 .any(|schema| schema_allows_null(root, schema))
3081 })
3082 })
3083 }
3084
3085 fn collect_string_enums<'a>(root: &'a Value, schema: &'a Value, values: &mut Vec<&'a str>) {
3086 if let Some(reference) = schema.get("$ref").and_then(|value| value.as_str()) {
3087 collect_string_enums(root, resolve_schema_ref(root, reference), values);
3088 }
3089
3090 if let Some(enum_values) = schema.get("enum").and_then(|value| value.as_array()) {
3091 for value in enum_values {
3092 if let Some(value) = value.as_str() {
3093 values.push(value);
3094 }
3095 }
3096 }
3097
3098 for combinator in ["anyOf", "oneOf", "allOf"] {
3099 if let Some(schemas) = schema.get(combinator).and_then(|value| value.as_array()) {
3100 for schema in schemas {
3101 collect_string_enums(root, schema, values);
3102 }
3103 }
3104 }
3105 }
3106
3107 fn collect_discriminator_values<'a>(
3108 root: &'a Value,
3109 schema: &'a Value,
3110 field: &str,
3111 values: &mut Vec<&'a str>,
3112 ) {
3113 if let Some(reference) = schema.get("$ref").and_then(|value| value.as_str()) {
3114 collect_discriminator_values(root, resolve_schema_ref(root, reference), field, values);
3115 return;
3116 }
3117
3118 if let Some(property) = schema
3119 .get("properties")
3120 .and_then(|properties| properties.get(field))
3121 {
3122 collect_string_enums(root, property, values);
3123 }
3124
3125 for combinator in ["anyOf", "oneOf", "allOf"] {
3126 if let Some(schemas) = schema.get(combinator).and_then(|value| value.as_array()) {
3127 for schema in schemas {
3128 collect_discriminator_values(root, schema, field, values);
3129 }
3130 }
3131 }
3132 }
3133
3134 fn schema_requires_discriminator(root: &Value, schema: &Value, field: &str) -> bool {
3135 if let Some(reference) = schema.get("$ref").and_then(|value| value.as_str()) {
3136 return schema_requires_discriminator(root, resolve_schema_ref(root, reference), field);
3137 }
3138
3139 if schema
3140 .get("properties")
3141 .and_then(|properties| properties.get(field))
3142 .is_some()
3143 {
3144 return schema
3145 .get("required")
3146 .and_then(|value| value.as_array())
3147 .is_some_and(|required| {
3148 required
3149 .iter()
3150 .any(|required_field| required_field.as_str() == Some(field))
3151 });
3152 }
3153
3154 for combinator in ["anyOf", "oneOf"] {
3155 if let Some(schemas) = schema.get(combinator).and_then(|value| value.as_array()) {
3156 return !schemas.is_empty()
3157 && schemas
3158 .iter()
3159 .all(|schema| schema_requires_discriminator(root, schema, field));
3160 }
3161 }
3162
3163 schema
3164 .get("allOf")
3165 .and_then(|value| value.as_array())
3166 .is_some_and(|schemas| {
3167 schemas
3168 .iter()
3169 .any(|schema| schema_requires_discriminator(root, schema, field))
3170 })
3171 }
3172
3173 #[test]
3178 fn registry_covers_every_listed_verb() {
3179 for verb in schema_verbs() {
3180 assert!(
3181 schema_for_verb(verb).is_some(),
3182 "verb '{verb}' is advertised by command contracts but schema_for_verb returned None"
3183 );
3184 }
3185 }
3186
3187 #[test]
3188 fn documented_registry_is_subset_of_runtime_registry() {
3189 let all = schema_verbs();
3190 for verb in documented_schema_verbs() {
3191 assert!(
3192 all.contains(verb),
3193 "documented schema verb '{verb}' is not advertised as a runtime schema"
3194 );
3195 }
3196 }
3197
3198 #[test]
3213 fn documented_swept_schema_structs_declare_output_kind() {
3214 let mut missing = Vec::new();
3215 for verb in documented_schema_verbs() {
3216 if opaque_schema_verbs().contains(verb) {
3220 continue;
3221 }
3222 let Some(discriminator) =
3223 command_catalog::command_json_discriminator_for_schema_verb(verb)
3224 else {
3225 continue;
3226 };
3227 if discriminator.field != "output_kind" {
3228 continue;
3229 }
3230 let bare = schema_for_registered_verb(verb)
3231 .unwrap_or_else(|| panic!("documented verb `{verb}` has no registered schema"));
3232 let declares = schema_declares_property(&bare, &bare, "output_kind");
3233 if !declares {
3234 missing.push(format!(
3235 "{verb}: catalog advertises output_kind=`{}` but the schema struct declares no `output_kind` property",
3236 discriminator.value
3237 ));
3238 }
3239 }
3240 assert!(
3241 missing.is_empty(),
3242 "Documented swept schema structs missing the `output_kind` property. Add \
3243 `pub output_kind: String` to each mirror struct so it matches the runtime \
3244 emission (the catalog injection masks this at the `heddle schemas` layer, \
3245 but the struct must be honest):\n - {}",
3246 missing.join("\n - ")
3247 );
3248 }
3249
3250 #[test]
3251 fn implementation_registry_matches_command_contract_registry() {
3252 let advertised = schema_verbs();
3253 let mut implemented = schema_implementation_verbs();
3254 for verb in opaque_schema_verbs() {
3255 if !implemented.contains(verb) {
3256 implemented.push(*verb);
3257 }
3258 assert!(
3259 advertised.contains(verb),
3260 "opaque schema verb '{verb}' must also be advertised by active command contracts"
3261 );
3262 }
3263 for verb in advertised {
3264 assert!(
3265 implemented.contains(verb),
3266 "verb '{verb}' is advertised by command contracts but the schema implementation registry does not handle it"
3267 );
3268 }
3269 for verb in &implemented {
3270 if cfg!(all(feature = "git-overlay", feature = "semantic")) {
3271 assert!(
3272 advertised.contains(verb),
3273 "verb '{verb}' has a schema implementation but is not advertised by active command contracts"
3274 );
3275 } else if !advertised.contains(verb) {
3276 assert!(
3277 schema_for_verb(verb).is_none(),
3278 "inactive schema implementation '{verb}' must not be publicly resolvable"
3279 );
3280 }
3281 }
3282 }
3283
3284 #[test]
3285 fn command_catalog_schema_verbs_match_schema_list_except_error_envelope() {
3286 let catalog = command_catalog::build_command_catalog();
3287 let mut catalog_verbs = catalog
3288 .commands
3289 .iter()
3290 .flat_map(|command| command.schema_verbs.iter().map(String::as_str))
3291 .collect::<Vec<_>>();
3292 catalog_verbs.sort_unstable();
3293 catalog_verbs.dedup();
3294
3295 let mut listed_verbs = schema_verbs().to_vec();
3296 listed_verbs.sort_unstable();
3297 listed_verbs.retain(|verb| *verb != "error");
3298
3299 assert_eq!(
3300 catalog_verbs, listed_verbs,
3301 "`heddle help --output json` command schema verbs must match `heddle schemas` except for the cross-cutting JSON error envelope"
3302 );
3303 }
3304
3305 #[cfg(not(feature = "git-overlay"))]
3306 #[test]
3307 fn native_only_schema_registry_excludes_git_overlay_verbs() {
3308 let catalog = command_catalog::build_command_catalog();
3309 for verb in [
3310 "bridge git status",
3311 "bridge git init",
3312 "bridge git import",
3313 "bridge git export",
3314 "bridge git sync",
3315 "bridge git reconcile",
3316 "bridge git push",
3317 "bridge git pull",
3318 "bridge git reason",
3319 "git-overlay",
3320 ] {
3321 assert!(
3322 !schema_verbs().contains(&verb),
3323 "native-only schema listing must not advertise git-overlay verb `{verb}`"
3324 );
3325 assert!(
3326 !documented_schema_verbs().contains(&verb),
3327 "native-only documented schema listing must not advertise git-overlay verb `{verb}`"
3328 );
3329 assert!(
3330 schema_for_verb(verb).is_none(),
3331 "native-only schema lookup must reject git-overlay verb `{verb}`"
3332 );
3333 assert!(
3334 catalog.commands.iter().all(|command| {
3335 !command
3336 .schema_verbs
3337 .iter()
3338 .any(|schema_verb| schema_verb == verb)
3339 && !command
3340 .documented_schema_verbs
3341 .iter()
3342 .any(|schema_verb| schema_verb == verb)
3343 }),
3344 "native-only command catalog must not advertise git-overlay schema verb `{verb}`"
3345 );
3346 }
3347 }
3348
3349 #[test]
3350 fn unknown_verb_returns_none() {
3351 assert!(schema_for_verb("nope").is_none());
3352 }
3353
3354 #[test]
3355 fn status_schema_has_expected_top_level_properties() {
3356 let schema = schema_for_verb("status").expect("status schema");
3357 let properties = schema
3358 .get("properties")
3359 .and_then(|p| p.as_object())
3360 .expect("status schema has properties");
3361 for required in &[
3362 "repository_capability",
3363 "storage_model",
3364 "hosted_enabled",
3365 "thread",
3366 "current_state",
3367 "actor",
3368 "blockers",
3369 "changes",
3370 ] {
3371 assert!(
3372 properties.contains_key(*required),
3373 "status schema missing property '{required}'"
3374 );
3375 }
3376 }
3377
3378 #[test]
3379 fn action_template_agent_may_fill_schema_describes_false_semantics() {
3380 let schema = schema_for_verb("verify").expect("verify schema");
3381 let action_template = schema
3382 .get("$defs")
3383 .or_else(|| schema.get("definitions"))
3384 .and_then(|defs| defs.get("ActionTemplateSchema"))
3385 .expect("verify schema includes ActionTemplateSchema definition");
3386 let description = property_schema(action_template, "agent_may_fill")
3387 .get("description")
3388 .and_then(Value::as_str)
3389 .expect("agent_may_fill schema description is present");
3390
3391 assert!(
3392 description.contains("When `agent_may_fill` is false"),
3393 "agent_may_fill schema description must document false semantics: {description}"
3394 );
3395 assert!(
3396 description.contains("display-only"),
3397 "agent_may_fill schema description must warn agents not to execute display-only templates: {description}"
3398 );
3399 assert!(
3400 description.contains("do not substitute `<name>`/`<url>` placeholders"),
3401 "agent_may_fill schema description must prohibit placeholder substitution when false: {description}"
3402 );
3403 }
3404
3405 #[test]
3418 fn action_fields_follow_presence_contract_in_every_schema() {
3419 fn walk(root: &Value, schema: &Value, verb: &str, path: &str) {
3420 match schema {
3421 Value::Object(object) => {
3422 if let Some(properties) = object.get("properties").and_then(|p| p.as_object()) {
3423 let required: Vec<&str> = object
3424 .get("required")
3425 .and_then(|value| value.as_array())
3426 .map(|fields| {
3427 fields.iter().filter_map(|field| field.as_str()).collect()
3428 })
3429 .unwrap_or_default();
3430 for (name, child) in properties {
3431 if matches!(name.as_str(), "next_action" | "recommended_action")
3432 && required.contains(&name.as_str())
3433 {
3434 assert!(
3435 schema_allows_null(root, child),
3436 "`{verb}` schema requires `{path}.{name}` without allowing \
3437 null; the action contract is null = no action, absent = \
3438 not applicable, never \"\": {child}"
3439 );
3440 }
3441 }
3442 }
3443 for (key, child) in object {
3444 walk(root, child, verb, &format!("{path}.{key}"));
3445 }
3446 }
3447 Value::Array(items) => {
3448 for (index, child) in items.iter().enumerate() {
3449 walk(root, child, verb, &format!("{path}[{index}]"));
3450 }
3451 }
3452 _ => {}
3453 }
3454 }
3455
3456 for verb in schema_verbs() {
3457 let schema =
3458 schema_for_verb(verb).unwrap_or_else(|| panic!("schema registered for `{verb}`"));
3459 walk(&schema, &schema, verb, "$");
3460 }
3461 }
3462
3463 #[test]
3464 fn status_schema_allows_null_recommended_action() {
3465 let schema = schema_for_verb("status").expect("status schema");
3466 let recommended_action = property_schema(&schema, "recommended_action");
3467 assert!(
3468 schema_allows_null(&schema, recommended_action),
3469 "status recommended_action must allow null because empty actions serialize as null: {recommended_action}"
3470 );
3471
3472 let required = required_fields(&schema);
3473 assert!(
3474 required.contains(&"recommended_action"),
3475 "status recommended_action should remain a stable emitted field: {schema}"
3476 );
3477 }
3478
3479 #[test]
3480 fn status_agent_context_fields_are_omittable() {
3481 let schema = schema_for_verb("status").expect("status schema");
3482 let required = required_fields(&schema);
3483 for field in [
3484 "path",
3485 "execution_path",
3486 "session_id",
3487 "heddle_session_id",
3488 "actor",
3489 "harness",
3490 "thinking_level",
3491 "usage_summary",
3492 "last_progress_at",
3493 "report_flush_state",
3494 "attach_reason",
3495 "target_thread",
3496 "parent_thread",
3497 "task",
3498 ] {
3499 assert!(
3500 !required.contains(&field),
3501 "status `{field}` is omitted when no agent/materialized context is recorded: {schema}"
3502 );
3503 }
3504 }
3505
3506 #[test]
3507 fn status_thread_mode_schema_matches_observed_modes() {
3508 let schema = schema_for_verb("status").expect("status schema");
3509 let mut values = Vec::new();
3510 collect_string_enums(
3511 &schema,
3512 property_schema(&schema, "thread_mode"),
3513 &mut values,
3514 );
3515
3516 for expected in ["materialized", "virtualized", "solid"] {
3517 assert!(
3518 values.contains(&expected),
3519 "status thread_mode schema missing observed mode `{expected}`: {values:?}"
3520 );
3521 }
3522 assert!(
3523 !values.contains(&"lightweight"),
3524 "status thread_mode schema must not advertise removed mode `lightweight`: {values:?}"
3525 );
3526 }
3527
3528 #[test]
3529 fn ready_schema_requires_stable_operator_and_readiness_fields() {
3530 let schema = schema_for_verb("ready").expect("ready schema");
3531 let properties = schema
3532 .get("properties")
3533 .and_then(|p| p.as_object())
3534 .expect("ready schema has properties");
3535 assert!(
3536 properties.contains_key("blockers"),
3537 "ready schema should still document blockers when emitted"
3538 );
3539 assert!(
3540 properties.contains_key("warnings"),
3541 "ready schema should still document warnings when emitted"
3542 );
3543 assert!(
3544 properties.contains_key("readiness"),
3545 "ready schema should document the stable readiness summary"
3546 );
3547 assert!(
3548 properties.contains_key("verification"),
3549 "ready schema should document the repository verification proof"
3550 );
3551
3552 let required = required_fields(&schema);
3553 for stable_field in ["blockers", "warnings", "readiness", "verification"] {
3554 assert!(
3555 required.contains(&stable_field),
3556 "ready schema must require `{stable_field}` because ready JSON always emits the stable field set: {schema}"
3557 );
3558 }
3559 assert!(
3560 properties.contains_key("captured_state"),
3561 "ready schema should document captured_state even though schemars models nullable Option fields as optional"
3562 );
3563 }
3564
3565 #[test]
3566 fn push_schema_requires_stable_runtime_fields() {
3567 let schema = schema_for_verb("push").expect("push schema");
3568 let required = required_fields(&schema);
3569 for stable_field in [
3570 "output_kind",
3571 "action",
3572 "status",
3573 "pushed",
3574 "changed",
3575 "success",
3576 "transport",
3577 "next_action",
3578 "next_action_template",
3579 "recommended_action",
3580 "recommended_action_template",
3581 ] {
3582 assert!(
3583 required.contains(&stable_field),
3584 "push schema must require stable emitted field `{stable_field}`: {schema}"
3585 );
3586 }
3587
3588 for skipped_when_none in [
3589 "remote",
3590 "push_scope",
3591 "ref_scope",
3592 "git_notes_ref",
3593 "git_notes_visibility_warning",
3594 "git_tracking_remote",
3595 "git_remote_configured",
3596 "git_upstream_configured",
3597 "tags_included",
3598 "force",
3599 "force_discard_warning",
3600 "thread",
3601 "state",
3602 "objects",
3603 ] {
3604 assert!(
3605 !required.contains(&skipped_when_none),
3606 "push schema must not require conditionally omitted field `{skipped_when_none}`: {schema}"
3607 );
3608 }
3609 }
3610
3611 #[test]
3612 fn advertised_json_discriminators_are_reflected_in_schemas() {
3613 use std::collections::{BTreeMap, BTreeSet};
3614
3615 for schema_verb in schema_verbs() {
3616 let mut discriminators =
3617 command_catalog::command_json_discriminators_for_schema_verb(schema_verb);
3618 if discriminators.is_empty() {
3619 continue;
3620 };
3621 let schema =
3622 schema_for_verb(schema_verb).unwrap_or_else(|| panic!("{schema_verb} schema"));
3623 if schema.get("anyOf").is_some() {
3624 for sibling in command_catalog::sibling_documented_schema_verbs(schema_verb) {
3629 discriminators.extend(
3630 command_catalog::command_json_discriminators_for_schema_verb(sibling),
3631 );
3632 }
3633 for discriminator in command_catalog::command_json_discriminators()
3634 .into_iter()
3635 .filter(|discriminator| {
3636 discriminator.display == *schema_verb
3637 && discriminator.schema_verb.as_deref() != Some(schema_verb)
3638 })
3639 {
3640 discriminators.push(discriminator);
3641 }
3642 }
3643
3644 let mut expected_by_field = BTreeMap::<String, BTreeSet<String>>::new();
3645 for discriminator in discriminators {
3646 expected_by_field
3647 .entry(discriminator.field)
3648 .or_default()
3649 .insert(discriminator.value);
3650 }
3651
3652 for (field, expected) in expected_by_field {
3653 let mut actual = Vec::new();
3654 collect_discriminator_values(&schema, &schema, &field, &mut actual);
3655 let actual = actual
3656 .into_iter()
3657 .map(str::to_string)
3658 .collect::<BTreeSet<_>>();
3659 assert_eq!(
3660 actual, expected,
3661 "{schema_verb} schema must narrow `{field}` to every catalog-advertised value"
3662 );
3663 assert!(
3664 schema_requires_discriminator(&schema, &schema, &field),
3665 "{schema_verb} schema must require discriminator field `{field}`"
3666 );
3667 }
3668 }
3669 }
3670
3671 #[test]
3672 fn oss_recovery_surfaces_do_not_use_opaque_generic_schema() {
3673 for verb in [
3674 "fsck",
3675 "resolve",
3676 "retro",
3677 "discuss open",
3678 "discuss append",
3679 "discuss resolve",
3680 "discuss list",
3681 "discuss show",
3682 "query",
3683 "query --attribution",
3684 ] {
3685 assert!(
3686 !opaque_schema_verbs().contains(&verb),
3687 "`{verb}` should have a concrete machine-contract schema, not the opaque generic object"
3688 );
3689 let schema = schema_for_verb(verb).unwrap_or_else(|| panic!("{verb} schema exists"));
3690 assert_ne!(
3691 schema.get("additionalProperties"),
3692 Some(&Value::Bool(true)),
3693 "`{verb}` schema should not accept arbitrary top-level fields"
3694 );
3695 }
3696 }
3697
3698 #[test]
3699 fn commit_schema_declares_op_id_replay_fields() {
3700 let schema = schema_for_verb("commit").expect("commit schema");
3701 let properties = schema
3702 .get("properties")
3703 .and_then(|p| p.as_object())
3704 .expect("commit schema has properties");
3705 for required in OP_ID_REPLAY_FIELD_NAMES {
3706 assert!(
3707 properties.contains_key(*required),
3708 "commit schema missing op-id replay property '{required}'"
3709 );
3710 }
3711 }
3712
3713 #[test]
3714 fn op_id_supported_schema_verbs_declare_replay_fields() {
3715 let mut checked = 0;
3716 for verb in schema_verbs() {
3717 if !schema_verb_supports_op_id(verb) {
3718 continue;
3719 }
3720 checked += 1;
3721 let schema =
3722 schema_for_verb(verb).unwrap_or_else(|| panic!("schema for `{verb}` exists"));
3723 let properties = schema
3724 .get("properties")
3725 .and_then(|p| p.as_object())
3726 .unwrap_or_else(|| panic!("schema for `{verb}` should expose properties"));
3727 for required in OP_ID_REPLAY_FIELD_NAMES {
3728 assert!(
3729 properties.contains_key(*required),
3730 "schema for op-id-supported verb `{verb}` missing replay property `{required}`"
3731 );
3732 }
3733 }
3734 assert!(
3735 checked > 1,
3736 "op-id schema coverage test should exercise more than commit"
3737 );
3738 }
3739
3740 #[test]
3741 fn log_schema_has_states_array() {
3742 let schema = schema_for_verb("log").expect("log schema");
3743 let properties = schema
3744 .get("properties")
3745 .and_then(|p| p.as_object())
3746 .unwrap();
3747 assert!(properties.contains_key("states"));
3748 assert!(properties.contains_key("repository_capability"));
3749 }
3750}