Enhance audit documentation and address critical Ref property issues

- Updated the audit guide to clarify the workflow and prerequisites for conducting a production-grade sync feature audit.
- Introduced a new plan file detailing fixes for critical issues identified in the two-way sync audit, including missing instance maps and stale paths after renames.
- Enhanced `ServeSession.lua` to correctly pass the instance map in patch updates, ensuring accurate handling of unresolved Ref properties.
- Implemented various fixes in `change_processor.rs` and `rojo_ref.rs` to address critical bugs related to Ref path handling and instance renaming.
- Added tests to verify the correct behavior of Ref properties and ensure robustness against edge cases.

These changes improve the reliability of the synchronization system and enhance the clarity of the audit process.
This commit is contained in:
Joe 2026-02-14 12:53:28 -06:00
parent 8f0df692d3
commit ca2a938902
8 changed files with 811 additions and 90 deletions

View file

@ -593,6 +593,32 @@ impl ApiService {
// Log summary of any syncback issues
stats.log_summary();
// Build a path map for instances added in this same request.
// This allows Ref properties in updates to reference just-added
// instances that aren't in the tree yet (VFS watcher is async).
let added_paths: HashMap<Ref, String> = {
let tree = self.serve_session.tree();
request
.added
.iter()
.filter_map(|(guid, added)| {
let parent_ref = added.parent?;
let parent_path = if tree.get_instance(parent_ref).is_some() {
crate::ref_target_path(tree.inner(), parent_ref)
} else {
return None;
};
let escaped_name = crate::escape_ref_path_segment(&added.name);
let path = if parent_path.is_empty() {
escaped_name.into_owned()
} else {
format!("{}/{}", parent_path, escaped_name)
};
Some((*guid, path))
})
.collect()
};
// Persist non-Source property changes to disk (meta/model files).
// Source property changes are handled by ChangeProcessor when it
// receives the PatchSet below.
@ -620,7 +646,9 @@ impl ApiService {
}
// Write non-Source properties to meta/model files
if let Err(err) = self.syncback_updated_properties(update, &tree) {
if let Err(err) =
self.syncback_updated_properties(update, &tree, &added_paths)
{
log::warn!(
"Failed to persist non-Source properties for instance {:?}: {}",
update.id,
@ -2547,6 +2575,7 @@ impl ApiService {
&self,
update: &crate::web::interface::InstanceUpdate,
tree: &crate::snapshot::RojoTree,
added_paths: &HashMap<Ref, String>,
) -> anyhow::Result<()> {
use anyhow::Context;
@ -2629,6 +2658,20 @@ impl ApiService {
}
ref_attributes.insert(attr_name, serde_json::Value::String(path));
} else if let Some(path) = added_paths.get(target_ref) {
// Target was added in the same /api/write request.
// It's not in the tree yet (VFS watcher hasn't picked it up),
// but we can compute the path from the AddedInstance data.
log::info!(
"Ref property '{}' for instance {:?}: target {:?} is a \
just-added instance, using pre-computed path '{}'",
key,
update.id,
target_ref,
path
);
ref_attributes
.insert(attr_name, serde_json::Value::String(path.clone()));
} else {
log::warn!(
"Cannot persist Ref property '{}' for instance {:?}: \