Context
Follow-up from #704 Wave 0B / PR #707.
PR #707 correctly closed the known cross-project mutation races in character/world profile generation, field regeneration, template application, outline generation/application, and snapshot restore. Review convergence also added same-ID project-incarnation tracking, fail-closed identity comparison, stale-outline-preview rejection, and regression coverage.
A final post-merge completeness sweep shows that #707 did not modify the remaining asynchronous AI paths below. They therefore still require an explicit mutation-authority audit rather than being implicitly considered closed by Wave 0B.
Residual surfaces to audit
1. Character portrait generation / refinement
generateCharacterPortraitThunk performs asynchronous AI image generation and then calls:
await storageService.saveImage(characterId, base64)
The thunk later fulfills with { characterId }, and projectSlice applies hasAvatar: true to the then-current project state.
Audit both portrait entry points in useCharacterView:
- initial portrait generation;
- portrait refinement.
2. World image generation / refinement
generateWorldImageThunk follows the analogous pattern:
await storageService.saveImage(worldId, base64)
The fulfilled reducer updates hasAmbianceImage in the then-current project state.
Audit both world-image entry points in useWorldView:
- initial image generation;
- image refinement.
3. Asset-storage ownership
storageService.saveImage(id, base64Data) is keyed by entity id and does not carry an explicit project id at this boundary.
Determine, for both IndexedDB and filesystem backends, whether a late image result originating under project A can overwrite, alias, or otherwise affect an asset later observed under project B when entity ids collide or project state changes while generation is in flight.
Do not assume Redux protection alone proves asset safety; prove the storage ownership semantics separately.
4. Character interview streaming
Audit the project/streamInterviewChunk path and its streaming producer(s). The reducer resolves characterId / interviewId against the currently active state.data.characterInterviews.
Determine whether a stream originating in project A can continue dispatching chunks after project B becomes active and mutate project-B-owned interview state.
Required invariant
For every asynchronous AI operation or streamed chunk that can mutate project-owned state or project-owned assets:
origin = project A
↓
async work / streaming
↓
active project becomes B
↓
late result/chunk MUST NOT mutate B
late asset write MUST NOT become owned/visible as B's asset
Use the shared project-identity authority introduced by #707 where appropriate. A request/generation token may supplement identity checks but must not replace originating-project identity.
Acceptance criteria
Scope boundary
This issue is about cross-project mutation / asset authority only.
Do not broaden it into the general Wave 0C cancellation/AbortController redesign unless cancellation is strictly required to enforce mutation authority. General cancellation lifecycle work remains owned by #704 Wave 0C.
Do not perform provider/model-catalog, capability-matrix, connection-test, or broader AI architecture work here.
Severity
Treat as a data-integrity follow-up to Wave 0B. P1 until the audit proves or disproves a direct reproducible cross-project overwrite; elevate if a deterministic corruption path is confirmed.
Context
Follow-up from #704 Wave 0B / PR #707.
PR #707 correctly closed the known cross-project mutation races in character/world profile generation, field regeneration, template application, outline generation/application, and snapshot restore. Review convergence also added same-ID project-incarnation tracking, fail-closed identity comparison, stale-outline-preview rejection, and regression coverage.
A final post-merge completeness sweep shows that #707 did not modify the remaining asynchronous AI paths below. They therefore still require an explicit mutation-authority audit rather than being implicitly considered closed by Wave 0B.
Residual surfaces to audit
1. Character portrait generation / refinement
generateCharacterPortraitThunkperforms asynchronous AI image generation and then calls:The thunk later fulfills with
{ characterId }, andprojectSliceapplieshasAvatar: trueto the then-current project state.Audit both portrait entry points in
useCharacterView:2. World image generation / refinement
generateWorldImageThunkfollows the analogous pattern:The fulfilled reducer updates
hasAmbianceImagein the then-current project state.Audit both world-image entry points in
useWorldView:3. Asset-storage ownership
storageService.saveImage(id, base64Data)is keyed by entity id and does not carry an explicit project id at this boundary.Determine, for both IndexedDB and filesystem backends, whether a late image result originating under project A can overwrite, alias, or otherwise affect an asset later observed under project B when entity ids collide or project state changes while generation is in flight.
Do not assume Redux protection alone proves asset safety; prove the storage ownership semantics separately.
4. Character interview streaming
Audit the
project/streamInterviewChunkpath and its streaming producer(s). The reducer resolvescharacterId/interviewIdagainst the currently activestate.data.characterInterviews.Determine whether a stream originating in project A can continue dispatching chunks after project B becomes active and mutate project-B-owned interview state.
Required invariant
For every asynchronous AI operation or streamed chunk that can mutate project-owned state or project-owned assets:
Use the shared project-identity authority introduced by #707 where appropriate. A request/generation token may supplement identity checks but must not replace originating-project identity.
Acceptance criteria
pnpm run ci:prepush, exact-head CI/CD and CodeQL are green before merge.Scope boundary
This issue is about cross-project mutation / asset authority only.
Do not broaden it into the general Wave 0C cancellation/AbortController redesign unless cancellation is strictly required to enforce mutation authority. General cancellation lifecycle work remains owned by #704 Wave 0C.
Do not perform provider/model-catalog, capability-matrix, connection-test, or broader AI architecture work here.
Severity
Treat as a data-integrity follow-up to Wave 0B. P1 until the audit proves or disproves a direct reproducible cross-project overwrite; elevate if a deterministic corruption path is confirmed.