Found while scoping #853. Not currently broken — filing so it doesn't become the next #852.
The hazard
UserProfile.Images is a List<ProfileImage> persisted as an EF JSON-string column via System.Text.Json (SpikerSoft.Data/Contexts/SpikerDbContext.cs:1059).
But ProfileImage's storage-key properties are annotated with Newtonsoft[JsonProperty]:
SpikerSoft.Data/Mongos/ProfileSettingsSubDocuments.cs:10 — [JsonProperty("gridFsFileId")] on GridFsFileId
SpikerSoft.Data/Mongos/ProfileSettingsSubDocuments.cs:31 — [JsonProperty("thumbnailGridFsId")] on ThumbnailGridFsId
System.Text.Json ignores[JsonProperty]. So these persist under their CLR names — PascalCase "GridFsFileId" / "ThumbnailGridFsId" — not the lowercase the attribute implies. The attribute is decorative and actively misleading.
Unlike ArtAsset.Artifacts, this converter has no case-insensitive read option, so there is no safety net.
Why it matters
This is #852's exact failure mode, unexploded. It is dormant purely because nobody has renamed those CLR properties. The moment someone does — exactly the "harmless rename" #849 looked like — every profile image key deserializes to its default and avatars/photos break, silently, with every sibling field still binding correctly.
ProfileTravelSubDocuments.cs:38 ([JsonProperty("gridFsId")] on GridFsId) is in the same JSON-column situation.
Note
These are genuinely still GridFS — Profile/avatars have not migrated to MinIO — so this is not a rename-to-objectKey task. The fix is to make the persisted contract honest and enforced:
Replace the Newtonsoft [JsonProperty] attributes with [JsonPropertyName] matching the name actually on disk today (PascalCase), or migrate the documents and pin the new name — the former is far cheaper and non-breaking.
Consider a guard test in the spirit of ArtStudioStorageKeyNamingTests pinning the serialized names.
Audit the other 63 HasConversion JSON columns for the same Newtonsoft-attribute-on-an-STJ-column pattern.
Related
#852 — the outage this pattern caused in Art Studio
#853 — the proper rename that removes the shim there
Found while scoping #853. **Not currently broken** — filing so it doesn't become the next #852.
## The hazard
`UserProfile.Images` is a `List<ProfileImage>` persisted as an EF JSON-string column via **System.Text.Json** (`SpikerSoft.Data/Contexts/SpikerDbContext.cs:1059`).
But `ProfileImage`'s storage-key properties are annotated with **Newtonsoft** `[JsonProperty]`:
- `SpikerSoft.Data/Mongos/ProfileSettingsSubDocuments.cs:10` — `[JsonProperty("gridFsFileId")]` on `GridFsFileId`
- `SpikerSoft.Data/Mongos/ProfileSettingsSubDocuments.cs:31` — `[JsonProperty("thumbnailGridFsId")]` on `ThumbnailGridFsId`
System.Text.Json **ignores** `[JsonProperty]`. So these persist under their CLR names — PascalCase `"GridFsFileId"` / `"ThumbnailGridFsId"` — not the lowercase the attribute implies. The attribute is decorative and actively misleading.
Unlike `ArtAsset.Artifacts`, this converter has **no** case-insensitive read option, so there is no safety net.
## Why it matters
This is #852's exact failure mode, unexploded. It is dormant purely because nobody has renamed those CLR properties. The moment someone does — exactly the "harmless rename" #849 looked like — every profile image key deserializes to its default and avatars/photos break, silently, with every sibling field still binding correctly.
`ProfileTravelSubDocuments.cs:38` (`[JsonProperty("gridFsId")]` on `GridFsId`) is in the same JSON-column situation.
## Note
These are **genuinely still GridFS** — Profile/avatars have not migrated to MinIO — so this is *not* a rename-to-`objectKey` task. The fix is to make the persisted contract honest and enforced:
- Replace the Newtonsoft `[JsonProperty]` attributes with `[JsonPropertyName]` matching the name **actually on disk today** (PascalCase), or migrate the documents and pin the new name — the former is far cheaper and non-breaking.
- Consider a guard test in the spirit of `ArtStudioStorageKeyNamingTests` pinning the serialized names.
- Audit the other 63 `HasConversion` JSON columns for the same Newtonsoft-attribute-on-an-STJ-column pattern.
## Related
- #852 — the outage this pattern caused in Art Studio
- #853 — the proper rename that removes the shim there
All Newtonsoft attributes on a type persisted through System.Text.Json, which is exactly the #852 mechanism: the attribute is inert, so the property serializes under its CLR name and any rename silently orphans the stored data.
Remaining, with the greps that show absence:
No [JsonPropertyName] replacement — git grep -n "JsonPropertyName" origin/master -- '*ProfileSettingsSubDocuments.cs' '*ProfileTravelSubDocuments.cs' exits 1 with no output.
No guard test pinning serialized names — git grep -n "gridFsFileId" origin/master returns exactly one hit: the attribute itself. Nothing asserts the wire name.
No safety net on the conversion — UserProfile.Images still passes (JsonSerializerOptions?)null in both directions (SpikerDbContext.cs:1108-1112), so it has the same case-sensitive, silently-empty failure mode #852 had.
Also still outstanding: the ticket's broader ask to audit the other 63HasConversion JSON columns for the same pattern. That audit has not been done, and it's the part that would tell us whether #852/#854 are two instances or the visible tip of a class.
Re-verified against `origin/master` — **nothing has changed. All three fix items outstanding.** File last touched 2025-11-20.
The latent twin is still there verbatim:
- `ProfileSettingsSubDocuments.cs:10` — `[JsonProperty("gridFsFileId")]`
- `ProfileSettingsSubDocuments.cs:31` — `[JsonProperty("thumbnailGridFsId")]`
- `ProfileTravelSubDocuments.cs:38` — likewise
All Newtonsoft attributes on a type persisted through System.Text.Json, which is exactly the #852 mechanism: the attribute is inert, so the property serializes under its CLR name and any rename silently orphans the stored data.
Remaining, with the greps that show absence:
1. **No `[JsonPropertyName]` replacement** — `git grep -n "JsonPropertyName" origin/master -- '*ProfileSettingsSubDocuments.cs' '*ProfileTravelSubDocuments.cs'` exits 1 with no output.
2. **No guard test pinning serialized names** — `git grep -n "gridFsFileId" origin/master` returns exactly one hit: the attribute itself. Nothing asserts the wire name.
3. **No safety net on the conversion** — `UserProfile.Images` still passes `(JsonSerializerOptions?)null` in both directions (`SpikerDbContext.cs:1108-1112`), so it has the same case-sensitive, silently-empty failure mode #852 had.
Also still outstanding: the ticket's broader ask to audit the other **63** `HasConversion` JSON columns for the same pattern. That audit has not been done, and it's the part that would tell us whether #852/#854 are two instances or the visible tip of a class.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found while scoping #853. Not currently broken — filing so it doesn't become the next #852.
The hazard
UserProfile.Imagesis aList<ProfileImage>persisted as an EF JSON-string column via System.Text.Json (SpikerSoft.Data/Contexts/SpikerDbContext.cs:1059).But
ProfileImage's storage-key properties are annotated with Newtonsoft[JsonProperty]:SpikerSoft.Data/Mongos/ProfileSettingsSubDocuments.cs:10—[JsonProperty("gridFsFileId")]onGridFsFileIdSpikerSoft.Data/Mongos/ProfileSettingsSubDocuments.cs:31—[JsonProperty("thumbnailGridFsId")]onThumbnailGridFsIdSystem.Text.Json ignores
[JsonProperty]. So these persist under their CLR names — PascalCase"GridFsFileId"/"ThumbnailGridFsId"— not the lowercase the attribute implies. The attribute is decorative and actively misleading.Unlike
ArtAsset.Artifacts, this converter has no case-insensitive read option, so there is no safety net.Why it matters
This is #852's exact failure mode, unexploded. It is dormant purely because nobody has renamed those CLR properties. The moment someone does — exactly the "harmless rename" #849 looked like — every profile image key deserializes to its default and avatars/photos break, silently, with every sibling field still binding correctly.
ProfileTravelSubDocuments.cs:38([JsonProperty("gridFsId")]onGridFsId) is in the same JSON-column situation.Note
These are genuinely still GridFS — Profile/avatars have not migrated to MinIO — so this is not a rename-to-
objectKeytask. The fix is to make the persisted contract honest and enforced:[JsonProperty]attributes with[JsonPropertyName]matching the name actually on disk today (PascalCase), or migrate the documents and pin the new name — the former is far cheaper and non-breaking.ArtStudioStorageKeyNamingTestspinning the serialized names.HasConversionJSON columns for the same Newtonsoft-attribute-on-an-STJ-column pattern.Related
Re-verified against
origin/master— nothing has changed. All three fix items outstanding. File last touched 2025-11-20.The latent twin is still there verbatim:
ProfileSettingsSubDocuments.cs:10—[JsonProperty("gridFsFileId")]ProfileSettingsSubDocuments.cs:31—[JsonProperty("thumbnailGridFsId")]ProfileTravelSubDocuments.cs:38— likewiseAll Newtonsoft attributes on a type persisted through System.Text.Json, which is exactly the #852 mechanism: the attribute is inert, so the property serializes under its CLR name and any rename silently orphans the stored data.
Remaining, with the greps that show absence:
[JsonPropertyName]replacement —git grep -n "JsonPropertyName" origin/master -- '*ProfileSettingsSubDocuments.cs' '*ProfileTravelSubDocuments.cs'exits 1 with no output.git grep -n "gridFsFileId" origin/masterreturns exactly one hit: the attribute itself. Nothing asserts the wire name.UserProfile.Imagesstill passes(JsonSerializerOptions?)nullin both directions (SpikerDbContext.cs:1108-1112), so it has the same case-sensitive, silently-empty failure mode #852 had.Also still outstanding: the ticket's broader ask to audit the other 63
HasConversionJSON columns for the same pattern. That audit has not been done, and it's the part that would tell us whether #852/#854 are two instances or the visible tip of a class.