Uploading multiple photos via one multi-select groups them ALL as one burst, even when the pictures are unrelated (observed: two completely different photos from different times shown as a burst).
Root cause (verified): the client mints one shared sessionId for ANY multi-select (photo-gallery.component.ts:254), the server persists it unvalidated, and BurstGrouping.Decide explicitly returns null for frames with a SessionId — the EXIF gap/same-camera rule never runs (contract #779 §3 "client sessionId always wins").
Fix: server treats the client sessionId as a hint. After EXIF extraction, a new pure rule BurstGrouping.ValidateClientSession checks chain-adjacency (sequence-ordered, same camera, |Δt| ≤ max(1,Δseq)×gap; new config Photography:ClientSessionMaxGapSeconds default 10.0). Frames failing validation are evicted (SessionId cleared) and flow through the existing EXIF auto-grouping. Untimed frames (no TakenAtPrecise) are never evicted (original focus-stack use case). Backfill phase added to tools/BackfillPhotographBursts heals existing bad prod sessions.
Backend-only; no Angular changes.
Uploading multiple photos via one multi-select groups them ALL as one burst, even when the pictures are unrelated (observed: two completely different photos from different times shown as a burst).
Root cause (verified): the client mints one shared sessionId for ANY multi-select (photo-gallery.component.ts:254), the server persists it unvalidated, and BurstGrouping.Decide explicitly returns null for frames with a SessionId — the EXIF gap/same-camera rule never runs (contract #779 §3 "client sessionId always wins").
Fix: server treats the client sessionId as a hint. After EXIF extraction, a new pure rule BurstGrouping.ValidateClientSession checks chain-adjacency (sequence-ordered, same camera, |Δt| ≤ max(1,Δseq)×gap; new config Photography:ClientSessionMaxGapSeconds default 10.0). Frames failing validation are evicted (SessionId cleared) and flow through the existing EXIF auto-grouping. Untimed frames (no TakenAtPrecise) are never evicted (original focus-stack use case). Backfill phase added to tools/BackfillPhotographBursts heals existing bad prod sessions.
Backend-only; no Angular changes.
Resolved in spikersoft-backend PR #505 (merged to master, e8bf57e0). Verified against origin/master — every bullet landed:
BurstGrouping.ValidateClientSession (SpikerSoft.Data/Mongos/Photography/BurstGrouping.cs:92) implements the adjacency rule as specified: order by SequenceIndex, deltaSeq = Math.Max(1, cur - prev), chain only when IsSameCamera(...) && deltaSeconds <= deltaSeq * maxGapSeconds (:108-114). Keeper run is the lowest SequenceIndex (:122), so repeated passes converge.
Untimed frames are never evicted — :97 filters to TakenAtPrecise is not null before building runs.
Photography:ClientSessionMaxGapSeconds: 10.0 is wired, not an orphan key: set at PhotographProcessor/appsettings.json:69, read at PhotographConsumers.cs:296.
Evicted frames re-flow through EXIF grouping — PhotographConsumers.cs:255-258 branches on SessionId is not null; TryValidateClientSessionAsync clears SessionId/SequenceIndex (:313-317) then feeds each evicted frame to TryAutoGroupAsync (:325-326).
Backfill for already-ingested sets: tools/BackfillPhotographBursts/Program.cs "Phase 1.5" (:95-130) calls the same pure rule, idempotent by construction.
Tests: ClientSessionValidationTests.cs, 15 cases including untimed/deleted/adjacency.
Closing.
Resolved in spikersoft-backend PR #505 (merged to `master`, `e8bf57e0`). Verified against `origin/master` — every bullet landed:
- `BurstGrouping.ValidateClientSession` (`SpikerSoft.Data/Mongos/Photography/BurstGrouping.cs:92`) implements the adjacency rule as specified: order by `SequenceIndex`, `deltaSeq = Math.Max(1, cur - prev)`, chain only when `IsSameCamera(...) && deltaSeconds <= deltaSeq * maxGapSeconds` (`:108-114`). Keeper run is the lowest `SequenceIndex` (`:122`), so repeated passes converge.
- Untimed frames are never evicted — `:97` filters to `TakenAtPrecise is not null` before building runs.
- `Photography:ClientSessionMaxGapSeconds: 10.0` is wired, not an orphan key: set at `PhotographProcessor/appsettings.json:69`, read at `PhotographConsumers.cs:296`.
- Evicted frames re-flow through EXIF grouping — `PhotographConsumers.cs:255-258` branches on `SessionId is not null`; `TryValidateClientSessionAsync` clears `SessionId`/`SequenceIndex` (`:313-317`) then feeds each evicted frame to `TryAutoGroupAsync` (`:325-326`).
- Backfill for already-ingested sets: `tools/BackfillPhotographBursts/Program.cs` "Phase 1.5" (`:95-130`) calls the same pure rule, idempotent by construction.
- Tests: `ClientSessionValidationTests.cs`, 15 cases including untimed/deleted/adjacency.
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.
Uploading multiple photos via one multi-select groups them ALL as one burst, even when the pictures are unrelated (observed: two completely different photos from different times shown as a burst).
Root cause (verified): the client mints one shared sessionId for ANY multi-select (photo-gallery.component.ts:254), the server persists it unvalidated, and BurstGrouping.Decide explicitly returns null for frames with a SessionId — the EXIF gap/same-camera rule never runs (contract #779 §3 "client sessionId always wins").
Fix: server treats the client sessionId as a hint. After EXIF extraction, a new pure rule BurstGrouping.ValidateClientSession checks chain-adjacency (sequence-ordered, same camera, |Δt| ≤ max(1,Δseq)×gap; new config Photography:ClientSessionMaxGapSeconds default 10.0). Frames failing validation are evicted (SessionId cleared) and flow through the existing EXIF auto-grouping. Untimed frames (no TakenAtPrecise) are never evicted (original focus-stack use case). Backfill phase added to tools/BackfillPhotographBursts heals existing bad prod sessions.
Backend-only; no Angular changes.
Resolved in spikersoft-backend PR #505 (merged to
master,e8bf57e0). Verified againstorigin/master— every bullet landed:BurstGrouping.ValidateClientSession(SpikerSoft.Data/Mongos/Photography/BurstGrouping.cs:92) implements the adjacency rule as specified: order bySequenceIndex,deltaSeq = Math.Max(1, cur - prev), chain only whenIsSameCamera(...) && deltaSeconds <= deltaSeq * maxGapSeconds(:108-114). Keeper run is the lowestSequenceIndex(:122), so repeated passes converge.:97filters toTakenAtPrecise is not nullbefore building runs.Photography:ClientSessionMaxGapSeconds: 10.0is wired, not an orphan key: set atPhotographProcessor/appsettings.json:69, read atPhotographConsumers.cs:296.PhotographConsumers.cs:255-258branches onSessionId is not null;TryValidateClientSessionAsyncclearsSessionId/SequenceIndex(:313-317) then feeds each evicted frame toTryAutoGroupAsync(:325-326).tools/BackfillPhotographBursts/Program.cs"Phase 1.5" (:95-130) calls the same pure rule, idempotent by construction.ClientSessionValidationTests.cs, 15 cases including untimed/deleted/adjacency.Closing.