Closer/firestore.indexes.json

79 lines
1.5 KiB
JSON
Raw Normal View History

{
"indexes": [
{
"collectionGroup": "invites",
"queryScope": "COLLECTION",
"fields": [
feat(functions): hourly cleanup of expired restore requests (Future.md security #1) A restore_requests doc left behind — partner wrapped the couple key but the recipient never completed, or nobody ever answered — keeps its ECIES keybox forever. It's sealed to the recipient alone, so it's not exploitable, but key material has no business lying around, and expiry was enforced only client-side (fulfil-time check, delete-before-re-request, delete-on-complete): a request whose device disappeared simply lived forever. cleanupExpiredRestoreRequests runs hourly (requests expire in 30 min, so a stranded keybox now lives ~1.5 h at most) over a collectionGroup query on expiresAt — chosen over iterating couples deliberately, because it also reaps requests orphaned under already-deleted couple docs, which a parent iteration can never see. Backed by a new COLLECTION_GROUP fieldOverride on restore_requests.expiresAt (expiresAt is epoch millis, not a Timestamp, which also rules out native Firestore TTL). Deletes only on positive evidence: a pure predicate re-verifies every query hit (a real expiresAt past a 5-min grace so a mid-completion restore is never raced; a day-old createdAt as the defensive fallback when expiresAt is unusable; neither → leave it and log). Status is deliberately irrelevant — a DECLINED-after-READY doc still carries the keybox. Requests that expired recently while still waiting on someone (REQUESTED/READY) nudge the requester — "start a new one whenever you're ready" — through the house pipeline (notification_queue + sendPushToUser, quiet hours respected). The doc is deleted BEFORE the nudge, so a notify failure costs a nudge, never a duplicate; a 2-h notify window keeps the first deploy from blasting the ancient backlog. queueAndPush moves from being file-local in onRestoreRequested.ts to a shared notifications/queueAndPush.ts — the cleanup needed identical semantics, and two copies of notification plumbing is how the same bug ends up existing twice. Sweep never throws (a scheduled-function throw retries in a storm; the next hourly run IS the retry): per-doc Promise.allSettled, one summary log line via the structured logger. 15 new tests; grace-window mutation check kills exactly the guard test. Functions suite 98/98, tsc clean. Deploy (scoped — the RevenueCat webhook must stay undeployed): firebase deploy --only firestore:indexes, then --only functions:cleanupExpiredRestoreRequests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 02:32:59 -05:00
{
"fieldPath": "inviterUserId",
"order": "ASCENDING"
},
{
"fieldPath": "createdAt",
"order": "DESCENDING"
}
]
},
{
"collectionGroup": "capsules",
"queryScope": "COLLECTION_GROUP",
"fields": [
feat(functions): hourly cleanup of expired restore requests (Future.md security #1) A restore_requests doc left behind — partner wrapped the couple key but the recipient never completed, or nobody ever answered — keeps its ECIES keybox forever. It's sealed to the recipient alone, so it's not exploitable, but key material has no business lying around, and expiry was enforced only client-side (fulfil-time check, delete-before-re-request, delete-on-complete): a request whose device disappeared simply lived forever. cleanupExpiredRestoreRequests runs hourly (requests expire in 30 min, so a stranded keybox now lives ~1.5 h at most) over a collectionGroup query on expiresAt — chosen over iterating couples deliberately, because it also reaps requests orphaned under already-deleted couple docs, which a parent iteration can never see. Backed by a new COLLECTION_GROUP fieldOverride on restore_requests.expiresAt (expiresAt is epoch millis, not a Timestamp, which also rules out native Firestore TTL). Deletes only on positive evidence: a pure predicate re-verifies every query hit (a real expiresAt past a 5-min grace so a mid-completion restore is never raced; a day-old createdAt as the defensive fallback when expiresAt is unusable; neither → leave it and log). Status is deliberately irrelevant — a DECLINED-after-READY doc still carries the keybox. Requests that expired recently while still waiting on someone (REQUESTED/READY) nudge the requester — "start a new one whenever you're ready" — through the house pipeline (notification_queue + sendPushToUser, quiet hours respected). The doc is deleted BEFORE the nudge, so a notify failure costs a nudge, never a duplicate; a 2-h notify window keeps the first deploy from blasting the ancient backlog. queueAndPush moves from being file-local in onRestoreRequested.ts to a shared notifications/queueAndPush.ts — the cleanup needed identical semantics, and two copies of notification plumbing is how the same bug ends up existing twice. Sweep never throws (a scheduled-function throw retries in a storm; the next hourly run IS the retry): per-doc Promise.allSettled, one summary log line via the structured logger. 15 new tests; grace-window mutation check kills exactly the guard test. Functions suite 98/98, tsc clean. Deploy (scoped — the RevenueCat webhook must stay undeployed): firebase deploy --only firestore:indexes, then --only functions:cleanupExpiredRestoreRequests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 02:32:59 -05:00
{
"fieldPath": "status",
"order": "ASCENDING"
},
{
"fieldPath": "unlockAt",
"order": "ASCENDING"
}
]
},
{
"collectionGroup": "questions",
"queryScope": "COLLECTION",
"fields": [
feat(functions): hourly cleanup of expired restore requests (Future.md security #1) A restore_requests doc left behind — partner wrapped the couple key but the recipient never completed, or nobody ever answered — keeps its ECIES keybox forever. It's sealed to the recipient alone, so it's not exploitable, but key material has no business lying around, and expiry was enforced only client-side (fulfil-time check, delete-before-re-request, delete-on-complete): a request whose device disappeared simply lived forever. cleanupExpiredRestoreRequests runs hourly (requests expire in 30 min, so a stranded keybox now lives ~1.5 h at most) over a collectionGroup query on expiresAt — chosen over iterating couples deliberately, because it also reaps requests orphaned under already-deleted couple docs, which a parent iteration can never see. Backed by a new COLLECTION_GROUP fieldOverride on restore_requests.expiresAt (expiresAt is epoch millis, not a Timestamp, which also rules out native Firestore TTL). Deletes only on positive evidence: a pure predicate re-verifies every query hit (a real expiresAt past a 5-min grace so a mid-completion restore is never raced; a day-old createdAt as the defensive fallback when expiresAt is unusable; neither → leave it and log). Status is deliberately irrelevant — a DECLINED-after-READY doc still carries the keybox. Requests that expired recently while still waiting on someone (REQUESTED/READY) nudge the requester — "start a new one whenever you're ready" — through the house pipeline (notification_queue + sendPushToUser, quiet hours respected). The doc is deleted BEFORE the nudge, so a notify failure costs a nudge, never a duplicate; a 2-h notify window keeps the first deploy from blasting the ancient backlog. queueAndPush moves from being file-local in onRestoreRequested.ts to a shared notifications/queueAndPush.ts — the cleanup needed identical semantics, and two copies of notification plumbing is how the same bug ends up existing twice. Sweep never throws (a scheduled-function throw retries in a storm; the next hourly run IS the retry): per-doc Promise.allSettled, one summary log line via the structured logger. 15 new tests; grace-window mutation check kills exactly the guard test. Functions suite 98/98, tsc clean. Deploy (scoped — the RevenueCat webhook must stay undeployed): firebase deploy --only firestore:indexes, then --only functions:cleanupExpiredRestoreRequests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 02:32:59 -05:00
{
"fieldPath": "active",
"order": "ASCENDING"
},
{
"fieldPath": "isPremium",
"order": "ASCENDING"
}
]
},
{
"collectionGroup": "bucket_list",
"queryScope": "COLLECTION",
"fields": [
feat(functions): hourly cleanup of expired restore requests (Future.md security #1) A restore_requests doc left behind — partner wrapped the couple key but the recipient never completed, or nobody ever answered — keeps its ECIES keybox forever. It's sealed to the recipient alone, so it's not exploitable, but key material has no business lying around, and expiry was enforced only client-side (fulfil-time check, delete-before-re-request, delete-on-complete): a request whose device disappeared simply lived forever. cleanupExpiredRestoreRequests runs hourly (requests expire in 30 min, so a stranded keybox now lives ~1.5 h at most) over a collectionGroup query on expiresAt — chosen over iterating couples deliberately, because it also reaps requests orphaned under already-deleted couple docs, which a parent iteration can never see. Backed by a new COLLECTION_GROUP fieldOverride on restore_requests.expiresAt (expiresAt is epoch millis, not a Timestamp, which also rules out native Firestore TTL). Deletes only on positive evidence: a pure predicate re-verifies every query hit (a real expiresAt past a 5-min grace so a mid-completion restore is never raced; a day-old createdAt as the defensive fallback when expiresAt is unusable; neither → leave it and log). Status is deliberately irrelevant — a DECLINED-after-READY doc still carries the keybox. Requests that expired recently while still waiting on someone (REQUESTED/READY) nudge the requester — "start a new one whenever you're ready" — through the house pipeline (notification_queue + sendPushToUser, quiet hours respected). The doc is deleted BEFORE the nudge, so a notify failure costs a nudge, never a duplicate; a 2-h notify window keeps the first deploy from blasting the ancient backlog. queueAndPush moves from being file-local in onRestoreRequested.ts to a shared notifications/queueAndPush.ts — the cleanup needed identical semantics, and two copies of notification plumbing is how the same bug ends up existing twice. Sweep never throws (a scheduled-function throw retries in a storm; the next hourly run IS the retry): per-doc Promise.allSettled, one summary log line via the structured logger. 15 new tests; grace-window mutation check kills exactly the guard test. Functions suite 98/98, tsc clean. Deploy (scoped — the RevenueCat webhook must stay undeployed): firebase deploy --only firestore:indexes, then --only functions:cleanupExpiredRestoreRequests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 02:32:59 -05:00
{
"fieldPath": "category",
"order": "ASCENDING"
},
{
"fieldPath": "addedAt",
"order": "DESCENDING"
}
]
}
],
"fieldOverrides": [
{
"collectionGroup": "invite_attempts",
"fieldPath": "expiresAt",
"ttl": true,
"indexes": []
feat(functions): hourly cleanup of expired restore requests (Future.md security #1) A restore_requests doc left behind — partner wrapped the couple key but the recipient never completed, or nobody ever answered — keeps its ECIES keybox forever. It's sealed to the recipient alone, so it's not exploitable, but key material has no business lying around, and expiry was enforced only client-side (fulfil-time check, delete-before-re-request, delete-on-complete): a request whose device disappeared simply lived forever. cleanupExpiredRestoreRequests runs hourly (requests expire in 30 min, so a stranded keybox now lives ~1.5 h at most) over a collectionGroup query on expiresAt — chosen over iterating couples deliberately, because it also reaps requests orphaned under already-deleted couple docs, which a parent iteration can never see. Backed by a new COLLECTION_GROUP fieldOverride on restore_requests.expiresAt (expiresAt is epoch millis, not a Timestamp, which also rules out native Firestore TTL). Deletes only on positive evidence: a pure predicate re-verifies every query hit (a real expiresAt past a 5-min grace so a mid-completion restore is never raced; a day-old createdAt as the defensive fallback when expiresAt is unusable; neither → leave it and log). Status is deliberately irrelevant — a DECLINED-after-READY doc still carries the keybox. Requests that expired recently while still waiting on someone (REQUESTED/READY) nudge the requester — "start a new one whenever you're ready" — through the house pipeline (notification_queue + sendPushToUser, quiet hours respected). The doc is deleted BEFORE the nudge, so a notify failure costs a nudge, never a duplicate; a 2-h notify window keeps the first deploy from blasting the ancient backlog. queueAndPush moves from being file-local in onRestoreRequested.ts to a shared notifications/queueAndPush.ts — the cleanup needed identical semantics, and two copies of notification plumbing is how the same bug ends up existing twice. Sweep never throws (a scheduled-function throw retries in a storm; the next hourly run IS the retry): per-doc Promise.allSettled, one summary log line via the structured logger. 15 new tests; grace-window mutation check kills exactly the guard test. Functions suite 98/98, tsc clean. Deploy (scoped — the RevenueCat webhook must stay undeployed): firebase deploy --only firestore:indexes, then --only functions:cleanupExpiredRestoreRequests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 02:32:59 -05:00
},
{
"collectionGroup": "restore_requests",
"fieldPath": "expiresAt",
"indexes": [
{
"queryScope": "COLLECTION_GROUP",
"order": "ASCENDING"
}
]
}
]
}