Repository navigation
fix(recording): date swept segments by their own session's start - #407
Conversation
Deactivate uploads every file under recordings/<productionId>/ on Strom, including segments earlier sessions left there, and stamped each RecordingDoc with the ending activation's start. Read startedAt from the timestamp Strom's recorder puts in the segment name instead, falling back to the activation start when the name has none. Document that the first deactivate with object storage uploads the backlog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
birme
left a comment
There was a problem hiding this comment.
Reviewed by the daily-backlog-pr housekeeping pass (Phase 3.5). Verified against main: uploadRecordings already sweeps every file under recordings/<productionId>/ via strom.media.list, so backlog segments from earlier sessions are real (no dependency on #359). New recordingStartFromFileName parses Strom's {prefix}_{YYYYmmdd_HHMMSS}_%05d name and validates via a Date.UTC round-trip (rejects month 13 / Feb 30, per tests); startedAt is threaded through UploadedSegment and used as seg.startedAt ?? doc.updatedAt, dating backlog segments by their own session while legacy names fall back to this activation. Good unit + integration coverage. No fabricated open-live APIs.
Note: edits the same deactivate recording block as #405 — textual conflict; whichever merges second must resolve it. Branch is behind main after #406 merged; it will be refreshed server-side, then merged on a subsequent pass.
|
This PR is part of a multi-PR recording-feature cluster that all touch the same deactivate/upload path and reference each other's merge/rebase order:
Holding off on automated review/merge here — this cluster needs a human (or the relevant dev agent) to assess the whole set and fix a merge order, not a mechanical per-PR pass. Nothing further is needed from the author; this comment records the dependency on the PR itself. |
birme
left a comment
There was a problem hiding this comment.
VERDICT: APPROVE
Reviewed as pr-reviewer (read-only; not the author), correctness-focused, with the extra guesswork/fabrication scrutiny the task calls for. The whole fix hinges on one external contract — the shape of the segment filename Strom's recorder writes — so I verified that against the authoritative source rather than trusting the PR description.
The contract — verified against authoritative source (this was the crux)
The PR claims Strom names segments {filename_prefix}_{YYYYmmdd_HHMMSS}_%05d.{ext}. This contradicts three pre-existing descriptions in open-live itself which all say {prefix}_%05d.{ext} with no datestamp:
src/lib/recording-uploader.ts:5("confirmed by the PM on issue #41, 2026-09-15")src/config.ts:220src/lib/flow-generator.ts:884
That contradiction mattered: open-live sets the recorder prefix property to production._id (flow-generator.ts:893). If Strom produced prod-xyz_00000.mp4 (no stamp), recordingStartFromFileName would never match, always return undefined, and every segment would fall back to doc.updatedAt — i.e. the PR would be a silent no-op in production while its (mock-fed) tests stayed green.
I checked Eyevinn/strom (public) backend/src/blocks/builtin/recorder.rs:
- L637
let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S"); - L638-648
format!("{}/{}_{}_%05d.{}", output_dir, filename_prefix, timestamp, file_ext)
So the actual name is {filename_prefix}_{YYYYmmdd_HHMMSS}_%05d.{ext} — the PR's claim is correct and the three sibling comments (plus recorder.rs's own stale module doc at L33) are the outdated ones. chrono::Local::now() is evaluated once at recorder-block build time, so every segment of a session shares the stamp, and the stamp carries no offset — exactly matching the PR's UTC-read note. Not fabricated.
Verified correct
- RecordingDoc shape (
src/db/types.ts:347-358):startedAt: stringexists; only the comment changed. No fabricated field. UploadedSegment.startedAt?added (recording-uploader.ts:61); populated only when parseable (...(startedAt ? {startedAt} : {})), so the field stays optional and typecheck holds.- Parser (
recording-uploader.ts:297): end-anchored regex matches_YYYYMMDD_HHMMSS_<seq>.<ext>, immune to digits in the prefix;Date.UTC+ round-trip field re-check rejects rolled-over values (month 13, Feb 30, etc.). Verified by the unit tests. No catastrophic-backtracking risk (fixed-width groups). - Dating logic (
routes/productions.ts):startedAt: seg.startedAt ?? doc.updatedAt. Segment with no derivable session →undefined→ clean fallback, no crash, no mis-attribution. This is strictly better than the prior all-doc.updatedAtbehavior and preserves it exactly for unparseable names. - Sweep (
uploadRecordings) listsrecordings/<productionId>/, filters!is_dir, keys as${prefix}${productionId}/${entry.name}— unchanged by this PR.
Fabrication / security findings
- None. No fabricated field or contract (the one high-risk claim was verified against Strom source). Test credentials are obviously fake (
AKIAEXAMPLE/secretexamplekey) — no real secrets. Regex is linear/bounded.
Merge conflict with main
GitHub reports MERGEABLE / BEHIND. A local git merge --no-commit --no-ff origin/main into the PR head auto-merged cleanly (0 conflicted files) even though main has since changed src/routes/productions.ts (#418/#419/#421). So BEHIND is just out-of-date, no real conflict in the recording-sweep/deactivate path. Rebasing on current main is safe.
Checks
gh pr checks 407: build-and-test pass.- Local:
pnpm install --frozen-lockfileok;pnpm typecheckpass; fullvitest run782/782 pass (75 files); targeted recording tests 14/14.
Non-blocking suggestions (follow-up, not required to merge)
- The three sibling comments that still say
{prefix}_%05d.{ext}(recording-uploader.ts:5, config.ts:220, flow-generator.ts:884) are now contradicted by the verified format — worth updating so the next reader doesn't conclude this fix is dead code (as it first appeared). - The fallback comment "
doc.updatedAtis this activation's start" is slightly imprecise if the production doc is written mid-session (updatedAt would then be later than activation start); behavior is unchanged from before, so this is cosmetic only.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude (Claude Code), for @wagenet. Landing order agreed with @wagenet: this PR and #404 merge before #359, unchanged. #359 then rebases onto Checked by merging this branch (
With those, |
|
This PR is part of wagenet's multi-PR recording feature line on Members, in dependency order:
Nothing further is needed from the author; this comment records the dependency on the PRs themselves rather than in a run log. |
Opened by Claude on behalf of @wagenet.
Problem
Deactivate uploads every file under Strom's
recordings/<productionId>/and writes oneRecordingDocper object withstartedAt: doc.updatedAt. Segments that earlier sessions left on Strom (a failed upload, or a session recorded before object storage was configured) are swept by the next deactivate that has storage. They all get that session's start time, so a segment recorded in January can be listed as starting in March. After the upload they are deleted from Strom (#366), so the original date is lost.Fix
Strom's recorder names segments
{filename_prefix}_{YYYYmmdd_HHMMSS}_%05d.{ext}(backend/src/blocks/builtin/recorder.rs), stamped when the recorder block is built.recordingStartFromFileNamereads that stamp,uploadRecordingsreturns it per segment, and deactivate uses it forstartedAt. If a name has no stamp, deactivate falls back todoc.updatedAtas before.Strom formats the stamp with
chrono::Localand writes no offset. open-live reads it as UTC, which matches Strom's container image (noTZis set). On a Strom host set to another time zone,startedAtis off by that offset. Before this change it could be off by weeks.endedAtis still the upload time. That matches theRecordingDocfield comment ("finalized/uploaded") and is out of scope here.Docs
docs/specs/vod-recording-minio.mdgains a "Segments left on Strom" section. It says the first deactivate with storage uploads the backlog, and wherestartedAtcomes from.Overlap with #359
#359 moves new recordings into per-activation directories named after the activation start. That covers segments recorded after it lands. Its shared-directory path, for files recorded before per-activation directories, still falls back to
doc.updatedAt. This PR is based on main because main has the bug in full. If #359 merges first, the rebase should becomeseg.activationStartedAt ?? seg.startedAt ?? doc.updatedAt.Tests
src/__tests__/recording-leftover-sweep.test.tsdrives the real deactivate route with segments from three sessions plus one name without a stamp. It checks eachRecordingDoc.startedAt. With the route change reverted, it fails and every doc gets2026-03-01T10:00:00.000Z.recording-wiring.test.tsgains unit tests for the parser, including rejection of out-of-range dates.pnpm typecheckandpnpm vitest runpass: 74 files, 768 tests.🤖 Generated with Claude Code