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