- Introduced two new plan files: one detailing fixes for issues identified during the ambiguous-paths branch audit, including recursive matching, ref path formatting, and dedup cleanup improvements. - The second plan outlines follow-up actions required for production audit, addressing critical round-trip issues, metadata consistency, and necessary regression tests. - Documented todos for each plan to ensure clarity on implementation steps and testing requirements. These additions enhance the auditing process and provide a structured approach to resolving identified issues, improving overall project reliability and maintainability.
12 KiB
| name | overview | todos | isProject | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| audit ambiguous paths followup | Production audit of the ambiguous-paths branch against origin/master. Documents critical round-trip issues, approved fixes, accepted limitations, and required regression tests for implementation in a follow-up Agent-mode session. |
|
false |
Ambiguous Paths Audit Fix Plan
This plan was generated by
/auditfrom a Plan-mode session. Refer to.cursor/rules/atlas.mdcfor full project standards.
Standards
These standards govern every fix in this plan. An implementer MUST read .cursor/rules/atlas.mdc before starting.
Round-Trip Identity (from atlas.mdc §Quality Standard)
Syncback (or two-way sync) writes a directory tree. Building an rbxl from that directory tree and forward-syncing it back must produce a bit-identical instance tree -- same names, same classes, same properties, same hierarchy, same ref targets. Any deviation is a bug.
CLI Syncback Parity (from atlas.mdc §Two-Way Sync Strategy)
Plugin-based sync must produce exactly the same filesystem output that atlas syncback would give for the same input. Byte-for-byte identical files, identical directory structures, identical naming. Any divergence is a bug until proven otherwise.
Code Quality (from atlas.mdc §Code Quality Standard)
DRY code that is easy to reason about and hard to get wrong. Small helper refactors are in scope; major pipeline redesign is deferred.
Context
Audited branch ambiguous-paths against origin/master using commit range origin/master..HEAD.
Primary findings:
- Critical path drift in dedup cleanup rename flow (
[src/change_processor.rs](d:/UserGenerated/rojo/src/change_processor.rs)). - Critical same-batch Ref target path synthesis mismatch for added instances (
[src/web/api.rs](d:/UserGenerated/rojo/src/web/api.rs)). - Critical ref-path post-processing strategy relies on global text replacement (
[src/syncback/mod.rs](d:/UserGenerated/rojo/src/syncback/mod.rs),[src/syncback/fs_snapshot.rs](d:/UserGenerated/rojo/src/syncback/fs_snapshot.rs)). - Known limitation accepted for this release: duplicate-named children under ProjectNode currently skipped in
[src/snapshot_middleware/project.rs](d:/UserGenerated/rojo/src/snapshot_middleware/project.rs). - Known limitation accepted for this release: greedy matcher is not globally optimal (
[src/snapshot/matching.rs](d:/UserGenerated/rojo/src/snapshot/matching.rs),[src/syncback/matching.rs](d:/UserGenerated/rojo/src/syncback/matching.rs),[plugin/src/Reconciler/matching.lua](d:/UserGenerated/rojo/plugin/src/Reconciler/matching.lua)).
Fixes
Each fix below was approved by the user during the audit quiz. Implement in order.
Fix 1: Keep tree metadata consistent after dedup cleanup rename
- Status: Approved
- Finding: Step 2d / Step 8 (rename/delete interactions + VFS suppression)
- Files:
[src/change_processor.rs](d:/UserGenerated/rojo/src/change_processor.rs) - Problem: In dedup cleanup (
~1073-1203), the code renames files (fs::rename) and suppresses both old/new paths, but does not update the renamed sibling'sInstigatingSource::Pathor metadata path indexes. Subsequent writes can target stale paths. - Solution: After successful
DedupCleanupAction::RemoveSuffix/PromoteLowest, locate the sibling instance being renamed, update its metadata source path and relevant path indexes in-memory, and keep suppression counters consistent with the updated path. - Risk: Medium; touches tree mutation bookkeeping and rename lifecycle.
- Verify round-trip: Create duplicate siblings (
Foo,Foo~1), deleteFoo, ensureFoo~1 -> Foorename occurs, then issue a property update to the survivor in same session and verify disk write targets new path and survives rebuild. - Verify syncback parity: Compare output of two-way deletion flow to CLI syncback on equivalent tree state; both must produce the same remaining filename/meta layout.
- Tests required:
- Rust integration test in
[tests/tests/two_way_sync.rs](d:/UserGenerated/rojo/tests/tests/two_way_sync.rs)covering delete-triggered dedup cleanup followed by update. - Optional focused unit test around cleanup metadata path update helper in
[src/change_processor.rs](d:/UserGenerated/rojo/src/change_processor.rs).
- Rust integration test in
Fix 2: Build correct same-batch added Ref paths
- Status: Approved
- Finding: Step 2a / Step 10 (plugin->server wire format and write processing)
- Files:
[src/web/api.rs](d:/UserGenerated/rojo/src/web/api.rs) - Problem:
added_pathsmap (~622-649) uses only slugified instance name; it omits middleware extension and dedup suffix.syncback_updated_propertiesthen writes incorrectRojo_Ref_* when target is newly added in same request (~2694-2706). - Solution: Derive same-batch added target path using the same filesystem naming pipeline as syncback write (class/middleware extension + dedup suffix + directory-vs-file representation). Reuse/shared helper preferred to avoid divergence.
- Risk: Medium; affects same-request Ref persistence edge path.
- Verify round-trip: Send
/api/writewith add(target script/model) + update(ref property pointing to target) in one batch; verify persisted path includes correct segment (.server.luau,.model.json5, or dedup suffix) and resolves after rebuild. - Verify syncback parity: Run equivalent scenario via CLI syncback fixture and compare written
Rojo_Ref_* attribute string. - Tests required:
- Rust integration test in
[tests/tests/two_way_sync.rs](d:/UserGenerated/rojo/tests/tests/two_way_sync.rs)for same-batch add+ref on file-backed and directory-backed targets. - Edge test for dedup suffix target (e.g.,
Foo~1).
- Rust integration test in
Fix 3: Replace global ref-path text rewriting with attribute-scoped final paths
- Status: Approved
- Finding: Step 2a / Step 9 / Step 11h (round-trip identity, parity, determinism)
- Files:
[src/syncback/mod.rs](d:/UserGenerated/rojo/src/syncback/mod.rs),[src/syncback/ref_properties.rs](d:/UserGenerated/rojo/src/syncback/ref_properties.rs),[src/syncback/fs_snapshot.rs](d:/UserGenerated/rojo/src/syncback/fs_snapshot.rs),[src/syncback/snapshot.rs](d:/UserGenerated/rojo/src/syncback/snapshot.rs) - Problem: Syncback currently calls
collect_referents(..., None)(~177) then performs broad post-hoc string replacement (~614-637) viafix_ref_paths(~456-492) across entire meta/model file text. This is not attribute-scoped and can alter unrelated strings. - Solution: Feed definitive final paths into ref linking directly (e.g., pass populated
final_pathstocollect_referents) soRojo_Ref_* attributes are written correctly first time. Remove or strictly confine generic text replacement path. - Risk: Medium-high; touches syncback ref pipeline and ordering.
- Verify round-trip: Scenario with duplicate-named ref target and non-ref string property containing same substring as tentative path; ensure only
Rojo_Ref_* changes and non-ref string is untouched. - Verify syncback parity: Compare CLI syncback and two-way outputs for refs targeting dedup-suffixed instances; paths must be byte-identical.
- Tests required:
- Rust integration test in
[tests/tests/syncback_roundtrip.rs](d:/UserGenerated/rojo/tests/tests/syncback_roundtrip.rs)validating non-ref string preservation while refs are rewritten correctly. - Rust integration test in
[tests/tests/two_way_sync.rs](d:/UserGenerated/rojo/tests/tests/two_way_sync.rs)for dedup-aware ref persistence without text rewrite side effects.
- Rust integration test in
Fix 4: Add regression coverage for approved fixes
- Status: Approved
- Finding: Step 13 (missing coverage)
- Files:
[tests/tests/two_way_sync.rs](d:/UserGenerated/rojo/tests/tests/two_way_sync.rs),[tests/tests/syncback_roundtrip.rs](d:/UserGenerated/rojo/tests/tests/syncback_roundtrip.rs), optionally helper fixtures under[rojo-test/syncback-tests](d:/UserGenerated/rojo/rojo-test/syncback-tests). - Problem: Current tests do not fully guard the three approved bug classes.
- Solution: Add targeted regression tests tied to Fixes 1-3 and ensure they fail pre-fix and pass post-fix.
- Risk: Low.
- Verify round-trip: Covered by added integration scenarios.
- Verify syncback parity: Add assertions comparing resulting file paths/attribute values against expected CLI-format output.
- Tests required:
- All tests listed under Fixes 1-3.
- One deterministic re-run assertion (second run no changes) for affected scenarios.
Skipped Fixes
Skipped: ProjectNode duplicate-child handling rewrite
- Finding: Step 2d / Step 11k
- Problem: Duplicate-named children under ProjectNode are currently skipped with warnings (
[src/snapshot_middleware/project.rs](d:/UserGenerated/rojo/src/snapshot_middleware/project.rs)around~587-683). - Reason skipped: User accepted as release limitation for now.
Skipped: Globally optimal matcher replacement
- Finding: Step 11h (determinism/assignment quality)
- Problem: Greedy assignment may produce suboptimal pairing in rare ambiguous groups.
- Reason skipped: User accepted greedy behavior for this release.
Accepted Limitations
- Limitation: ProjectNode duplicate-named children are skipped during syncback.
- Scenario: Under project-backed nodes, duplicate names are detected and omitted from child maps; warning logged.
- User decision: Accepted for this release.
- Mitigation: Keep explicit warning logs; add failing acceptance test (below) to track until fixed.
- Limitation: Greedy matching is not globally optimal.
- Scenario: Ambiguous duplicate groups can choose a local minimum causing rename churn.
- User decision: Accepted for this release.
- Mitigation: Add stress/failing test for known counterexample and keep deterministic tie behavior.
Deferred Refactors
- Consolidate filesystem-segment derivation into one shared helper used by
[src/rojo_ref.rs](d:/UserGenerated/rojo/src/rojo_ref.rs),[src/snapshot/tree.rs](d:/UserGenerated/rojo/src/snapshot/tree.rs), and[src/web/api.rs](d:/UserGenerated/rojo/src/web/api.rs)to eliminate duplicate naming logic. - Revisit optimal assignment algorithm for large ambiguous groups (major rewrite; out of current scope).
Test Plan
Rust Unit Tests (#[cfg(test)])
- Dedup cleanup metadata update helper behavior after rename (Fix 1).
- Added-path segment generation helper returns correct extension/suffix across middleware types (Fix 2).
Rust Integration Tests (tests/tests/)
- Delete base dedup name then update promoted sibling in same serve session; verify writes hit new path and persist after rebuild (Fix 1).
- Same
/api/writerequest: add target + set Ref to target, verify persisted path includes expected extension/suffix (Fix 2). - Ref rewrite safety: non-ref string fields are unchanged while
Rojo_Ref_* gets final dedup path (Fix 3).
Lua Spec Tests (.spec.lua)
- No mandatory Lua-side changes for approved fixes; optional guard test for same-batch add+ref behavior if plugin-side helper touched.
Snapshot Tests (insta)
- Add or update syncback snapshot fixture showing final dedup-aware
Rojo_Ref_* output without incidental string changes (Fix 3).
Failing Tests for Known Limitations
- ProjectNode duplicate-named child round-trip identity test asserting correct behavior (expected to fail until limitation removed).
- Ambiguous-group matching counterexample test asserting globally optimal pairing (expected to fail with greedy algorithm).
Test Rules
For bugs found and fixed
- Each approved bug fix must have a regression test that fails pre-fix and passes post-fix.
For missing coverage identified
- Add coverage in affected layer(s); investigate any unexpected failure as a potential additional bug.
For known limitations (Accepted Limitations section)
- Write correctness-asserting tests even if they fail now.
- Leave them failing (do not ignore), to serve as future acceptance criteria.
Scope
- Cover both persistence correctness (filesystem outputs) and in-memory tree consistency where applicable.
Final Step: Run CI
After implementing all approved fixes and tests, run /ci and require a clean pass before considering this plan complete.