Confirmed root cause (supersedes the earlier "poisoned document" hypothesis)
Server log for traceId 476a07892afaa9a0f4973bc08686bf09:
System.InvalidOperationException: Field 'Social' required but not present in BsonDocument for a 'UserProfile'.
at MongoDB.EntityFrameworkCore.Storage.BsonBinding.GetBsonDocument(BsonDocument parent, String name, Boolean required, ITypeBase declaredType)
... SpikerSoft.Api.Domain.Sponsor.SponsorController.GetPublicProfiles(...) : line 90
Not a corrupt document — schema drift from the social layer (#631, deployed 2026-07-17). That PR added UserProfile.Social as a non-nullable native embedded sub-document. The MongoDB EF Core provider maps a non-nullable owned navigation as required, so materializing any user-profiles document written before #631 (no Social element) throws. Both broken endpoints scan the entireUserProfiles collection (GetPublicProfiles, GetCountryTravelStatsQueryHandler), so they hit a legacy document and 500. Healthy endpoints (/api/geography/countries, /api/blog/posts) don't touch UserProfiles.
Social was the lone single-object sub-document on UserProfile that was neither JSON-string value-converted (Learning/Travel/Appearance/Sponsorship/…) nor nullable — every sibling already tolerates absence. Scan confirmed no other single-object nested prop has this shape (TravelDocuments is unconverted too but is a List → missing array = empty, never throws).
Fix
Code: spikersoft-backend PR #374 — maps Social as an optional owned navigation (ProfileSocial? + Navigation(...).IsRequired(false)); absent → null → all-off defaults; all readers coalesce. New integration test writes a legacy-shaped doc and proves a full-collection scan no longer throws (2/2 pass on Testcontainers Mongo; removing the mapping change reproduces this exact error).
Immediate mitigation (DBA, before the deploy lands): backfill the default Social sub-document onto legacy documents. Shape/enum verified against a real EF-written doc (PascalCase elements, DisplayNameMode as Int32 0). Idempotent, order-independent with the code fix (needed under the current required mapping, harmless under the fixed optional one). Self-verifying mongosh script:
// Run: mongosh "mongodb://<mongo-router>:27017/<db>" backfill-userprofile-social.mongosh.js
constcoll=db.getCollection("user-profiles");constmissing=coll.countDocuments({Social:{$exists:false}});print(`[backfill] documents missing 'Social': ${missing}`);// Verify shape against a known-good doc before writing (keys: DisplayNameMode Int32,
// ShareBookmarks/ShareReadingList/ViewOthersBookmarks Bool).
constsample=coll.findOne({Social:{$exists:true}},{Social:1});if(sample){print("[backfill] existing 'Social' shape:");printjson(sample.Social);}if(missing>0){constres=coll.updateMany({Social:{$exists:false}},{$set:{Social:{ShareBookmarks:false,ViewOthersBookmarks:false,ShareReadingList:false,DisplayNameMode:NumberInt(0),// NumberInt => BSON Int32 (matches EF enum storage)
}}});print(`[backfill] matched=${res.matchedCount} modified=${res.modifiedCount}`);print(`[backfill] still missing: ${coll.countDocuments({Social:{$exists:false} })}`);}
Note the earlier scan output for PROBE_SOCIAL_SHAPE/enum was verified empirically: { DisplayNameMode: 0, ShareBookmarks: false, ShareReadingList: false, ViewOthersBookmarks: false }.
Follow-up (separate)
GetPublicProfiles / GetCountryTravelStats materialize whole UserProfile documents where a projection would do — narrowing them would cut memory and structurally dodge this bug class.
## Confirmed root cause (supersedes the earlier "poisoned document" hypothesis)
Server log for traceId `476a07892afaa9a0f4973bc08686bf09`:
```
System.InvalidOperationException: Field 'Social' required but not present in BsonDocument for a 'UserProfile'.
at MongoDB.EntityFrameworkCore.Storage.BsonBinding.GetBsonDocument(BsonDocument parent, String name, Boolean required, ITypeBase declaredType)
... SpikerSoft.Api.Domain.Sponsor.SponsorController.GetPublicProfiles(...) : line 90
```
Not a corrupt document — **schema drift from the social layer (#631, deployed 2026-07-17)**. That PR added `UserProfile.Social` as a **non-nullable native embedded sub-document**. The MongoDB EF Core provider maps a non-nullable owned navigation as **required**, so materializing any `user-profiles` document written before #631 (no `Social` element) throws. Both broken endpoints scan the **entire** `UserProfiles` collection (`GetPublicProfiles`, `GetCountryTravelStatsQueryHandler`), so they hit a legacy document and 500. Healthy endpoints (`/api/geography/countries`, `/api/blog/posts`) don't touch `UserProfiles`.
`Social` was the lone single-object sub-document on `UserProfile` that was neither JSON-string value-converted (Learning/Travel/Appearance/Sponsorship/…) nor nullable — every sibling already tolerates absence. Scan confirmed no other single-object nested prop has this shape (`TravelDocuments` is unconverted too but is a `List` → missing array = empty, never throws).
## Fix
**Code:** spikersoft-backend PR #374 — maps `Social` as an optional owned navigation (`ProfileSocial?` + `Navigation(...).IsRequired(false)`); absent → null → all-off defaults; all readers coalesce. New integration test writes a legacy-shaped doc and proves a full-collection scan no longer throws (2/2 pass on Testcontainers Mongo; removing the mapping change reproduces this exact error).
**Immediate mitigation (DBA, before the deploy lands):** backfill the default `Social` sub-document onto legacy documents. Shape/enum verified against a real EF-written doc (PascalCase elements, `DisplayNameMode` as Int32 0). Idempotent, order-independent with the code fix (needed under the current required mapping, harmless under the fixed optional one). Self-verifying mongosh script:
```js
// Run: mongosh "mongodb://<mongo-router>:27017/<db>" backfill-userprofile-social.mongosh.js
const coll = db.getCollection("user-profiles");
const missing = coll.countDocuments({ Social: { $exists: false } });
print(`[backfill] documents missing 'Social': ${missing}`);
// Verify shape against a known-good doc before writing (keys: DisplayNameMode Int32,
// ShareBookmarks/ShareReadingList/ViewOthersBookmarks Bool).
const sample = coll.findOne({ Social: { $exists: true } }, { Social: 1 });
if (sample) { print("[backfill] existing 'Social' shape:"); printjson(sample.Social); }
if (missing > 0) {
const res = coll.updateMany(
{ Social: { $exists: false } },
{ $set: { Social: {
ShareBookmarks: false,
ViewOthersBookmarks: false,
ShareReadingList: false,
DisplayNameMode: NumberInt(0), // NumberInt => BSON Int32 (matches EF enum storage)
} } }
);
print(`[backfill] matched=${res.matchedCount} modified=${res.modifiedCount}`);
print(`[backfill] still missing: ${coll.countDocuments({ Social: { $exists: false } })}`);
}
```
Note the earlier scan output for `PROBE_SOCIAL_SHAPE`/enum was verified empirically: `{ DisplayNameMode: 0, ShareBookmarks: false, ShareReadingList: false, ViewOthersBookmarks: false }`.
## Follow-up (separate)
`GetPublicProfiles` / `GetCountryTravelStats` materialize whole `UserProfile` documents where a projection would do — narrowing them would cut memory and structurally dodge this bug class.
spikerj
changed title from PROD: /api/sponsor/profiles and /api/geography/travel-stats return 500 — likely poisoned UserProfiles materialization; fails e2e-anonymous CI to PROD: sponsor/profiles + geography/travel-stats 500 — UserProfile.Social required-owned-nav throws on pre-#631 documents (schema drift)2026-07-17 18:42:37 +00:00
Code fix merged: backend PR #374 — UserProfile.Social is ProfileSocial? with Navigation(...).IsRequired(false) on current master (SpikerDbContext L930-931), with the in-code comment documenting the required-owned-nav trap. The regression test UserProfileSocialAbsenceEfDeserializationIntegrationTests (legacy-shaped doc + full-collection scan) is in the integration suite.
Production verified live: both previously-500 endpoints now return 200 —
GET https://api.spikersoft.com/api/sponsor/profiles → 200
GET https://api.spikersoft.com/api/geography/travel-stats → 200
Full-collection scans over user-profiles no longer throw on pre-#631 documents, which also means either the deploy alone fixed it (optional mapping tolerates absence regardless of backfill) or the backfill ran too — either way the failure mode is gone and the code no longer depends on the backfill. Closing.
**Verified resolved end-to-end — closing.** (Ticket-triage loop 2026-07-18.)
- **Code fix merged:** backend PR #374 — `UserProfile.Social` is `ProfileSocial?` with `Navigation(...).IsRequired(false)` on current master (`SpikerDbContext` L930-931), with the in-code comment documenting the required-owned-nav trap. The regression test `UserProfileSocialAbsenceEfDeserializationIntegrationTests` (legacy-shaped doc + full-collection scan) is in the integration suite.
- **Production verified live:** both previously-500 endpoints now return 200 —
- `GET https://api.spikersoft.com/api/sponsor/profiles` → **200**
- `GET https://api.spikersoft.com/api/geography/travel-stats` → **200**
Full-collection scans over `user-profiles` no longer throw on pre-#631 documents, which also means either the deploy alone fixed it (optional mapping tolerates absence regardless of backfill) or the backfill ran too — either way the failure mode is gone and the code no longer depends on the backfill. Closing.
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.
Confirmed root cause (supersedes the earlier "poisoned document" hypothesis)
Server log for traceId
476a07892afaa9a0f4973bc08686bf09:Not a corrupt document — schema drift from the social layer (#631, deployed 2026-07-17). That PR added
UserProfile.Socialas a non-nullable native embedded sub-document. The MongoDB EF Core provider maps a non-nullable owned navigation as required, so materializing anyuser-profilesdocument written before #631 (noSocialelement) throws. Both broken endpoints scan the entireUserProfilescollection (GetPublicProfiles,GetCountryTravelStatsQueryHandler), so they hit a legacy document and 500. Healthy endpoints (/api/geography/countries,/api/blog/posts) don't touchUserProfiles.Socialwas the lone single-object sub-document onUserProfilethat was neither JSON-string value-converted (Learning/Travel/Appearance/Sponsorship/…) nor nullable — every sibling already tolerates absence. Scan confirmed no other single-object nested prop has this shape (TravelDocumentsis unconverted too but is aList→ missing array = empty, never throws).Fix
Code: spikersoft-backend PR #374 — maps
Socialas an optional owned navigation (ProfileSocial?+Navigation(...).IsRequired(false)); absent → null → all-off defaults; all readers coalesce. New integration test writes a legacy-shaped doc and proves a full-collection scan no longer throws (2/2 pass on Testcontainers Mongo; removing the mapping change reproduces this exact error).Immediate mitigation (DBA, before the deploy lands): backfill the default
Socialsub-document onto legacy documents. Shape/enum verified against a real EF-written doc (PascalCase elements,DisplayNameModeas Int32 0). Idempotent, order-independent with the code fix (needed under the current required mapping, harmless under the fixed optional one). Self-verifying mongosh script:Note the earlier scan output for
PROBE_SOCIAL_SHAPE/enum was verified empirically:{ DisplayNameMode: 0, ShareBookmarks: false, ShareReadingList: false, ViewOthersBookmarks: false }.Follow-up (separate)
GetPublicProfiles/GetCountryTravelStatsmaterialize wholeUserProfiledocuments where a projection would do — narrowing them would cut memory and structurally dodge this bug class.PROD: /api/sponsor/profiles and /api/geography/travel-stats return 500 — likely poisoned UserProfiles materialization; fails e2e-anonymous CIto PROD: sponsor/profiles + geography/travel-stats 500 — UserProfile.Social required-owned-nav throws on pre-#631 documents (schema drift)Verified resolved end-to-end — closing. (Ticket-triage loop 2026-07-18.)
UserProfile.SocialisProfileSocial?withNavigation(...).IsRequired(false)on current master (SpikerDbContextL930-931), with the in-code comment documenting the required-owned-nav trap. The regression testUserProfileSocialAbsenceEfDeserializationIntegrationTests(legacy-shaped doc + full-collection scan) is in the integration suite.GET https://api.spikersoft.com/api/sponsor/profiles→ 200GET https://api.spikersoft.com/api/geography/travel-stats→ 200Full-collection scans over
user-profilesno longer throw on pre-#631 documents, which also means either the deploy alone fixed it (optional mapping tolerates absence regardless of backfill) or the backfill ran too — either way the failure mode is gone and the code no longer depends on the backfill. Closing.