Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 0 additions & 47 deletions backend/database/dream_profile.py

This file was deleted.

14 changes: 0 additions & 14 deletions backend/database/dream_store.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Transactional Dream admission and conservative stale-lease settlement.

Check warning on line 1 in backend/database/dream_store.py

View workflow job for this annotation

GitHub Actions / Hygiene

Large changed file

backend/database/dream_store.py is 829 lines; consider splitting files over 800 lines.

Abandoned workers retain their reservation and pending dirty versions. New
admission and reclamation share one transaction, fenced by the current run_id.
Expand Down Expand Up @@ -208,7 +208,7 @@
return usage


def acquire(

Check warning on line 211 in backend/database/dream_store.py

View workflow job for this annotation

GitHub Actions / Hygiene

Long function

acquire is 226 lines; consider extracting focused helpers over 150 lines.
uid,
caps: Caps,
*,
Expand Down Expand Up @@ -315,8 +315,6 @@
'hunt': not has_dirty,
'dry_hunts': _dry_hunts(data),
'backoff_would_defer': backoff_would_defer and not has_dirty,
# Weekly whole-corpus state pass cadence; derived from the state doc already read.
'state_pass_due': _state_pass_due(data, now),
'canary': canary,
'mode': 'shadow' if canary else mode(),
'size_bucket': data.get('size_bucket', 'light'),
Expand Down Expand Up @@ -467,16 +465,6 @@
raise RuntimeError('dream_lease_lost')


def _state_pass_due(data, now):
# Kept here (not utils.dream_state_pass) so acquire stays import-light.
try:
days = max(1, int(os.getenv('DREAM_STATE_PASS_WEEK_DAYS', '7') or 7))
except ValueError:
days = 7
last = data.get('last_state_pass_at')
return not isinstance(last, datetime) or now - last >= timedelta(days=days)


def _dry_hunts(data):
try:
return max(0, int(data.get('dry_hunts', 0) or 0))
Expand All @@ -484,7 +472,7 @@
return 0


def finish(

Check warning on line 475 in backend/database/dream_store.py

View workflow job for this annotation

GitHub Actions / Hygiene

Long function

finish is 270 lines; consider extracting focused helpers over 150 lines.
uid,
lease,
report,
Expand Down Expand Up @@ -519,7 +507,7 @@
deferred_ids = {dirty_id(*ref.split('/', 1)) for ref in deferred - set(seeded)}

@firestore.transactional
def complete(tx):

Check warning on line 510 in backend/database/dream_store.py

View workflow job for this annotation

GitHub Actions / Hygiene

Long function

complete is 233 lines; consider extracting focused helpers over 150 lines.
data = state.get(transaction=tx).to_dict() or {}
current_lease = data.get('lease') or {}
if current_lease.get('run_id') != lease['run_id']:
Expand Down Expand Up @@ -730,8 +718,6 @@
patch['cleanup_cursor'] = cleanup_cursor
if not zero_refund:
patch['last_pass_at'] = lease['started_at']
if report.get('state_pass_done'):
patch['last_state_pass_at'] = lease['started_at']
for ref in sorted(set(reviewed) - deferred)[:8]:
tx.set(
state.collection('reviews').document(review_id(ref)),
Expand Down
10 changes: 10 additions & 0 deletions backend/database/redis_db.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ast

Check warning on line 1 in backend/database/redis_db.py

View workflow job for this annotation

GitHub Actions / Hygiene

Large changed file

backend/database/redis_db.py is 1598 lines; consider splitting files over 800 lines.
import base64
import json
from typing import Any, Callable, Dict, List, Optional, TypeVar, Union, cast
Expand Down Expand Up @@ -1406,6 +1406,16 @@
return result is not None


def try_acquire_ai_profile_refresh_lock(uid: str, ttl: int = 60 * 60) -> bool:
"""One backend AI-profile refresh per owner per ``ttl``; also throttles retries after a failure."""
result = r.set(f'users:{uid}:ai_profile_refresh_lock', '1', ex=ttl, nx=True)
return result is not None


def release_ai_profile_refresh_lock(uid: str) -> None:
r.delete(f'users:{uid}:ai_profile_refresh_lock')


def try_acquire_daily_wear_lock(uid: str, date: str, ttl: int = 60 * 60 * 24) -> bool:
"""At most one wear FCM per uid per UTC day. True iff this caller may send."""
result = r.set(f'users:{uid}:daily_wear_lock:{date}', '1', ex=ttl, nx=True)
Expand Down
49 changes: 47 additions & 2 deletions backend/database/users.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from database.people_generation import bump_people_generation, write_person

Check warning on line 1 in backend/database/users.py

View workflow job for this annotation

GitHub Actions / Hygiene

Large changed file

backend/database/users.py is 2566 lines; consider splitting files over 800 lines.
from database.person_speech_samples import add_sample_transaction
from database.people_projection import project_people
from config.refactor_modes import serving_for
Expand Down Expand Up @@ -2501,21 +2501,66 @@
return get_or_fetch(_USER_AI_PROFILE_CACHE, uid, lambda: _get_ai_user_profile_from_firestore(uid))


def get_ai_user_profile_fresh(uid: str) -> Optional[dict]:
"""Uncached read for read-modify-write and refresh cadence decisions."""
return _get_ai_user_profile_from_firestore(uid)


def update_ai_user_profile(
uid: str, profile_text: str = None, generated_at=None, data_sources_used: int = None
uid: str,
profile_text: str = None,
generated_at=None,
data_sources_used: int = None,
source: Optional[str] = None,
) -> dict:
"""Update AI user profile. Only writes non-None fields (partial update)."""
"""Update AI user profile. Only writes non-None fields (partial update).

``source`` records who wrote the text (``user_edit``/``exploration`` from clients);
a legacy PATCH without one is recorded as ``client``.
"""
# Read existing profile directly from Firestore — never from cache — because
# this is a read-modify-write path. Using a stale cached projection here
# could overwrite newer profile fields.
existing = _get_ai_user_profile_from_firestore(uid) or {}
if profile_text is not None:
existing['profile_text'] = profile_text
existing['source'] = source or 'client'
if generated_at is not None:
existing['generated_at'] = generated_at
elif profile_text is not None and source:
existing['generated_at'] = datetime.now(timezone.utc).isoformat()
if data_sources_used is not None:
existing['data_sources_used'] = data_sources_used
user_ref = db.collection('users').document(uid)
user_ref.update({'ai_user_profile': existing})
invalidate(_USER_AI_PROFILE_CACHE, uid)
return existing


@transactional
def _write_ai_user_profile_if_unchanged_txn(transaction, user_ref, expected_text: str, profile: dict) -> bool:
snapshot = user_ref.get(transaction=transaction)
if not snapshot.exists:
return False
current = (snapshot.to_dict() or {}).get('ai_user_profile') or {}
if str(current.get('profile_text') or '').strip() != expected_text:
return False
transaction.update(user_ref, {'ai_user_profile': profile})
return True


def write_ai_user_profile_if_unchanged(uid: str, expected_text: str, profile: dict) -> bool:
"""Replace the generated profile only if the stored text is still the synthesis input.

A user edit or delete that lands while the model runs wins; returns False then.
"""
client = get_firestore_client()
user_ref = client.collection('users').document(uid)
written = _write_ai_user_profile_if_unchanged_txn(client.transaction(), user_ref, expected_text, profile)
invalidate(_USER_AI_PROFILE_CACHE, uid)
return written


def delete_ai_user_profile(uid: str) -> None:
db.collection('users').document(uid).update({'ai_user_profile': firestore.DELETE_FIELD})
invalidate(_USER_AI_PROFILE_CACHE, uid)
2 changes: 0 additions & 2 deletions backend/deploy/runtime_env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4909,7 +4909,6 @@ environments:
category: reliability
REVIEW_SURFACE_MODE: {value: 'on', category: rollout}
DREAM_AGENT_PASSES_PER_DAY: {value: '48', category: rollout}
DREAM_STATE_PASS_MODE: {value: shadow, category: rollout}
DREAM_AGENT_HUNT_INTERVAL_HOURS: {value: '2', category: rollout}
DREAM_AGENT_MANUAL_RUNS_PER_DAY: {value: '3', category: rollout}
DREAM_AGENT_DAILY_USD: {value: '20', category: rollout}
Expand Down Expand Up @@ -5422,7 +5421,6 @@ environments:
value: '8'
category: reliability
DREAM_AGENT_PASSES_PER_DAY: {value: '48', category: rollout}
DREAM_STATE_PASS_MODE: {value: shadow, category: rollout}
MENTOR_PIPELINE: {value: cohort, category: rollout}
SPEAKER_GROUPING_SHADOW: {value: cohort, category: rollout}
SPEAKER_GROUPING_SHADOW_PERCENT: {value: '10', category: rollout}
Expand Down
6 changes: 0 additions & 6 deletions backend/deploy/runtime_env/prod.overlay.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1168,9 +1168,6 @@ overlay:
DREAM_AGENT_PASSES_PER_DAY:
value: '48'
category: rollout
DREAM_STATE_PASS_MODE:
value: shadow
category: rollout
DREAM_AGENT_HUNT_INTERVAL_HOURS:
value: '2'
category: rollout
Expand Down Expand Up @@ -1572,9 +1569,6 @@ overlay:
DREAM_AGENT_PASSES_PER_DAY:
value: '48'
category: rollout
DREAM_STATE_PASS_MODE:
value: shadow
category: rollout
MENTOR_PIPELINE:
value: cohort
category: rollout
Expand Down
Loading
Loading