From dbdccc50c7d5ccbc48580d2938771cfcc2366bd9 Mon Sep 17 00:00:00 2001 From: Hare Date: Mon, 8 Jun 2026 08:22:01 +0900 Subject: [PATCH] ticket: quote frontmatter strings conservatively --- crates/ticket/src/lib.rs | 60 ++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/crates/ticket/src/lib.rs b/crates/ticket/src/lib.rs index f3b18f96..918f0e18 100644 --- a/crates/ticket/src/lib.rs +++ b/crates/ticket/src/lib.rs @@ -1803,27 +1803,6 @@ fn ticket_meta(frontmatter: TicketItemFrontmatter) -> TicketMeta { } fn format_yaml_string_scalar(value: &str) -> String { - let reserved = matches!( - value, - "" | "null" - | "Null" - | "NULL" - | "~" - | "true" - | "True" - | "TRUE" - | "false" - | "False" - | "FALSE" - ); - let plain_safe = !reserved - && value - .chars() - .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '/' | '.')); - if plain_safe { - return value.to_string(); - } - let mut out = String::from("'"); for ch in value.chars() { if ch == '\'' { @@ -2514,6 +2493,45 @@ workflow_state: intake assert!(report.is_ok(), "{:?}", report.diagnostics); } + #[test] + fn create_round_trips_numeric_looking_string_frontmatter_values() { + let tmp = TempDir::new().unwrap(); + let backend = backend(&tmp); + let mut input = NewTicket::new("123"); + input.slug = Some("numeric-looking-strings".to_string()); + input.labels = vec!["123".into(), "01".into()]; + input.risk_flags = vec!["1".into(), "42".into()]; + input.assignee = Some("42".into()); + input.attention_required = Some("0".into()); + input.action_required = Some("true".into()); + let ticket = backend.create(input).unwrap(); + + let record = backend.show(TicketIdOrSlug::Id(ticket.id.clone())).unwrap(); + assert_eq!(record.meta.title, "123"); + assert_eq!(record.meta.labels, vec!["123", "01"]); + assert_eq!(record.meta.risk_flags, vec!["1", "42"]); + assert_eq!(record.meta.assignee.as_deref(), Some("42")); + assert_eq!(record.meta.attention_required.as_deref(), Some("0")); + assert_eq!(record.meta.action_required.as_deref(), Some("true")); + + let item = fs::read_to_string( + tmp.path() + .join("tickets/open") + .join(&ticket.id) + .join("item.md"), + ) + .unwrap(); + assert!(item.contains("title: '123'"), "{item}"); + assert!(item.contains("labels: ['123', '01']"), "{item}"); + assert!(item.contains("risk_flags: ['1', '42']"), "{item}"); + assert!(item.contains("assignee: '42'"), "{item}"); + assert!(item.contains("attention_required: '0'"), "{item}"); + assert!(item.contains("action_required: 'true'"), "{item}"); + + let report = backend.doctor().unwrap(); + assert!(report.is_ok(), "{:?}", report.diagnostics); + } + #[test] fn add_event_review_status_and_close_preserve_local_layout() { let tmp = TempDir::new().unwrap();