Skip to content

Commit d6837ad

Browse files
Merge pull request #142 from codeflash-ai/fix/calendar-event-filenames
Fix Google Calendar filenames for long event ids
2 parents 989dd2e + 7b82b71 commit d6837ad

3 files changed

Lines changed: 174 additions & 3 deletions

File tree

crates/locality-google-calendar/src/connector.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use locality_core::model::{
1717
};
1818
use locality_core::planner::{PropertyValue, PushOperation, PushOperationKind};
1919
use locality_core::search::{RAW_SEARCH_METADATA_KEY, SearchMetadata};
20+
use locality_core::shadow::stable_hash;
2021
use locality_core::validation::ValidationIssue;
2122
use locality_core::{LocalityError, LocalityResult};
2223
use serde::{Deserialize, Serialize};
@@ -38,6 +39,8 @@ const LOCAL_DRAFT_REMOTE_ID: &str = "google-calendar-draft:local";
3839
const GOOGLE_CALENDAR_DRAFT_NATIVE_KIND: &str = "google_calendar_draft";
3940
const LOCAL_DRAFT_EVENT_ID: &str = "locality-google-calendar-draft-local";
4041
const LOCAL_DRAFT_CONFERENCE_REQUEST_ID: &str = "locality-google-calendar-conference-local";
42+
const EVENT_FILENAME_SLUG_MAX_LEN: usize = 96;
43+
const EVENT_FILENAME_HASH_LEN: usize = 16;
4144

4245
#[derive(Clone, PartialEq, Eq)]
4346
pub struct GoogleCalendarConfig {
@@ -1119,7 +1122,23 @@ fn safe_slug(value: &str) -> String {
11191122
if slug.is_empty() {
11201123
"untitled".to_string()
11211124
} else {
1122-
slug.to_string()
1125+
bound_slug(slug, value)
1126+
}
1127+
}
1128+
1129+
fn bound_slug(slug: &str, source: &str) -> String {
1130+
if slug.len() <= EVENT_FILENAME_SLUG_MAX_LEN {
1131+
return slug.to_string();
1132+
}
1133+
1134+
let hash = stable_hash(source);
1135+
let hash = &hash[..EVENT_FILENAME_HASH_LEN];
1136+
let prefix_len = EVENT_FILENAME_SLUG_MAX_LEN.saturating_sub(hash.len() + 1);
1137+
let prefix = slug[..prefix_len].trim_matches('-');
1138+
if prefix.is_empty() {
1139+
hash.to_string()
1140+
} else {
1141+
format!("{prefix}-{hash}")
11231142
}
11241143
}
11251144

@@ -1542,6 +1561,29 @@ google_calendar:
15421561
);
15431562
}
15441563

1564+
#[test]
1565+
fn event_filename_bounds_long_event_ids_to_filesystem_component_limit() {
1566+
let long_event_id = format!("loc{}", "a".repeat(1024));
1567+
let filename = super::event_filename(&event_fixture("event-1"), &long_event_id);
1568+
let event_id_hash = locality_core::shadow::stable_hash(&long_event_id);
1569+
1570+
assert!(
1571+
filename.len() <= 255,
1572+
"filename component must fit common filesystem limits: {}",
1573+
filename.len()
1574+
);
1575+
assert!(filename.starts_with("20260720-100000-design-review-"));
1576+
assert!(filename.ends_with(".md"));
1577+
assert!(
1578+
filename.len() < long_event_id.len(),
1579+
"long event ids should be shortened in filenames"
1580+
);
1581+
assert!(
1582+
filename.contains(&event_id_hash[..16]),
1583+
"shortened event ids should keep a stable hash suffix"
1584+
);
1585+
}
1586+
15451587
#[test]
15461588
fn apply_create_entity_inserts_primary_event_with_send_updates_and_meet() {
15471589
let api = Arc::new(FakeGoogleCalendarApi::default());

crates/localityd/src/push.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use locality_core::push::{
3939
};
4040
use locality_core::shadow::{
4141
MarkdownBlockKind, ShadowBlock, ShadowDocument, rendered_bodies_equivalent,
42-
segment_markdown_body,
42+
segment_markdown_body, stable_hash,
4343
};
4444
use locality_core::validation::{ValidationIssue, ValidationReport};
4545
use locality_core::{LocalityError, LocalityResult};
@@ -82,6 +82,9 @@ use crate::virtual_fs::{
8282
virtual_mutation_content_path_for_read,
8383
};
8484

85+
const CREATED_ENTITY_FILENAME_SLUG_MAX_LEN: usize = 96;
86+
const CREATED_ENTITY_FILENAME_HASH_LEN: usize = 16;
87+
8588
pub fn execute_push_job<S, Source>(
8689
store: &mut S,
8790
job: PushJob,
@@ -4244,7 +4247,23 @@ fn created_entity_safe_slug(value: &str) -> String {
42444247
if slug.is_empty() {
42454248
"untitled".to_string()
42464249
} else {
4247-
slug.to_string()
4250+
bound_created_entity_slug(slug, value)
4251+
}
4252+
}
4253+
4254+
fn bound_created_entity_slug(slug: &str, source: &str) -> String {
4255+
if slug.len() <= CREATED_ENTITY_FILENAME_SLUG_MAX_LEN {
4256+
return slug.to_string();
4257+
}
4258+
4259+
let hash = stable_hash(source);
4260+
let hash = &hash[..CREATED_ENTITY_FILENAME_HASH_LEN];
4261+
let prefix_len = CREATED_ENTITY_FILENAME_SLUG_MAX_LEN.saturating_sub(hash.len() + 1);
4262+
let prefix = slug[..prefix_len].trim_matches('-');
4263+
if prefix.is_empty() {
4264+
hash.to_string()
4265+
} else {
4266+
format!("{prefix}-{hash}")
42484267
}
42494268
}
42504269

crates/localityd/tests/push_execution.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,6 +1256,116 @@ fn daemon_push_reconciles_google_calendar_draft_create_to_canonical_event_filena
12561256
);
12571257
}
12581258

1259+
#[test]
1260+
fn daemon_push_reconciles_long_google_calendar_event_id_to_filesystem_safe_filename() {
1261+
let fixture = PushFixture::new();
1262+
let state_root = fixture.root.join(".state");
1263+
let source_path = Path::new("draft/design-review.md");
1264+
let content_root = virtual_fs_content_root(&state_root, &fixture.mount_id);
1265+
let cache_path =
1266+
virtual_fs_content_path(&state_root, &fixture.mount_id, source_path).expect("cache path");
1267+
fs::create_dir_all(cache_path.parent().expect("cache parent")).expect("cache parent");
1268+
fs::write(
1269+
&cache_path,
1270+
"---\ntitle: Design review\nsummary: Design review\nstart:\n dateTime: \"2026-07-20T10:00:00-07:00\"\nend:\n dateTime: \"2026-07-20T10:30:00-07:00\"\n---\nAgenda\n",
1271+
)
1272+
.expect("cache file");
1273+
1274+
let draft_folder_id = RemoteId::new("google-calendar-folder:draft");
1275+
let events_folder_id = RemoteId::new("google-calendar-folder:events");
1276+
let long_event_id = format!("loc{}", "a".repeat(1024));
1277+
let event_id_hash = locality_core::shadow::stable_hash(&long_event_id);
1278+
let created_remote_id = RemoteId::new(format!("google-calendar-event:primary:{long_event_id}"));
1279+
let mut store = InMemoryStateStore::new();
1280+
store
1281+
.save_mount(
1282+
MountConfig::new(fixture.mount_id.clone(), "google-calendar", &fixture.root)
1283+
.projection(ProjectionMode::LinuxFuse),
1284+
)
1285+
.expect("save mount");
1286+
store
1287+
.save_entity(EntityRecord::new(
1288+
fixture.mount_id.clone(),
1289+
draft_folder_id.clone(),
1290+
EntityKind::Directory,
1291+
"draft",
1292+
"draft",
1293+
))
1294+
.expect("save draft folder");
1295+
store
1296+
.save_entity(EntityRecord::new(
1297+
fixture.mount_id.clone(),
1298+
events_folder_id.clone(),
1299+
EntityKind::Directory,
1300+
"events",
1301+
"events",
1302+
))
1303+
.expect("save events folder");
1304+
store
1305+
.save_virtual_mutation(virtual_mutation(
1306+
&fixture.mount_id,
1307+
"local:calendar-draft",
1308+
VirtualMutationKind::Create,
1309+
None,
1310+
Some(draft_folder_id),
1311+
"draft/design-review.md",
1312+
Some(cache_path),
1313+
))
1314+
.expect("save mutation");
1315+
let source = FakePushSource::default()
1316+
.with_created_entity(
1317+
created_remote_id.clone(),
1318+
rendered_google_calendar_entity(
1319+
created_remote_id.as_str(),
1320+
"Design review",
1321+
"2026-07-20T10:00:00-07:00",
1322+
"Agenda",
1323+
),
1324+
)
1325+
.with_apply_effects(vec![JournalApplyEffect::CreatedEntity {
1326+
operation_id: PushOperationId("create-calendar-draft".to_string()),
1327+
operation_index: 0,
1328+
parent_id: events_folder_id,
1329+
entity_id: created_remote_id.clone(),
1330+
}]);
1331+
1332+
let report = execute_push_job_with_content_root(
1333+
&mut store,
1334+
PushJob {
1335+
target_path: fixture.root.join(source_path),
1336+
assume_yes: true,
1337+
confirm_dangerous: false,
1338+
},
1339+
&source,
1340+
Some(&state_root),
1341+
)
1342+
.expect("push google calendar draft with long event id");
1343+
1344+
assert_eq!(report.action, PushJobAction::Reconciled);
1345+
let event = store
1346+
.get_entity(&fixture.mount_id, &created_remote_id)
1347+
.expect("get created event")
1348+
.expect("created event entity");
1349+
let filename = event
1350+
.path
1351+
.file_name()
1352+
.expect("event filename")
1353+
.to_string_lossy();
1354+
assert!(
1355+
filename.len() <= 255,
1356+
"filename component must fit common filesystem limits: {}",
1357+
filename.len()
1358+
);
1359+
assert!(filename.starts_with("20260720-100000-design-review-"));
1360+
assert!(filename.ends_with(".md"));
1361+
assert!(
1362+
filename.contains(&event_id_hash[..16]),
1363+
"shortened event ids should keep a stable hash suffix"
1364+
);
1365+
assert!(content_root.join(&event.path).exists());
1366+
assert!(!content_root.join(source_path).exists());
1367+
}
1368+
12591369
#[test]
12601370
fn daemon_push_accepts_google_calendar_summary_only_draft_create() {
12611371
let fixture = PushFixture::new();

0 commit comments

Comments
 (0)