Skip to content

fix(session): reclaim orphan session codes on create#162

Merged
gelbh merged 3 commits into
mainfrom
fix/lifecycle-reclaim-oneshot
Jul 24, 2026
Merged

fix(session): reclaim orphan session codes on create#162
gelbh merged 3 commits into
mainfrom
fix/lifecycle-reclaim-oneshot

Conversation

@gelbh

@gelbh gelbh commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Summary

  • On create, delete and reuse sessionCodes docs whose session is missing or already ended (instead of only regenerating).
  • endRemoteSession deletes the code document instead of marking status: "ended" (defense in depth; host UI prefers the endSession callable).
  • One-shot cleanup script stays agent-local under .cursor/scripts/ (not committed).

Test plan

  • npm test -- src/services/firestore/firestoreAnnotations.reclaim.test.ts
  • Create session after planting an orphan code doc → same code reusable
  • After deploy: dry-run then --apply oneshot (local script)

Summary by CodeRabbit

  • New Features
    • Session codes are now eligible for automatic reclaiming once their sessions have ended or become unavailable.
    • Ended session codes are removed so they can be reused for new sessions.
  • Bug Fixes
    • Prevented active session codes from being reclaimed or overwritten.
  • Tests
    • Added coverage to confirm reclaim behavior for ended, null, and undefined cases.
    • Updated emulator tests to verify that ended session codes no longer resolve.
  • Security / Rules
    • Adjusted delete permissions for session codes to allow cleanup only under valid conditions.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Session-code reclaimability now covers missing or ended session data. Session creation attempts to delete existing code documents before reuse, session ending deletes the associated code, and Firestore rules authorize eligible deletions. Tests cover helper behavior and post-ending lookup results.

Changes

Session code lifecycle

Layer / File(s) Summary
Reclaimability rules
src/services/firestore/firestoreAnnotations.ts, firestore.rules, src/services/firestore/firestoreAnnotations.reclaim.test.ts
The exported helper recognizes missing or ended sessions as reclaimable, Firestore permits eligible deletions, and tests reject active sessions.
Firestore code cleanup
src/services/firestore/firestoreAnnotations.ts, src/services/firestore/firestoreAnnotations.emulator.test.ts
createRemoteSession attempts to delete existing codes before reuse, endRemoteSession deletes the associated code, and emulator tests expect the code lookup to become missing.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • gelbh/jetlag#159: Implements related cleanup of orphaned or terminal session-code documents.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/lifecycle-reclaim-oneshot
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/lifecycle-reclaim-oneshot

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/services/firestore/firestoreAnnotations.reclaim.test.ts`:
- Around line 4-20: Expand the Vitest Firestore emulator coverage beyond
isReclaimableSessionForCode to exercise createRemoteSession and
endRemoteSession: reclaim missing and ended sessions, preserve active mappings,
delete only the ending session’s mapping, and verify persisted endedAt values
round-trip in the expected representation. Keep the tests focused on the changed
lifecycle behavior and its edge cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: adfe991c-82da-4490-93a1-75dbab5600bd

📥 Commits

Reviewing files that changed from the base of the PR and between 2e4464e and fdc3ea5.

📒 Files selected for processing (2)
  • src/services/firestore/firestoreAnnotations.reclaim.test.ts
  • src/services/firestore/firestoreAnnotations.ts

Comment on lines +4 to +20
describe("isReclaimableSessionForCode", () => {
it("reclaims missing sessions", () => {
expect(isReclaimableSessionForCode(null)).toBe(true);
expect(isReclaimableSessionForCode(undefined)).toBe(true);
});

it("reclaims ended sessions", () => {
expect(isReclaimableSessionForCode({ status: "ended" })).toBe(true);
expect(
isReclaimableSessionForCode({ endedAt: "2026-01-01T00:00:00.000Z" }),
).toBe(true);
});

it("does not reclaim live active sessions", () => {
expect(isReclaimableSessionForCode({ status: "active" })).toBe(false);
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

Cover the Firestore lifecycle, not only the predicate.

These tests do not exercise the changed createRemoteSession and endRemoteSession behavior. Add Vitest emulator coverage for reclaiming missing/ended sessions, preserving active mappings, deleting only the ending session’s mapping, and verifying the persisted endedAt representation round-trips correctly.

As per path instructions, tests should cover changed behavior and edge cases in the Vitest-focused scope.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/firestore/firestoreAnnotations.reclaim.test.ts` around lines 4 -
20, Expand the Vitest Firestore emulator coverage beyond
isReclaimableSessionForCode to exercise createRemoteSession and
endRemoteSession: reclaim missing and ended sessions, preserve active mappings,
delete only the ending session’s mapping, and verify persisted endedAt values
round-trip in the expected representation. Keep the tests focused on the changed
lifecycle behavior and its edge cases.

Source: Path instructions

@gelbh
gelbh force-pushed the fix/lifecycle-reclaim-oneshot branch from fbed16b to 420cec4 Compare July 24, 2026 21:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/services/firestore/firestoreAnnotations.ts (1)

760-767: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make session ending and code deletion retryable as one lifecycle operation.

The session update removes code before deleteDoc is confirmed. If deletion fails after the update, the operation rejects, but a retry reads a session without its code and can no longer identify the stale sessionCodes/{code} document. Use an atomic lifecycle write or retain durable cleanup state until deletion succeeds.

As per path instructions, Firestore services must cover “Offline write queuing and idempotency.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/firestore/firestoreAnnotations.ts` around lines 760 - 767,
Update the session-ending flow around the session update and sessionCodeDoc
deletion so the session code remains durably available until stale-code cleanup
succeeds, or perform both changes through an atomic Firestore lifecycle write.
Ensure retries can identify and delete the same sessionCodes/{code} document
after offline queuing or a partial failure, while preserving idempotent
behavior.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@firestore.rules`:
- Around line 778-787: Update the delete rule for the code mapping to reject
deletion solely by the host; allow it only when the linked session is missing or
ended, while retaining the signed-in, code-size, and sessionId validations.
Ensure the rule preserves authenticated-write checks and matches the TypeScript
session-ending lifecycle so active session mappings cannot be removed during
collision handling.

In `@src/services/firestore/firestoreAnnotations.ts`:
- Around line 276-283: Update the session-code deletion retry loop around
sessionCodeDoc and generateSessionCode so exhausting the retry limit never
leaves an unchecked candidate for the subsequent write. After the final failed
deletion, either throw immediately or verify the newly generated code before
proceeding, while preserving the existing retry behavior for attempts that
remain.

---

Outside diff comments:
In `@src/services/firestore/firestoreAnnotations.ts`:
- Around line 760-767: Update the session-ending flow around the session update
and sessionCodeDoc deletion so the session code remains durably available until
stale-code cleanup succeeds, or perform both changes through an atomic Firestore
lifecycle write. Ensure retries can identify and delete the same
sessionCodes/{code} document after offline queuing or a partial failure, while
preserving idempotent behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d28d2246-ecc5-4684-ac98-e768b94d871c

📥 Commits

Reviewing files that changed from the base of the PR and between fdc3ea5 and 420cec4.

📒 Files selected for processing (4)
  • firestore.rules
  • src/services/firestore/firestoreAnnotations.emulator.test.ts
  • src/services/firestore/firestoreAnnotations.reclaim.test.ts
  • src/services/firestore/firestoreAnnotations.ts

Comment thread firestore.rules
Comment on lines +778 to +787
// Host end path, or reclaim when the linked session is missing / ended.
allow delete: if isSignedIn()
&& code.size() == 4
&& resource.data.sessionId is string
&& (
resource.data.hostUid == request.auth.uid
|| !exists(/databases/$(database)/documents/sessions/$(resource.data.sessionId))
|| get(/databases/$(database)/documents/sessions/$(resource.data.sessionId)).data.status == 'ended'
|| get(/databases/$(database)/documents/sessions/$(resource.data.sessionId)).data.endedAt is string
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not allow deletion of an active session’s code.

The resource.data.hostUid == request.auth.uid branch permits deleting a code while its linked session is still active. Since createRemoteSession attempts this deletion for every collision, a host can remove an active mapping, reuse the code for a new session, and leave the old session pointing at the new mapping.

Restrict deletion to missing or ended linked sessions, or make the end update and deletion an atomic, end-specific operation.

As per path instructions, Firestore rules require auth checks on all writes and parity with the TypeScript lifecycle behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@firestore.rules` around lines 778 - 787, Update the delete rule for the code
mapping to reject deletion solely by the host; allow it only when the linked
session is missing or ended, while retaining the signed-in, code-size, and
sessionId validations. Ensure the rule preserves authenticated-write checks and
matches the TypeScript session-ending lifecycle so active session mappings
cannot be removed during collision handling.

Source: Path instructions

Comment on lines +276 to +283
try {
// Rules allow delete only for host, missing session, or ended session.
await deleteDoc(sessionCodeDoc(code));
break;
} catch {
code = generateSessionCode();
attempts += 1;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not use the unverified candidate after the retry limit.

After the eighth failed deletion, Line 281 generates a new code and the loop exits without checking whether that candidate already exists. The subsequent write can collide with an existing document and fail unexpectedly. Throw when the limit is exhausted or verify the final candidate before writing.

As per path instructions, Firestore services must cover “Offline write queuing and idempotency.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/firestore/firestoreAnnotations.ts` around lines 276 - 283,
Update the session-code deletion retry loop around sessionCodeDoc and
generateSessionCode so exhausting the retry limit never leaves an unchecked
candidate for the subsequent write. After the final failed deletion, either
throw immediately or verify the newly generated code before proceeding, while
preserving the existing retry behavior for attempts that remain.

Source: Path instructions

@gelbh
gelbh merged commit 2356808 into main Jul 24, 2026
8 checks passed
gelbh added a commit that referenced this pull request Jul 24, 2026
* fix(session): reclaim orphan session codes on create

* fix(session): allow orphan sessionCodes delete in rules

* test(session): expect missing code after endRemoteSession delete
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.

1 participant