feat(voice): keep the transcript on the server
The review sheet rendered the full dictation at the top of the modal, unconditionally. A raw transcript can carry the patient's spoken name — the spec said so itself in §9, while §10 said telemetry must never contain it. The transcript no longer reaches the browser by any route: - removed from the success response (VoiceExtractionResponse is now plain ResolvedExtraction, which never had the field) - removed from the error body. VOICE_EXTRACT_FAILED carried details.transcript for a salvage dialog that was never built, and ApiError['details'] is an array, so the shape never even matched — it was serialized onto the wire and dropped - removed from the sheet, and from VoiceExtractionResult. tsc proves that <p> was the only reader in the whole frontend It is logged instead: one info line per recording, written immediately after the emptiness check so a failed extraction still records it, and deliberately outside logTelemetry so that method's patient-free guarantee stays literally true. Accepted consequence, recorded in §10: patient words now persist in production server logs at default level, so whatever retention and access control applies to those logs applies to dictation. The repo's other sensitive-text path (openrouter.provider.ts) uses debug level with truncation; moving this line to debug is a one-word change. Transcript salvage is dropped rather than deferred, which settles §11 open item 16 by taking its second option. When extraction fails the clinician re-dictates; an operator can read the words in the log, the person who spoke them cannot. Spec: §7, §9 and §10 rewritten, item 16 resolved, decisions 51-53 added, and decisions 10 and 25 marked superseded so the log stops contradicting itself. No automated coverage for the response shape or the log line: there is no voice.service.spec.ts — the service is I/O orchestration and has never been unit tested. Removing the type field is what proves no reader survives. Gates: backend 216 tests, nest build, ESLint clean on the voice module; frontend tsc --noEmit clean, 52 Vitest tests, next build clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -59,7 +59,6 @@ const LAB_DEPENDENT = new Set(['prosthesis']);
|
||||
|
||||
function baseResult(overrides: Partial<VoiceExtractionResult> = {}): VoiceExtractionResult {
|
||||
return {
|
||||
transcript: '',
|
||||
treatmentType: 'restoration',
|
||||
teeth: [],
|
||||
toothSelectionGroups: [],
|
||||
|
||||
@@ -219,10 +219,6 @@ export function VoiceReviewSheet({
|
||||
{t('voiceReviewTitle')}
|
||||
</h2>
|
||||
|
||||
<p className="mt-2 rounded-[var(--radius-md)] bg-background-card/60 px-3 py-2 text-sm text-text-secondary">
|
||||
{effective.transcript}
|
||||
</p>
|
||||
|
||||
{nothingToApply ? (
|
||||
<p className="mt-4 text-sm text-text-secondary">{t('voiceNothingExtracted')}</p>
|
||||
) : (
|
||||
|
||||
@@ -46,7 +46,10 @@ export interface VoiceProsthesisAssignment {
|
||||
}
|
||||
|
||||
export interface VoiceExtractionResult {
|
||||
transcript: string;
|
||||
/**
|
||||
* No transcript. A raw dictation can carry the patient's spoken name, so the server never
|
||||
* sends it — it is logged there instead (spec §10).
|
||||
*/
|
||||
treatmentType: string | null;
|
||||
teeth: FdiToothId[];
|
||||
toothSelectionGroups: ToothSelectionGroup[];
|
||||
|
||||
Reference in New Issue
Block a user