Repository navigation
Enforce the transcription cap where cloud STT is dispatched (flag off) #21866
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
1f6bb7e
fix: enforce transcription cap at funded STT dispatch (flag off)
Git-on-my-level 2a07b6b
fix: retain deferred live wire ledger binding through cap pauses
Git-on-my-level 9581c42
fix: avoid labeling unresolved cap observations as available
Git-on-my-level a85f94d
Merge remote-tracking branch 'origin/main' into lane/sub-cap-dispatch
Git-on-my-level acb09a1
fix: export GPU cap audio counters and preserve exhaustion provenance
Git-on-my-level 9dfdcfa
fix: publish additive held-audio result in client API snapshot
Git-on-my-level ca343a5
fix: regenerate client DTOs for additive held-audio result
Git-on-my-level e26e11c
fix: declare generated cap response DTO as internal mobile change
Git-on-my-level 89120f3
fix: keep cap recovery and policy imports within their owning runtime
Git-on-my-level 550e565
fix: preserve held sync ownership and recovery provenance
Git-on-my-level 36a2f2c
Merge remote-tracking branch 'origin/main' into lane/sub-cap-dispatch
Git-on-my-level 2c448f4
test: align isolated calendar window regressions with current renderer
Git-on-my-level 7c0f57f
test: isolate parity inference and assert usage protection structurally
Git-on-my-level 4b7a9e2
Merge remote-tracking branch 'origin/main' into lane/sub-cap-dispatch
Git-on-my-level 85e1c6e
fix: retain hold admission and provider provenance across recovery
Git-on-my-level c54f5a8
fix: type heterogeneous held-audio recovery receipts explicitly
Git-on-my-level f9605a6
fix: preserve legacy sync usage arguments when no audio is held
Git-on-my-level e2f6d5f
Merge remote-tracking branch 'origin/main' into lane/sub-cap-dispatch
Git-on-my-level 914beb1
fix: validate recap envelopes through the generated response decoder
Git-on-my-level a8b4119
Merge remote-tracking branch 'origin/main' into lane/sub-cap-dispatch
Git-on-my-level 5c5de21
Merge remote-tracking branch 'origin/main' into lane/sub-cap-dispatch
Git-on-my-level fc2c4c1
Merge remote-tracking branch 'origin/main' into lane/sub-cap-dispatch
Git-on-my-level b44eff7
Fix held STT recovery dispatch fencing and oversized retry admission
Git-on-my-level 99c9a95
Preserve flag-off batch admission before duration coercion
Git-on-my-level cdae9d7
Merge remote-tracking branch 'origin/main' into lane/sub-cap-dispatch
Git-on-my-level f2fbef1
fix: reconcile held STT attempts without stranding audio or siblings
Git-on-my-level d54c929
fix: type recovery boundaries and share held-audio uploads
Git-on-my-level c47bc2f
Merge remote-tracking branch 'origin/main' into lane/sub-cap-dispatch
Git-on-my-level File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
10 changes: 10 additions & 0 deletions
10
.github/failure-classes/FC-stt-allowance-not-enforced-at-dispatch.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "schema_version": 1, | ||
| "id": "FC-stt-allowance-not-enforced-at-dispatch", | ||
| "violated_contract": "The transcription cap is computed but not enforced where Omi-funded provider audio is dispatched; replay, fallback and delayed execution can bypass request-time allowance checks.", | ||
| "canonical_prevention": "Authorize actual provider funding at creation and every send, share live admission across channels and successors, and recheck queued execution before dispatch. Persist denied sync audio before acknowledging it. Fence provider-bound recovery receipts before side effects and atomically prove ingestion, clear known non-calls, and reconcile ambiguous attempts with one bounded re-dispatch while retaining terminal failures and finalizing siblings. Exercise both flag states at provider and queue boundaries, including oversized empty retries without spending failure attempts when held.", | ||
| "canonical_prevention_artifact": ["backend/tests/unit/test_stt_cap_dispatch.py", "backend/tests/unit/test_sync_cap_hold.py", "backend/tests/unit/test_batch_pipeline_admission.py", "backend/tests/unit/test_batch_pipeline_worker.py", "backend/tests/unit/test_sync_cap_coordinator.py", "backend/tests/unit/test_sync_cap_hold_recovery.py", "backend/tests/unit/test_sync_cap_recovery_checkpoint.py", "backend/tests/unit/test_sync_cap_recovery_dispatch_signal.py", "backend/tests/unit/test_sync_cap_recovery_plan.py", "backend/tests/unit/test_sync_cap_reconciliation_metrics.py", "backend/tests/unit/test_prerecorded_dispatch_selection.py", "backend/tests/unit/test_sync_cap_content.py"], | ||
| "evidence_prs": [], | ||
| "scope_hints": ["backend/routers/listen/", "backend/utils/stt/", "backend/utils/sync/", "backend/utils/batch_pipeline/"], | ||
| "status": "open" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "kind": "none" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| """Dark dispatch policy. No environment/deployment changes enable this implicitly.""" | ||
|
|
||
| import os | ||
|
|
||
|
|
||
| def enabled() -> bool: | ||
| return os.getenv('STT_CAP_DISPATCH_ENFORCEMENT', 'false').lower() == 'true' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| """Transfer fenced sync-ledger ownership to durable, recoverable audio holds. | ||
|
|
||
| A held upload is acknowledged, but is not proof of completed transcription. | ||
| Only settlement of every referenced hold can publish that proof. | ||
| """ | ||
|
|
||
| from datetime import datetime, timedelta, timezone | ||
| from typing import Any | ||
|
|
||
| from google.cloud import firestore | ||
|
|
||
| from database._client import get_firestore_client | ||
| from database.sync_ledger import ( | ||
| sync_content_reference, | ||
| sync_content_owner_matches, | ||
| is_valid_completed_sync_content_result, | ||
| is_valid_held_sync_content_result, | ||
| LEDGER_RETENTION_DAYS, | ||
| ) | ||
|
|
||
| PROTOCOL = 'stt_cap_hold_v1' | ||
|
|
||
|
|
||
| def mark_content_held( | ||
| uid: str, | ||
| content_id: str, | ||
| job_id: str, | ||
| result: dict, | ||
| hold_ids: list[str], | ||
| *, | ||
| run_token: str | None = None, | ||
| run_epoch: int | None = None, | ||
| ) -> bool: | ||
| if not hold_ids or not is_valid_held_sync_content_result(result): | ||
| raise ValueError('durable held audio receipts required') | ||
| client = get_firestore_client() | ||
| ref = sync_content_reference(client, uid, content_id) | ||
|
|
||
| @firestore.transactional | ||
| def commit(tx: Any) -> bool: | ||
| row = ref.get(transaction=tx).to_dict() or {} | ||
| if not sync_content_owner_matches(row, job_id, run_token, run_epoch): | ||
| return False | ||
| tx.update( | ||
| ref, | ||
| { | ||
| 'status': 'held', | ||
| 'dispatch_protocol': PROTOCOL, | ||
| 'result': result, | ||
| 'cap_hold_ids': sorted(set(hold_ids)), | ||
| 'pending_cap_hold_ids': sorted(set(hold_ids)), | ||
| 'updated_at': datetime.now(timezone.utc), | ||
| # An unprocessed capture must survive inactive accounts. | ||
| 'expires_at': firestore.DELETE_FIELD, | ||
| }, | ||
| ) | ||
| return True | ||
|
|
||
| return commit(client.transaction()) | ||
|
|
||
|
|
||
| def recovery_status(uid: str, content_id: str, hold_id: str) -> str: | ||
| row = sync_content_reference(get_firestore_client(), uid, content_id).get().to_dict() or {} | ||
| if row.get('status') == 'completed' and is_valid_completed_sync_content_result(row.get('result')): | ||
| return 'completed' | ||
| if row.get('dispatch_protocol') == PROTOCOL and row.get('status') == 'held': | ||
| if hold_id not in row.get('cap_hold_ids', []): | ||
| return 'waiting' | ||
| return 'ready' if hold_id in row.get('pending_cap_hold_ids', []) else 'settled' | ||
| return 'waiting' # The original worker still owns preparation/retry. | ||
|
|
||
|
|
||
| def settle_hold(uid: str, content_id: str, hold_id: str, response: dict, pieces: int) -> bool: | ||
| """Idempotently join one recovered hold; never overwrite another owner.""" | ||
| client = get_firestore_client() | ||
| ref = sync_content_reference(client, uid, content_id) | ||
|
|
||
| @firestore.transactional | ||
| def commit(tx: Any) -> bool: | ||
| row = ref.get(transaction=tx).to_dict() or {} | ||
| if row.get('status') == 'completed': | ||
| return is_valid_completed_sync_content_result(row.get('result')) | ||
| if row.get('dispatch_protocol') != PROTOCOL or row.get('status') != 'held': | ||
| return False | ||
| if hold_id not in row.get('cap_hold_ids', []): | ||
| return False | ||
| pending = set(row.get('pending_cap_hold_ids') or []) | ||
| if hold_id not in pending: | ||
| return True | ||
| pending.remove(hold_id) | ||
| result = dict(row['result']) | ||
| for key in ('new_memories', 'updated_memories'): | ||
| result[key] = sorted(set(result.get(key) or []) | set(response.get(key) or [])) | ||
| result['recovered_segments'] = int(result.get('recovered_segments', 0)) + pieces | ||
| result['held_audio'] = bool(pending) | ||
| now = datetime.now(timezone.utc) | ||
| updates: dict[str, Any] = {'result': result, 'pending_cap_hold_ids': sorted(pending), 'updated_at': now} | ||
| if not pending: | ||
| # All source audio has reached content/usage settlement; only now | ||
| # can ordinary duplicate admission converge a completed result. | ||
| result.update(failed_segments=0, errors=[], outcome='success') | ||
| updates.update(status='completed', expires_at=now + timedelta(days=LEDGER_RETENTION_DAYS)) | ||
| tx.update(ref, updates) | ||
| return True | ||
|
|
||
| return commit(client.transaction()) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| """Piece-lineage proof committed in the exact conversation-ingest transaction.""" | ||
|
|
||
| import time | ||
| from typing import Any | ||
|
|
||
| from database._client import get_firestore_client | ||
|
|
||
|
|
||
| class RecoveryIngestFenced(RuntimeError): | ||
| """An expired/replaced recovery attempt cannot append conversation content.""" | ||
|
|
||
|
|
||
| def ingest_hooks(uid: str, context: dict, incoming: dict): | ||
| ref = ( | ||
| get_firestore_client() | ||
| .collection('users') | ||
| .document(uid) | ||
| .collection('stt_cap_holds') | ||
| .document(context['hold_id']) | ||
| ) | ||
| current = {} | ||
| piece = context['piece'] | ||
|
|
||
| def before(transaction): | ||
| row = ref.get(transaction=transaction).to_dict() or {} | ||
| receipt = (row.get('piece_receipts') or {}).get(piece) or {} | ||
| if ( | ||
| row.get('lease_token') != context['lease_token'] | ||
| or row.get('lease_until', 0) <= time.time() | ||
| or receipt.get('state') != 'dispatched' | ||
| or receipt.get('attempt', 1) != context['attempt'] | ||
| ): | ||
| raise RecoveryIngestFenced('held piece ingest ownership changed') | ||
| current.clear() | ||
| current.update(row.get('piece_ingests') or {}) | ||
|
|
||
| def after(transaction, assigned): | ||
| conversation, created, _ = assigned | ||
| cid = conversation['id'] | ||
| response: dict[str, Any] = {'new_memories': [], 'updated_memories': []} | ||
| repeat = conversation.get('_sync_lineage_repeat_only') | ||
| if not repeat: | ||
| response['new_memories' if created else 'updated_memories'] = [cid] | ||
| if conversation.get('sync_relevance') == 'keep' and ( | ||
| not repeat or conversation.get('_sync_lineage_completion_pending') | ||
| ): | ||
| response['_merged'] = {cid: incoming.get('language')} | ||
| current[piece] = { | ||
| **context['receipt'], | ||
| 'state': 'transcribed', | ||
| 'has_content': True, | ||
| 'response': response, | ||
| 'lineage_anchor': incoming['id'], | ||
| 'conversation_id': cid, | ||
| } | ||
| # No reads here: assignment has already staged its conversation writes. | ||
| transaction.update(ref, {'piece_ingests': dict(current)}) | ||
|
|
||
| return before, after | ||
|
|
||
|
|
||
| def ingested_piece(uid: str, hold_id: str, piece: str) -> dict | None: | ||
| row = ( | ||
| get_firestore_client() | ||
| .collection('users') | ||
| .document(uid) | ||
| .collection('stt_cap_holds') | ||
| .document(hold_id) | ||
| .get() | ||
| .to_dict() | ||
| or {} | ||
| ) | ||
| return (row.get('piece_ingests') or {}).get(piece) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a v2 hold is recovered while its original Redis job is still within the 24-hour reconciliation window, this transition updates only the Firestore content ledger.
GET /v2/sync-jobs/{job_id}continues reading the unchanged terminal Redis job, so it keeps returningheld_audio: true, zero/stale segment counts, and the pre-recovery memory lists even though recovery has completed. Update the matching Redis job after the final hold settles, or have status reads converge from the completed ledger.AGENTS.md reference: backend/AGENTS.md:L151-L151
Useful? React with 👍 / 👎.