Skip to content

Fail loudly on sibling filename collisions - #123

Merged
argonui merged 1 commit into
mainfrom
fix/filename-collision-error
Aug 4, 2026
Merged

argonui merged 1 commit into
mainfrom
fix/filename-collision-error

Conversation

@argonui

@argonui argonui commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Summary

getAGoodFileName() returns <sanitized nickname>.<guid>, used both as the on-disk filename and as the object's identity in the *_order arrays, with no uniqueness check. Two siblings that collapse to the same name — a shared GUID inside a container, or nicknames differing only in stripped punctuation — caused the second WriteObj to silently overwrite the first.

The maintainer chose to fail loudly on collision rather than disambiguate.

Changes

  • Add checkFilenameCollisions([]*objConfig) which returns an error naming the colliding filename and the two GUIDs involved.
  • Invoke it where siblings sharing a directory are enumerated:
    • Printer.PrintObjectStates — root objects (top-level objects/ directory).
    • parseFromJSON — contained objects and states together, since both are written into the same subdirectory.
  • The check is scoped strictly to siblings sharing a directory, where the overwrite actually occurs.
  • Add unit tests: TestPrintObjectStatesCollision (shared GUID and stripped-punctuation cases) and TestContainedObjectCollision.

go build ./..., go vet ./..., and go test ./... all pass; no existing fixtures collide.

Fixes #107

getAGoodFileName() serves as both the on-disk filename and the identity in
the *_order arrays, but nothing checked it was unique among siblings. Two
siblings collapsing to the same name (a shared GUID inside a container, or
nicknames differing only in stripped punctuation) caused the second to
silently overwrite the first.

Add a checkFilenameCollisions helper and invoke it where siblings sharing a
directory are enumerated: root objects in Printer.PrintObjectStates, and
contained objects plus states in parseFromJSON. A collision now returns a
clear error naming the offending filename and the GUIDs involved.

Fixes #107

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@argonui
argonui force-pushed the fix/filename-collision-error branch from a4ddb60 to 00ec93c Compare August 4, 2026 02:25
@argonui
argonui merged commit 2a15dcf into main Aug 4, 2026
@argonui
argonui deleted the fix/filename-collision-error branch August 4, 2026 02:25
feuerfritas added a commit to feuerfritas/TTSModManager that referenced this pull request Sep 19, 2026
argonui#123 made two siblings that produce the same filename a hard error, which
fixed the silent overwrite in argonui#107. But TTS does not keep GUIDs unique
inside containers -- it only renumbers an object when it is drawn out onto
the table -- so real Workshop mods contain siblings sharing a nickname and
a GUID, and the error makes them impossible to decompose at all.

The second and later occurrences now get an ordinal suffix after the GUID
("Card.dup", "Card.dup-2", ...), assigned in document order, so nothing is
overwritten and the same input always yields the same names. A GUID is six
hex characters, so no naturally derived name ends in "<guid>-<n>" and the
suffix cannot collide. The resolved name is kept on the object and used on
the way back in, so reverse and build agree on it.

Also fixes a latent non-determinism found while writing this: state
children were collected from a Go map, whose iteration order is
randomised, so ordinal assignment (and before that, collision reporting)
could differ between runs. They are now sorted by state name.

The argonui#107 tests are kept and now assert the disambiguated names; a
three-way case checks that numbering is stable across an unrelated
sibling. go test ./... passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Object filename collisions overwrite silently

1 participant