- Marked all todos in the slugify branch audit plan as completed, confirming the successful verification of core invariants and sync processes. - Introduced a new plan to address critical bugs, correctness issues, and refactors identified during the audit, including enhancements to metadata handling and slugification logic. - Added tests to ensure proper handling of filenames with forbidden characters and deduplication of instances with colliding names. - Refactored metadata operations to improve clarity and maintainability, consolidating logic for reading and writing the `name` field in `.meta.json5` files.
24 KiB
| name | overview | todos | isProject | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Audit slugify branch | Production-grade audit of the `slugify` branch. The invariant: filesystem must be a perfect 1:1 representation of the Studio place such that syncback → rebuild from filesystem produces a byte-identical place file. Every audit item is evaluated against this round-trip fidelity standard. |
|
false |
Audit: slugify Branch -- Production-Grade Round-Trip Fidelity
Quality Standard
This is a production-grade synchronization system. The filesystem must represent a 1:1 copy of the Studio place. The core invariant:
Round-trip identity: 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.
Every audit item below must be evaluated against this standard. A "works most of the time" finding is not acceptable. If there is ANY code path where an instance name, property, or hierarchy relationship can be lost, mangled, or silently altered through a syncback→rebuild cycle, flag it as critical.
Code Quality Standard
We are building a maintainable codebase. During the audit, watch for duplicated logic -- the same slugify/dedup/meta-update pattern copy-pasted across change_processor.rs, api.rs, dir.rs, project.rs, etc. If you find duplicated code that could be consolidated into a shared helper:
- Small refactor (extracting a helper function, consolidating 2-3 call sites): flag it AND fix it as part of this audit.
- Major system rewrite (restructuring data flow, changing function signatures across 10+ call sites, redesigning the snapshot pipeline): flag it clearly in a "Deferred Refactors" section at the bottom of the report. We will evaluate whether it's worth doing after the audit is complete.
The goal is clean, DRY code that is easy to reason about and hard to get wrong. If the same "compute meta path, check if name needs slugify, upsert/remove name field" sequence appears in 3+ places, that's a refactor candidate.
Context
This audit covers two plans that drove the branch:
- nuke_path_encoding_migration -- removed
src/path_encoding.rs, replaced all encode/decode callsites withslugify_name()+deduplicate_name()+ metadatanamefield - fix_stem-level_dedup -- fixed dedup key propagation so
taken_namescontains bare slugs (not filenames with extensions)
Run git diff main...HEAD for the full diff. Read both plan files in full. Then audit every area below, citing file paths, line numbers, and code snippets. Flag anything missing, incorrect, inconsistent, or undertested.
1. Round-Trip Identity Verification
This is the single most important audit area. Trace the complete lifecycle of an instance with a name containing forbidden chars through every path:
1a. Syncback → Forward Sync Round-Trip
Pick a concrete example: instance named "Key:Script" (ServerScript class).
- Syncback writes:
Key_Script.server.luau+Key_Script.meta.json5with{"name": "Key:Script"} - Forward sync reads:
Key_Script.server.luau→ detects adjacentKey_Script.meta.json5→ readsnamefield → setsspecified_nameon metadata → final instance name becomes"Key:Script" - Verify: Trace this exact path through the code. Is there ANY point where the name could become
"Key_Script"instead of"Key:Script"? Any middleware that reads the filename stem and ignores the meta name?
1b. Two-Way Sync → Forward Sync Round-Trip
Instance added in Studio with name "What?Module" during a live rojo serve session:
- API handler writes:
What_Module.luau+What_Module.meta.json5with{"name": "What?Module"} - Change processor picks up the new files → snapshots them → applies to DOM
- Verify: Does the DOM instance end up with name
"What?Module"or"What_Module"? If the change processor snapshots the new file before the meta file is written, what happens?
1c. Rename Round-Trip
Instance renamed in Studio from "Clean" to "Has/Slash":
- Old file
Clean.server.luauis removed - New file
Has_Slash.server.luau+Has_Slash.meta.json5with{"name": "Has/Slash"}is created - Verify: Is there a window where the old file is deleted but the new file hasn't been written yet? Could the file watcher fire in between and produce an incorrect intermediate state?
1d. Collision Round-Trip
Two sibling instances: "A/B" (ModuleScript) and "A_B" (ModuleScript). Both slugify to A_B.
- Syncback: First gets
A_B.luau, second getsA_B~1.luau(with meta{"name": "A_B"}since slug+dedup differs from real name) - Forward sync rebuild:
A_B.luau→ name"A/B"(from meta),A_B~1.luau→ name"A_B"(from meta) - Verify: Is the ordering deterministic? If processed in reverse order next time, do names get swapped? Is there a stable sort or tie-breaking rule?
1e. Directory Instance Round-Trip
Folder named "My:Folder":
- Syncback: Directory
My_Folder/+My_Folder/init.meta.json5with{"name": "My:Folder"} - Forward sync: Reads directory → finds
init.meta.json5→ applies name override → instance named"My:Folder" - Verify: Does
DirectoryMetadata.apply_name()actually work? Isinit.meta.json5read before or after the directory name is set?
1f. Model JSON Round-Trip
Configuration instance named "What?Model":
- Syncback:
What_Model.model.json5with{"name": "What?Model", "className": "Configuration"} - Forward sync: Reads
.model.json5→ parsesnamefield → uses it as instance name - Verify: Trace through json_model.rs. Does the
namefield actually override? Is there a priority conflict withproperties.Name?
2. Forward Sync Completeness (filesystem → Roblox)
Every middleware that reads files and produces InstanceSnapshot must correctly resolve the instance name.
- src/snapshot_middleware/mod.rs: Is
decode_path_namefully removed? Does every middleware path derive the instance name from the metadatanamefield (when present) or the filename stem (when absent)? - src/snapshot_middleware/meta_file.rs: Do
AdjacentMetadataandDirectoryMetadataboth havename: Option<String>? Doapply_all/apply_namecorrectly setspecified_nameonInstanceMetadata? Isis_empty()updated forname? - src/snapshot_middleware/json_model.rs: Does
namefrom.model.json5actually override the filename-derived name? Trace parse →InstanceSnapshotconstruction → final instance name. Any path wherenameis silently ignored? - src/snapshot_middleware/lua.rs, txt.rs, csv.rs: Correct instance names? Any lingering
encode_path_name/decode_path_namereferences? - Edge cases:
Foo~1.server.luauwith NO meta file -- must produce instance nameFoo~1(backward compat, tilde is NOT a dedup marker during reads)Foo~1.server.luauWITH meta"name": "Foo/Bar"-- meta name must wininit.meta.json5with"name": "SomeOverride"inside directoryMyFolder-- must name the directory instanceSomeOverride
3. Reverse Sync Completeness (Roblox → filesystem / syncback)
Every path that writes instances to the filesystem must use slugify + dedup + metadata correctly, and the written files must round-trip back to identical instances.
- src/syncback/file_names.rs: Audit
slugify_name()for all 9 forbidden chars (< > : " / \ | ? *), Windows reserved names (CON, PRN, NUL, COM1-9, LPT1-9), trailing dots/spaces, empty strings, all-forbidden-chars strings. Doesdeduplicate_name()handle~1already taken (skip to~2)? Doesname_for_inst()correctly return(filename, needs_meta, dedup_key)for both old-inst and new-inst paths? - src/syncback/snapshot.rs: Do
with_joined_pathandwith_base_pathpropagatededup_key? Are callers in dir.rs/project.rs usingdedup_key(notfilename) when inserting intotaken_names? - src/snapshot_middleware/dir.rs: Is
taken_namesseeded correctly (tree children, not disk)? Isdedup_key.to_lowercase()inserted after each child? Any ordering issue where a child is processed before its collision partner is in the set? - src/snapshot_middleware/project.rs: Same checks. Verify
or_insert_withclosure seeds from tree children. - Meta file emission: When
needs_metais true, isnameactually written to.meta.json5or.model.json5? Trace fromname_for_instreturningneeds_meta=true→ meta file on disk. Is there ANY code path whereneeds_metais true but no meta file gets written? This would silently lose the real instance name on rebuild. - Determinism: Given the same instance tree, does syncback ALWAYS produce the same filesystem output? Is child ordering stable? Is dedup suffix assignment deterministic? Non-determinism means different builds from the same source, which violates the invariant.
- Edge cases:
- Instance name
Hey_Bro(no forbidden chars, slug equals another instance's slug) -- no meta if first claim,~1+ meta if second - Instance name exactly a Windows reserved name (e.g.
CON) -- what doesslugify_nameproduce? Can the result be read back?
- Instance name
4. Two-Way Sync Completeness (live serve sessions)
Highest-risk area. Every change made in Studio must produce filesystem state that, if the serve session were killed and the place rebuilt from scratch, would produce an identical tree.
- src/change_processor.rs -- instance renamed in Studio:
- Slugifies new name?
- Dedups against sibling files in same directory?
- Creates/updates/removes meta
namefield as needed? - Renaming FROM slugified TO clean: removes stale
namefrom meta? (checkremove_meta_name_field) - Renaming FROM clean TO slugified: creates meta with
name? (checkupsert_meta_name_field) - Suppresses filesystem events correctly to avoid feedback loops?
- After the rename completes, if you killed the server and ran
rojo build, would the instance have the correct name?
- src/web/api.rs -- instance added:
syncback_added_instanceuses slugify + dedup correctly?taken_namesseeded from tree siblings (not disk)?- Writes meta file when slug differs from real name?
- Meta file path matches script file path (e.g.
Foo_Bar.server.luaupairs withFoo_Bar.meta.json5, not some other path)? - After the add completes, does
rojo buildinclude this instance with the correct name, class, and source?
- src/web/api.rs -- instance removed:
- Cleans up both script file AND associated meta file?
- Handles missing meta file gracefully (instance had clean name)?
- After the remove, does
rojo buildcorrectly omit this instance?
- Atomicity: When a rename involves deleting old files and creating new files, is there a window where the filesystem is in an inconsistent state that the file watcher could pick up? Could this cause a "ghost" instance or a lost instance in the DOM?
- Race conditions: When multiple renames happen in quick succession, is
taken_namesrebuilt each time or cached? Could stale data cause incorrect dedup?
5. Dedup Key Consistency
The dedup system must be watertight. Any inconsistency means two instances can silently overwrite each other's files.
name_for_instold-inst path: derivesdedup_keyby stripping middleware extension from existing filename? What if Dir middleware (no extension)? What if extension doesn't matchextension_for_middleware(manually renamed file)?name_for_instnew-inst path:dedup_keyis always the slug BEFORE extension is appended?- Case sensitivity: Is
.to_lowercase()applied consistently at ALL insertion sites in dir.rs, project.rs, api.rs, change_processor.rs? A single site that forgets lowercase would cause silent overwrites on case-insensitive filesystems (Windows/macOS). - Cross-format collision: Instance
Helperas ModuleScript (Helper.luau) and instanceHelperas Folder (Helper/) -- both dedup keys should behelper(lowercase). Do they collide correctly, producingHelper/andHelper~1.luau(or vice versa)? - Stable ordering: When two instances collide, which one gets the base name and which gets
~1? Is this deterministic across runs? If not, consecutive syncbacks could swap filenames, causing unnecessary git churn.
6. Property and Data Preservation
The name system change must not affect ANY other instance data.
- Name property vs instance name: Roblox instances have both
Instance.Name(the tree name) and aNamestring property. Are these conflated anywhere? Does the newnamefield in meta/model files interact with theNameproperty inproperties? - Ref properties: Do ref properties (ObjectValue.Value, etc.) still resolve correctly after the naming change? Refs use instance IDs, not names, but verify no code path accidentally uses filenames for ref resolution.
- Attributes: Are instance attributes preserved through syncback? The naming change shouldn't affect them, but verify no metadata merge operation accidentally clobbers attributes stored in meta files.
- Source scripts: Does script source content survive the rename cycle? When a file is renamed (old deleted, new created), is the content preserved byte-for-byte?
7. Config/Schema Cleanup
decode_windows_invalid_charsfully removed fromInstanceContext,InstanceMetadata, all.snapfiles,serve_session.rs?encode_windows_invalid_charsfully removed fromSyncbackRules, project schema, JSON schema?- Grep for ANY lingering references to:
path_encoding,encode_path_name,decode_path_name,%SLASH%,%COLON%,%QUESTION%,%DOT%,%STAR%,%PIPE%,%QUOTE%,%LESSTHAN%,%GREATERTHAN% - Do old project files with these removed fields parse without crashing? (serde should ignore unknown fields, but verify)
8. Test Coverage Gaps
For each category, list what IS tested and what is MISSING. Prioritize round-trip tests -- these are the ultimate proof of correctness.
- Round-trip tests: Is there a test that does syncback → rebuild → compare instance trees? This is the gold standard. If it doesn't exist, flag as critical missing coverage.
- Unit tests (src/syncback/file_names.rs):
slugify_nameedge cases,deduplicate_nameedge cases,name_for_install paths,dedup_keycorrectness - Build integration (tests/tests/build.rs):
.model.json5withnamefield?init.meta.json5withname?~Nsuffixed files read back correctly? - Syncback integration (tests/tests/syncback.rs): Forbidden-char instances produce slugified filenames + meta? Collision test (two instances whose slugs match)?
- Two-way sync (tests/tests/two_way_sync.rs): All 28 old encoded-pattern references updated? Rename test (clean → slugified, slugified → clean)? Add test (new instance with forbidden chars)? Collision test during live sync?
- Snapshot tests: Any pending
cargo insta reviewchanges? All.snapfiles reflect new system? - Serve test fixtures (rojo-test/serve-tests/syncback_encoded_names/): Fixtures match slugified format? Meta files with
namefields present? - Negative test: File
Foo~1.luauWITHOUT meta produces instance nameFoo~1(tilde NOT parsed as dedup marker during forward sync)? - Idempotency test: Run syncback twice on the same tree -- does the second run produce zero file changes? If not, something is unstable.
- Large tree stress test: 100+ instances with various collision patterns -- does dedup produce correct, deterministic results?
9. JSON Model name Field
- Read path (json_model.rs):
.model.json5with"name": "Real Name"usesReal Name?specified_nameset on metadata? - Write path (syncback):
namefield emitted when slug differs? Omitted when slug matches? - Legacy compat: Old
.model.jsonwith"Name": "Something"inpropertiesstill works? Any conflict between new top-levelnameand oldproperties.Name? What wins if both are present? **json_model_legacy_namebuild test**: Snapshot changed fromExpected NametoOverridden Name. Is this correct? What was the test fixture's intent? Read the fixture files to understand the expected behavior.
10. Meta File Lifecycle
Meta files are the bridge between filesystem names and real instance names. Any bug here silently corrupts names.
- Creation: Only when
needs_metais true? If meta already exists with other fields (e.g.ignoreUnknownInstances), is thenamefield MERGED in (not replacing the whole file)? Verifyupsert_meta_name_fieldin change_processor.rs does a proper merge. - Update: When instance renamed and slug changes, is meta updated? When slug stays same but real name changes (e.g.
A/B→A:B, both slugify toA_B) -- the metanamefield must change from"A/B"to"A:B". Is this handled? - Deletion: Renaming from slugified to clean removes
namefrom meta? If meta has no other fields after removal, is the file itself deleted? Verifyremove_meta_name_fieldhandles both cases. - Orphaned meta files: Script deleted but meta remains -- does forward sync handle this gracefully (ignore the orphan) or does it create a phantom instance?
- Model JSON meta vs adjacent meta: For
.model.json5files, thenameis stored IN the model file (not in a separate.meta.json5). Is this consistent? Could there be a case where both a.model.json5namefield and a.meta.json5namefield exist for the same instance? What wins?
11. Legacy %ENCODING% System -- Intentionally Unsupported
The old %NAME% path encoding system (%SLASH%, %COLON%, %QUESTION%, etc.) is intentionally removed with no backward compatibility. This is a full refactor, not an incremental change. The old system was inferior -- it produced ugly filenames, required special decoding on read, and was fragile. The new slugify + metadata name approach is strictly better.
Do NOT flag the removal of %ENCODING% support as a regression. It is by design. However, DO verify:
src/path_encoding.rsis fully deleted and no references remain anywhere in the codebase- No
%SLASH%,%COLON%,%QUESTION%,%DOT%,%STAR%,%PIPE%,%QUOTE%,%LESSTHAN%,%GREATERTHAN%patterns exist in source code, test fixtures, or snapshots - Projects with the old
encodeWindowsInvalidChars/decodeWindowsInvalidCharsfields in.project.jsonparse without crashing (serde should ignore unknown fields -- verify this) - The
syncback_encoded_namestest fixture has been fully migrated from%ENCODED%filenames to slugified filenames + meta files, with no old-format files remaining
12. Known Issues (Already Identified)
These have already been found. Include them in the report as confirmed, and check whether the suggested fixes are correct and complete.
12a. CRITICAL: Meta name field update skipped when slug doesn't change
Files: src/change_processor.rs:834-879, src/change_processor.rs:734-789
The meta file name field update logic (upsert_meta_name_field / remove_meta_name_field) is nested inside the if new_path != *path condition. When renaming an instance where both old and new names slugify to the same filename (e.g. "Foo/Bar" to "Foo|Bar" -- both become "Foo_Bar"), the path doesn't change, so the entire block is skipped -- including the meta file updates.
Impact: The instance rename is silently lost. The meta file retains the old name value, so subsequent forward syncs restore the old name instead of the new one. This is a direct round-trip fidelity violation.
Affected paths: Both regular file renames (line ~835) and init file/directory renames (line ~735).
Suggested fix: Move the meta name field update logic OUTSIDE the if new_path != *path block. The meta name must be updated whenever the instance name changes, regardless of whether the filesystem path changes. The condition for updating the meta file is old_name != new_name, not old_path != new_path.
13. Deliverables
Produce a structured report with:
- Critical issues -- bugs that would cause data loss, incorrect instance names, file overwrites, or round-trip identity failures
- Correctness concerns -- logic that looks wrong or has edge cases that might not manifest immediately but could cause silent data corruption
- Missing test coverage -- specific test cases that should exist but don't, prioritized by risk (round-trip tests > unit tests)
- Determinism concerns -- any non-deterministic behavior that could cause different builds from the same source
- Cleanup items -- leftover references, dead code, stale comments
- Deferred refactors -- major structural improvements that would reduce duplication or improve maintainability but are too large to do inline. Describe the problem, the affected files, and the rough scope. These will be evaluated separately after the audit.
- For each issue: file path, line numbers, description, and suggested fix