Skip to content

Preserve private audio originals and confirm written answers - #2861

Merged
Chris0Jeky merged 3 commits into
mainfrom
codex/2808-audio-originals
Sep 10, 2026
Merged

Preserve private audio originals and confirm written answers#2861
Chris0Jeky merged 3 commits into
mainfrom
codex/2808-audio-originals

Conversation

@Chris0Jeky

@Chris0Jeky Chris0Jeky commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Private audio originals and written confirmation

Saving a recording from a Thinking Deck question now retains the original privately without pretending the audio was transcribed or the question answered. The owner can replay/download it, save separate written versions, and explicitly confirm one into private memory. Retrying an uncertain upload compares its stable upload ID and original bytes; stale question/recording revisions reject confirmation without losing drafts or original evidence.

This continues #2808 and is stacked on #2857. Existing Classic, Studio, Companion and Unified experiences share the same flow. No shared card change, automatic processing job, remote transcription or telemetry is introduced.

  • Native Capture/SourceAsset intake; SQLite chunks bounded at 64 KiB, owner-scoped deduplication/references, quota checks before reading, transactional rollback and erasure.
  • Separate Transcript payloads and immutable Representation headers/supersession; explicit human confirmation, with no invented processing run. Legacy representation backfill remains separate.
  • File/microphone draft UI with 2 MiB uploads and a 60-second recording cap, original playback, written alternatives, retained drafts, bounded request waits and recording/save navigation guards. Actor changes dispose private drafts; same-user token refresh retains them.
  • Both account exports include original object/reference/chunk and representation/answer links. Buffered export preflights/enforces a budget; streaming emits bounded chunks. Deleted-board originals remain exportable; account erasure removes the owned source graph. Shared exports exclude private answers.
  • Three additive migrations introduce the new storage/representation/answer tables. UPGRADING, product scope, interface map, STATUS, masterplan and validation ledger updated.

Verification

  • Full frontend: 6,330 passed, three existing skips across 409 files; typecheck and production build passed. Later identity guards: 20 targeted tests and typecheck passed. Scoped ESLint and 674 Markdown doc-link/governance checks passed.
  • Full backend command executed: 9,175 passed, 34 existing skips, one capture-export parity failure. Fixed the omitted nullable blob-reference field in the manual streaming writer; 34 scoped API tests then passed, including that failing test and an additional 7 MiB buffered-refusal/streamed-export case. The original full command is retained as a failure, not described as a green full rerun. CLI (243), architecture (28, one skip) and integration (seven, 29 skips) passed.
  • Chromium journey passed in 47.4 seconds: real synthetic WAV playback, accepted-upload/lost-response retry, one stored original, explicit written confirmation, reload across all four experiences, 375 px with no overflow or serious/critical axe findings, unchanged card.
  • Bounded independent Luna storage pass CLEAN; full audio integration pass found no HIGH/CRITICAL finding. Local-draft reload wording is a MEDIUM follow-through tracked under Continue prototype interactions, context and continuity after the switchable overhaul #2808. Hosted checks are pending.

Actual microphone hardware, physical mobile browsers, external transcription/provider quality and restore import are unverified. Account JSON is archival. Raw untranscribed user input has narrowly documented caption-lint exceptions; the UI retains written alternatives and never fabricates timed captions. No working database was migrated. Human decisions in OUTSTANDING_TASKS.md remain unchanged. The wider #2808 work remains open.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-10T00:02:04.827111Z b719cb8 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@Chris0Jeky Chris0Jeky moved this from Pending to Review in Taskdeck Execution Sep 9, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b719cb8c9f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +674 to +676
await WriteSourceRowsAsync(writer, "objects", _sourceStorage.ObjectsAsync(userId, cancellationToken), cancellationToken);
await WriteSourceRowsAsync(writer, "references", _sourceStorage.ReferencesAsync(userId, cancellationToken), cancellationToken);
await WriteSourceRowsAsync(writer, "chunks", _sourceStorage.ChunksAsync(userId, cancellationToken), cancellationToken);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Export source arrays from one database snapshot

If an audio upload commits after objects finishes but before references or chunks, these independent queries emit references and bytes for a BlobId that is absent from the exported object list; concurrent deletion can produce the inverse. The buffered path performs the same sequential reads, so either account-export format can produce an archival package that cannot be reconstructed or hash-verified. Read the entire source graph from one database snapshot or pin a fixed set of owned IDs.

Useful? React with 👍 / 👎.

public Task<Result<ThinkingAudioDto>> ConfirmAsync(Guid userId, Guid id, ThinkingAudioConfirmDto dto, CancellationToken ct) => TransactionAsync(async () =>
{
var answer = await OwnedAsync(userId, id, ct);
if (answer.ConfirmedMemoryId.HasValue) return await MapAsync(answer, ct);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject mismatched confirmation replays

When two tabs hold an unconfirmed receipt and one confirms first, every later confirmation gets a successful response here before its expected revisions, representation, deck revision, or status are checked. A stale tab can therefore request a different choice and report it as confirmed even though the committed memory is unchanged—and the returned DTO does not expose the committed status. Treat only an exact replay as idempotent and return 409 for a mismatched request.

Useful? React with 👍 / 👎.

Comment on lines +99 to +101
var saved = await writtenAnswers.AnswerAsync(userId, answer.BoardId.Value, answer.CardId, answer.LayerId,
new(dto.ExpectedDeckRevision, previousText.Text, dto.Status), ct);
if (!saved.IsSuccess) throw new DomainException(saved.ErrorCode, saved.ErrorMessage);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject confirmation when a separate text answer exists

When the user already has a normal text answer with the same text and status, ThinkingAnswerService.AnswerAsync returns that existing memory as a success, so this path marks the audio receipt confirmed and creates a Verified representation linked to a memory produced by the separate text-answer flow. A direct API caller can thus bypass the UI's answerAlreadyKept guard and contradict the documented rule that an existing answer must be corrected in Memory. Detect the pre-existing answer and return a conflict instead of adopting it.

Useful? React with 👍 / 👎.

Comment on lines +84 to +86
if (!live) return
if (playbackUrl.value) URL.revokeObjectURL(playbackUrl.value)
playbackUrl.value = URL.createObjectURL(blob)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Discard stale audio downloads when the answer changes

If an original-audio download is in flight while the shared question is edited and saved, the revision-triggered reload can replace or clear saved before the old download completes. Because completion is guarded only by component liveness, the old blob is then installed as playbackUrl; after a replacement answer is uploaded, its player and download link serve the previous question's recording. Capture and verify the answer identity or generation before accepting the blob, and clear the URL on every saved-answer identity transition.

Useful? React with 👍 / 👎.

@Chris0Jeky

Copy link
Copy Markdown
Owner Author

Bounded independent Luna review of 1b836c9..b719cb8: no direct HIGH/CRITICAL correctness, privacy or data-loss finding. Storage and full audio integration have each received one scoped pass; no additional review loop is requested. Owner checks, transaction rollback, stable upload retries, representation lineage, export and erasure were traced; supplied local proving evidence is documented above.

MEDIUM follow-through: the local-draft sentence should distinguish the in-component "Reload saved recording" action from a browser hard reload, which cannot preserve an unuploaded File. This wording will be narrowed in the next original-library slice under #2808. The existing quota-before-hashing duplicate-at-capacity policy edge remains tracked, not a privacy or loss defect. Physical microphone/device, external STT and restore import remain unverified.

Base automatically changed from codex/2808-companion-continuity to main September 10, 2026 07:26
@Chris0Jeky
Chris0Jeky merged commit b719cb8 into main Sep 10, 2026
38 of 39 checks passed
@Chris0Jeky
Chris0Jeky deleted the codex/2808-audio-originals branch September 10, 2026 07:26
@github-project-automation github-project-automation Bot moved this from Review to Done in Taskdeck Execution Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant