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