Skip to content
Draft
Show file tree
Hide file tree
Changes from 151 commits
Commits
Show all changes
340 commits
Select commit Hold shift + click to select a range
3ac09a6
test(audio): preserve structured resource rejection contract
seonghobae Sep 1, 2026
336195a
fix(audio): preserve resource rejection provenance
seonghobae Sep 1, 2026
7862ad2
test(audio): fail closed on parser ValueError leakage
seonghobae Sep 1, 2026
1f33cde
fix(audio): contain metadata parser ValueError details
seonghobae Sep 1, 2026
2ca91e0
test(audio): require one canonical PCM decode port
seonghobae Sep 2, 2026
5cc67e1
test(audio): keep decode-port RED compatible with frozen policy
seonghobae Sep 2, 2026
4b3009c
fix(audio): own one bounded PCM decode port
seonghobae Sep 2, 2026
c2e6509
fix(audio): route MIR consumers through owned decode port
seonghobae Sep 2, 2026
3a76907
test(audio): follow the owned decode-port preflight seam
seonghobae Sep 2, 2026
c5cc94f
fix(audio): document decode-port regressions
seonghobae Sep 3, 2026
609de8d
Merge protected develop into Resource Admission owner
seonghobae Sep 3, 2026
9852265
test(audio): bind native intake diagnostics to resource owner
seonghobae Sep 5, 2026
dbeee9c
merge: adopt protected develop in resource admission lane
seonghobae Sep 5, 2026
804a286
test(audio): reject growth while materializing admitted source
seonghobae Sep 5, 2026
0beee45
fix(audio): bound admitted source materialization
seonghobae Sep 5, 2026
a2b1bd9
fix(audio): materialize selected source into project storage
seonghobae Sep 5, 2026
0ee15f0
docs(audio): trace app-owned source materialization
seonghobae Sep 5, 2026
323a7fa
fix(audio): export bounded source materialization port
seonghobae Sep 5, 2026
dcb3b25
docs(audio): record native port export repair
seonghobae Sep 5, 2026
131d6d7
test(audio): expose bounded-copy destination error
seonghobae Sep 5, 2026
ac4adfd
fix(audio): distinguish bounded-copy destination failure
seonghobae Sep 5, 2026
e2257d9
docs(audio): record bounded-copy diagnostics repair
seonghobae Sep 5, 2026
dc41379
test(audio): require native content identity receipt
seonghobae Sep 5, 2026
566cd1f
fix(audio): emit streaming content identity receipt
seonghobae Sep 5, 2026
f955661
docs(audio): trace native content identity receipt
seonghobae Sep 5, 2026
373824c
test(core): require reusable SHA-256 reader boundary
seonghobae Sep 5, 2026
d1ba406
fix(core): expose reusable SHA-256 reader boundary
seonghobae Sep 5, 2026
8a4f50c
docs(core): record reusable SHA-256 consolidation port
seonghobae Sep 5, 2026
fdfdd70
test(audio): require publication-bound source identity
seonghobae Sep 5, 2026
a1c85cb
fix(audio): verify published source identity
seonghobae Sep 5, 2026
20e7faa
fix(audio): expose publication receipt verifier
seonghobae Sep 5, 2026
505d214
docs(audio): trace publication receipt verification
seonghobae Sep 5, 2026
6a0692e
test(audio): bound publication verification to receipt size
seonghobae Sep 5, 2026
c65a9fd
fix(audio): stop publication verification at expected bytes
seonghobae Sep 5, 2026
92f436a
docs(audio): bound publication verification read evidence
seonghobae Sep 5, 2026
dedaab7
test(audio): reject invalid publication receipt lengths
seonghobae Sep 5, 2026
ed9fe7e
test(audio): require publication-bound materializer receipt
seonghobae Sep 6, 2026
bdf8f87
fix(audio): bind published source to native receipt
seonghobae Sep 6, 2026
539bd57
docs(audio): align publication identity traceability
seonghobae Sep 6, 2026
51734ce
test(audio): align native oversize policy expectation
seonghobae Sep 6, 2026
dd78dee
test(audio): keep zero-byte guard on decode port
seonghobae Sep 6, 2026
6ef0096
test(audio): patch canonical decode boundary
seonghobae Sep 6, 2026
e5726df
test(audio): preserve unrelated separation test wording
seonghobae Sep 6, 2026
46ca91a
docs(security): align local-audio publication authority
seonghobae Sep 6, 2026
45b1f72
test(audio): require no-clobber source publication
seonghobae Sep 6, 2026
eb972e9
fix(audio): publish local source without clobber race
seonghobae Sep 6, 2026
55b0da5
docs(audio): record no-clobber publication decision
seonghobae Sep 6, 2026
bad908c
test(audio): require path-free publication identity handoff
seonghobae Sep 6, 2026
87bdeea
fix(audio): expose path-free publication identity
seonghobae Sep 6, 2026
344a9a3
fix(audio): export publication identity handoff
seonghobae Sep 6, 2026
681675d
docs(audio): record path-free publication identity boundary
seonghobae Sep 6, 2026
645457e
test(audio): require native publication identity retention
seonghobae Sep 6, 2026
f89996b
test(audio): keep Tauri retention as next production slice
seonghobae Sep 6, 2026
cbfa967
test(audio): require native retention of publication identity
seonghobae Sep 6, 2026
d8c57ce
test(audio): keep native-retention RED off canonical head
seonghobae Sep 6, 2026
106ae75
test(audio): require native retention of publication identity
seonghobae Sep 6, 2026
e4e2ba7
fix(audio): retain verified publication identity natively
seonghobae Sep 6, 2026
9a13d2b
docs(audio): record native publication identity retention
seonghobae Sep 6, 2026
06092be
fix(ci): format audio decode regressions
seonghobae Sep 6, 2026
839f5a0
fix(ci): format audio metadata regression
seonghobae Sep 6, 2026
841e1c9
chore(test): normalize decode docstring wrap
seonghobae Sep 6, 2026
ebc5055
test(audio): require durable publication commit
seonghobae Sep 7, 2026
d794555
fix(audio): add platform publication durability barrier
seonghobae Sep 7, 2026
4e2bccc
test(audio): compile publication durability tests
seonghobae Sep 7, 2026
94086ed
fix(audio): durably commit local source publication
seonghobae Sep 7, 2026
e3f6151
docs(audio): record crash-durable source publication
seonghobae Sep 7, 2026
00f720c
docs(audio): doctor publication durability boundary
seonghobae Sep 7, 2026
60816b1
test(audio): preserve existing publication on commit failure
seonghobae Sep 7, 2026
6011294
fix(audio): preserve existing source on publication collision
seonghobae Sep 7, 2026
53747bd
test(audio): bind decoded memory ceiling to canonical float32
seonghobae Sep 7, 2026
379d4a1
fix(audio): cap canonical decoded buffer at float32 footprint
seonghobae Sep 7, 2026
6b16e3c
docs(audio): doctor resource policy v2 memory contract
seonghobae Sep 7, 2026
e96b6f0
test(audio): require canonical float32 decoded PCM
seonghobae Sep 7, 2026
b24a6ff
fix(audio): enforce canonical float32 decoded PCM
seonghobae Sep 7, 2026
3ee3f64
docs(audio): record canonical PCM dtype policy
seonghobae Sep 7, 2026
623294d
docs(audio): doctor canonical float32 admission
seonghobae Sep 7, 2026
6a329fe
style(audio): restore transcription EOF formatting
seonghobae Sep 7, 2026
44a9087
style(audio): restore temporal EOF formatting
seonghobae Sep 7, 2026
31a7b79
style(audio): restore separator EOF formatting
seonghobae Sep 7, 2026
8adcec8
test(audio): align resource policy version regression
seonghobae Sep 7, 2026
f294100
test(audio): bind metadata errors to policy version
seonghobae Sep 7, 2026
370a3d5
test(runtime): require native analysis cancellation
seonghobae Sep 7, 2026
0238c67
fix(runtime): add typed analysis cancellation error
seonghobae Sep 7, 2026
7aa8055
fix(runtime): expose cancelled job error contract
seonghobae Sep 7, 2026
4b85582
fix(runtime): cancel active analysis children
seonghobae Sep 7, 2026
c4e2f30
docs(runtime): record native cancellation boundary
seonghobae Sep 7, 2026
0a54d60
docs(runtime): record analysis cancellation behavior
seonghobae Sep 7, 2026
12cc6bd
test(runtime): bind cancellation error wire value
seonghobae Sep 7, 2026
36c5605
fix(runtime): declare cancellation command permission
seonghobae Sep 7, 2026
a90a7ea
fix(runtime): grant analysis cancellation capability
seonghobae Sep 7, 2026
b746f60
fix(runtime): add generated cancellation permission contract
seonghobae Sep 7, 2026
9e5d1d5
test(runtime): bind cancellation command capability
seonghobae Sep 7, 2026
2dac5b6
docs(audio): bind cancellation to Tauri runtime authority
seonghobae Sep 7, 2026
2f254ac
docs(security): bind analysis cancellation IPC authority
seonghobae Sep 7, 2026
1756759
docs(architecture): record Tauri command authority boundary
seonghobae Sep 7, 2026
d3f6b53
test(tauri): require cancellation permission in generated schemas
seonghobae Sep 7, 2026
a4c1d71
fix(tauri): refresh generated cancellation capability snapshot
seonghobae Sep 7, 2026
ae4d4d3
fix(tauri): regenerate cancellation permission schema
seonghobae Sep 7, 2026
3bfe659
fix(tauri): restore complete generated desktop schema
seonghobae Sep 7, 2026
a9a7e27
test(tauri): fail on tracked generated schema drift
seonghobae Sep 7, 2026
ea97fc0
fix(tauri): avoid build-mutated schema worktree assertion
seonghobae Sep 7, 2026
5e0180e
docs(architecture): align resource admission and cancellation truth
seonghobae Sep 7, 2026
2d21b6e
docs(architecture): sync local-first resource boundary
seonghobae Sep 7, 2026
1161252
docs(doctoring): trace process-tree containment constraints
seonghobae Sep 7, 2026
6b416c0
test(audio): enforce encoded size at decode boundary
seonghobae Sep 7, 2026
6afb2d8
fix(audio): enforce encoded bytes in decode port
seonghobae Sep 7, 2026
959891e
test(audio): reject non-mono decoder output before normalization
seonghobae Sep 7, 2026
65c9b6a
fix(audio): fail closed on non-mono decoder shapes
seonghobae Sep 7, 2026
ec6c41e
style(audio): restore canonical Python file terminator
seonghobae Sep 7, 2026
b34be05
test(audio): require renderer cancellation bridge
seonghobae Sep 7, 2026
aa07f08
fix(audio): expose cancellation through renderer bridge
seonghobae Sep 7, 2026
4fed274
test(audio): cover cancellation bridge races
seonghobae Sep 7, 2026
d3a50f2
test(audio): expose terminal cancellation race
seonghobae Sep 7, 2026
7991631
test(audio): pin cancellation acceptance lock
seonghobae Sep 7, 2026
2fba61c
fix(audio): serialize terminal cancellation
seonghobae Sep 7, 2026
fced227
docs(audio): record cancellation ordering invariant
seonghobae Sep 7, 2026
23445c1
test(audio): expose queued cancellation cleanup race
seonghobae Sep 7, 2026
4956aac
fix(audio): serialize queued cancellation cleanup
seonghobae Sep 7, 2026
c8a4869
fix(score): restore project score root call
seonghobae Sep 7, 2026
f402f54
docs: establish product technical gap baseline
seonghobae Sep 7, 2026
3dd62bb
test(tauri): gate cancellation on runtime authority
seonghobae Sep 7, 2026
aa1c938
test(tauri): match generic cancellation finalizer
seonghobae Sep 7, 2026
db1a18f
test(tauri): parse generated cancellation schemas
seonghobae Sep 7, 2026
a8bd64a
test(tauri): validate cancellation schema structure
seonghobae Sep 7, 2026
8f0d89e
style(tauri): format schema contract helper
seonghobae Sep 7, 2026
b0384cb
test(audio): reject post-admission decode growth
seonghobae Sep 7, 2026
c0b9c2f
fix(audio): bound decoder reads to admitted bytes
seonghobae Sep 7, 2026
f995307
test(audio): cover bounded readinto EOF semantics
seonghobae Sep 7, 2026
7152b92
test(audio): cover bounded seek branches
seonghobae Sep 7, 2026
7413f92
docs(audio): doctor bounded decode authority
seonghobae Sep 7, 2026
a7282aa
test(audio): reject invalid publication identity JSON
seonghobae Sep 7, 2026
43b889e
fix(audio): validate publication identity deserialization
seonghobae Sep 7, 2026
1789da7
docs(product): refresh audio admission gap baseline
seonghobae Sep 7, 2026
593d44e
test(audio): expose pre-normalization decoder budget gap
seonghobae Sep 7, 2026
f606236
fix(audio): reject over-budget decoder buffers before normalization
seonghobae Sep 7, 2026
2299b21
docs(product): record decoder pre-normalization resource guard
seonghobae Sep 7, 2026
c040eed
docs(audio): distinguish decoder allocation from canonical artifact b…
seonghobae Sep 7, 2026
24465db
docs(changelog): clarify decoder allocation admission
seonghobae Sep 7, 2026
dcfa90b
test(audio): cap finiteness validation temporary memory
seonghobae Sep 7, 2026
a98165a
fix(audio): bound finiteness scan temporary memory
seonghobae Sep 7, 2026
bfeefe8
docs(audio): record bounded finiteness validation memory
seonghobae Sep 7, 2026
f7f5be0
docs(audio): trace bounded finiteness scan memory
seonghobae Sep 7, 2026
6c559ca
docs(audio): record bounded finiteness scan fix
seonghobae Sep 7, 2026
9b5d1e9
test(audio): assert finiteness rejection provenance
seonghobae Sep 7, 2026
8a4d6f0
test(audio): freeze decoder dtype and resampler contract
seonghobae Sep 7, 2026
2d98bfb
fix(audio): pin canonical decode dtype and resampler
seonghobae Sep 7, 2026
bcb533f
test(audio): align decode-port contract with pinned resampler
seonghobae Sep 7, 2026
3761c75
fix(audio): satisfy reproducibility regression lint
seonghobae Sep 7, 2026
e24d7ce
fix(audio): apply pinned Ruff formatter output
seonghobae Sep 7, 2026
5e303e0
docs(audio): pin canonical decoder reproducibility evidence
seonghobae Sep 7, 2026
815841d
docs(changelog): record canonical decoder pin
seonghobae Sep 7, 2026
c60fb7c
docs(audio): narrow decoder reproducibility claim
seonghobae Sep 7, 2026
fb000fb
docs(changelog): narrow decoder reproducibility claim
seonghobae Sep 7, 2026
da43fea
test(audio): reject decoder views retaining oversized backing memory
seonghobae Sep 7, 2026
e2d39f9
fix(audio): reject decoder views with hidden backing memory
seonghobae Sep 7, 2026
f80b962
docs(audio): record hidden decoder backing-memory admission gap
seonghobae Sep 7, 2026
ab94470
test(audio): require owned PCM for decoder views
seonghobae Sep 7, 2026
1a35277
fix(audio): detach decoder views into owned PCM
seonghobae Sep 7, 2026
44fbdcf
docs(audio): distinguish retained artifact memory from decoder peak RSS
seonghobae Sep 7, 2026
eab2c68
docs(audio): record owned canonicalization of decoder views
seonghobae Sep 7, 2026
8fc4da8
docs(audio): doctor decoder view ownership boundary
seonghobae Sep 7, 2026
574e86d
test(audio): reject malformed decoder dtypes
seonghobae Sep 7, 2026
8f7e1a5
fix(audio): fail closed on malformed decoder dtypes
seonghobae Sep 7, 2026
45b1727
docs(audio): record decoder dtype admission boundary
seonghobae Sep 7, 2026
bc8aa65
test(audio): prove dtype rejection precedes PCM allocation
seonghobae Sep 7, 2026
25ff54f
test(audio): reject canonicalization budget expansion
seonghobae Sep 7, 2026
2216a1d
fix(audio): budget canonical PCM before allocation
seonghobae Sep 7, 2026
43ce16f
docs(audio): record canonicalization budget boundary
seonghobae Sep 7, 2026
0510b57
test(audio): resolve duplicate resource-policy import
seonghobae Sep 7, 2026
0467043
test(audio): pin allocation rejection provenance
seonghobae Sep 7, 2026
4861e78
test(audio): reject canonical allocation exhaustion
seonghobae Sep 7, 2026
c19f60b
fix(audio): normalize canonical allocation exhaustion
seonghobae Sep 7, 2026
664efd0
docs(audio): record allocator-failure claim boundary
seonghobae Sep 7, 2026
ba53156
docs(audio): record canonical allocation exhaustion
seonghobae Sep 7, 2026
fb94f21
test(audio): distinguish pre-canonical allocation failure
seonghobae Sep 7, 2026
cacc982
fix(audio): scope allocator budget mapping to canonical copy
seonghobae Sep 7, 2026
69adec3
docs(audio): distinguish canonical allocation exhaustion
seonghobae Sep 7, 2026
95d9cc7
test(audio): pin allocator exception provenance
seonghobae Sep 7, 2026
cd3e0d5
test(audio): reject empty YouTube artifact with correct reason
seonghobae Sep 7, 2026
f925e23
fix(audio): preserve YouTube admission failure reason
seonghobae Sep 7, 2026
eed3f8e
fix(audio): lease YouTube artifact ownership per video
seonghobae Sep 7, 2026
d04d1c1
test(audio): create malformed YouTube output under active lease
seonghobae Sep 7, 2026
ef95475
test(audio): isolate same-ID YouTube cache ownership
seonghobae Sep 7, 2026
ab7a4c1
fix(audio): bind YouTube cleanup to leased video identity
seonghobae Sep 7, 2026
8facfa0
test(audio): reject noncanonical YouTube completed filename
seonghobae Sep 7, 2026
c4c2c9e
fix(audio): require canonical YouTube completed artifact name
seonghobae Sep 7, 2026
ef5b223
docs(audio): bind YouTube completion to canonical filename
seonghobae Sep 7, 2026
f23ba9f
test(audio): preserve nontransient same-ID cache files
seonghobae Sep 7, 2026
3dc1bb4
test(audio): bound transient sibling sweep
seonghobae Sep 7, 2026
b82bfdf
fix(audio): restrict YouTube abort cleanup authority
seonghobae Sep 7, 2026
d2b72b9
test(audio): preserve yt-dlp part-fragment cleanup
seonghobae Sep 7, 2026
09bb020
fix(audio): normalize yt-dlp part-fragment cleanup stems
seonghobae Sep 7, 2026
be97c73
test(audio): preserve YouTube IDs containing fragment tokens
seonghobae Sep 8, 2026
98366b6
fix(audio): preserve fragment tokens in YouTube video IDs
seonghobae Sep 8, 2026
0e11700
style(audio): restore youtube module terminator
seonghobae Sep 8, 2026
3c39fa7
docs(audio): record YouTube fragment identity boundary
seonghobae Sep 8, 2026
7664e34
docs(audio): record fragment identity cleanup fix
seonghobae Sep 8, 2026
c09ec58
test(audio): constrain yt-dlp fragment cleanup to ASCII indices
seonghobae Sep 8, 2026
668d726
fix(audio): match yt-dlp ASCII fragment indices exactly
seonghobae Sep 8, 2026
ab569ca
docs(audio): narrow yt-dlp fragment cleanup authority
seonghobae Sep 8, 2026
740c357
docs(audio): trace yt-dlp fragment index grammar
seonghobae Sep 8, 2026
21458ae
docs(audio): record ASCII fragment cleanup contract
seonghobae Sep 8, 2026
f551694
test(audio): reject fragment lookalikes ending in part
seonghobae Sep 8, 2026
7a0387f
fix(audio): reject fragment suffix lookalikes before part cleanup
seonghobae Sep 8, 2026
af94694
test(audio): require Unix analysis process-group containment
seonghobae Sep 8, 2026
4e0aa5d
fix(audio): contain analysis descendants on Unix
seonghobae Sep 8, 2026
3ece757
docs(audio): record Unix cancellation containment
seonghobae Sep 8, 2026
e6f5d36
docs(audio): trace Unix process-group cancellation
seonghobae Sep 8, 2026
55764a1
docs(audio): log Unix descendant cancellation
seonghobae Sep 8, 2026
a07be5c
docs(architecture): record Unix analysis containment
seonghobae Sep 8, 2026
143f04b
docs(security): narrow Unix cancellation claim
seonghobae Sep 8, 2026
65c94f7
test(audio): expose YouTube descendant timeout leak
seonghobae Sep 8, 2026
81a4890
fix(audio): contain YouTube import descendants on Unix
seonghobae Sep 8, 2026
9c36cf0
test(audio): verify YouTube containment in shell gate
seonghobae Sep 8, 2026
e8b887c
docs(audio): record YouTube descendant containment gap repair
seonghobae Sep 8, 2026
5f7692f
docs(audio): record YouTube subprocess containment
seonghobae Sep 8, 2026
066d891
docs(architecture): include YouTube process containment
seonghobae Sep 8, 2026
76ca2c0
docs(security): include YouTube descendant containment
seonghobae Sep 8, 2026
91b64cf
docs(doctoring): trace YouTube process containment
seonghobae Sep 8, 2026
04e4d5c
test(process): require shared containment owner
seonghobae Sep 8, 2026
25dd7a5
refactor(process): share containment primitives
seonghobae Sep 8, 2026
e772af6
docs(process): record shared containment owner
seonghobae Sep 8, 2026
e395dc4
docs(gap): mark containment ownership consolidated
seonghobae Sep 8, 2026
650d93f
docs(changelog): record containment consolidation
seonghobae Sep 8, 2026
ce96055
docs(architecture): point process containment to shared owner
seonghobae Sep 8, 2026
6619840
docs(security): consolidate subprocess containment owner
seonghobae Sep 8, 2026
5771e9b
test(process): align containment platform guards
seonghobae Sep 8, 2026
bb14420
fix(process): align containment platform guards
seonghobae Sep 8, 2026
3b2df5d
test(process): scope core containment regression
seonghobae Sep 8, 2026
c1c2106
test(process): scope Tauri containment regression
seonghobae Sep 8, 2026
9b6340a
test(process): reject success-path descendant pipe hang
seonghobae Sep 8, 2026
650ff2f
fix(process): drain residual descendants after parent exit
seonghobae Sep 8, 2026
04ebdf1
docs(product): cover terminal descendant containment
seonghobae Sep 8, 2026
5e1903d
docs(changelog): record terminal descendant cleanup
seonghobae Sep 8, 2026
6d3af83
docs(security): doctor subprocess terminal containment
seonghobae Sep 8, 2026
1c2f9b9
docs(architecture): align subprocess containment truth
seonghobae Sep 8, 2026
26b16ae
docs(security): cover terminal descendant pipe cleanup
seonghobae Sep 8, 2026
fb04b95
test(process): reject unbounded import output capture
seonghobae Sep 8, 2026
90cbe99
fix(process): bound captured helper output
seonghobae Sep 8, 2026
6abb294
refactor(process): expose bounded output owner
seonghobae Sep 8, 2026
2b99124
docs(process): bound helper output evidence
seonghobae Sep 8, 2026
0fbe2cf
docs(gap): include bounded subprocess output admission
seonghobae Sep 8, 2026
c7334e5
docs(security): bound subprocess capture memory
seonghobae Sep 8, 2026
d766304
docs(changelog): record bounded helper output
seonghobae Sep 8, 2026
a0d7f13
fix(process): compile Unix group termination
seonghobae Sep 8, 2026
11acaf5
test(audio): reject slow helper after output overflow
seonghobae Sep 8, 2026
ab9b8a1
fix(audio): terminate helper on output admission failure
seonghobae Sep 8, 2026
0dc2faf
docs(audio): record prompt output-admission termination
seonghobae Sep 8, 2026
8770768
docs(product): align helper-output admission baseline
seonghobae Sep 8, 2026
170a2e8
docs(changelog): record helper overflow termination
seonghobae Sep 8, 2026
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@
- Display the analyzed song tempo (BPM) as a badge in the rehearsal workspace.
- 각 합주 역할(Role)별 개인 연습 진행도를 0~100% 범위로 기록 및 시각화할 수 있는 연습 진척도(`practiceProgress`) 트래커 기능 추가. UI 컨트롤(슬라이더 및 +/- 버튼)과 한/영 다국어 지원 포함.

### Fixed

- Enforce one canonical local-audio resource policy across native local-file/YouTube bootstrap intake, the desktop bridge, Python request preflight, temporal decoding, and stem separation so oversized, overlong, malformed, wrong-rate, or non-finite input fails before bootstrap storage or expensive analysis/model work.
Comment thread
cursor[bot] marked this conversation as resolved.
- Preflight source-container duration, sample rate, and channel count from the already-open audio handle before temporal, stem, or bass-transcription decoders resample, downmix, or truncate it; successful metadata probes rewind the handle and malformed probes fail closed.
- Bound the admitted canonical decoded mono buffer to 317,520,000 bytes as well as the existing 39,690,000-sample ceiling, so decoder dtype expansion cannot stay within the sample count while exceeding the explicit in-memory audio budget.
- Fail closed on malformed known YouTube duration metadata before `download=True`; Boolean, non-numeric, non-finite, zero, negative, and non-canonical numeric-subtype duration evidence can no longer authorize a media download through Python numeric coercion or subclass semantics.
- Align YouTube download admission with that same 100 MiB encoded-byte ceiling: abort in-flight with yt-dlp `max_filesize` and a progress hook, reject announced oversize before `download=True`, delete owned `.part` / `.ytdl` / `-Frag*` siblings from that import directory on abort, reject a completed path that resolves outside the current import cache before post-download validation, cleanup, or success, and delete owned post-download artifacts that still exceed the policy. A 60 MiB import that the old 50 MB check rejected is now accepted; a file one byte over 100 MiB is not.
- Bound native stored-score PDF reads to the 25 MiB product limit before heap allocation and revalidate PDF magic on the same opened descriptor, preventing an attached score that later grows from bypassing the local resource boundary.
- Treat every zero-element NumPy layout as empty chord input, including shapes whose first dimension is non-zero, before feature extraction.

### Changed

- Consolidated Bandit, dependency audits, supplemental secret checks, and Trivy into one trusted-branch security backstop, delegated CodeQL to GitHub default setup, and removed duplicate local PR security and release-preflight runs.
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ publish = false

[lib]
name = "bandscope_desktop_core"
path = "src/lib.rs"
path = "src/root.rs"

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage)'] }
Expand Down
325 changes: 325 additions & 0 deletions apps/desktop/core/src/audio_resource.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
use crate::content_sha256::StreamingSha256;
use std::io::{ErrorKind, Read, Write};

/// Maximum encoded local-audio file size accepted by the desktop bootstrap boundary.
pub const MAX_LOCAL_AUDIO_FILE_BYTES: u64 = 100 * 1024 * 1024;

const LOCAL_AUDIO_READ_ERROR: &str = "Could not read the selected audio file.";
const LOCAL_AUDIO_WRITE_ERROR: &str = "Could not prepare the local project workspace.";
const LOCAL_AUDIO_TOO_LARGE_ERROR: &str =
"Choose a shorter or smaller song file to start analysis.";

/// Immutable identity evidence for one successfully staged local-audio byte stream.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LocalAudioCopyReceipt {
/// Exact number of bytes written successfully to the staging writer.
pub file_size_bytes: u64,
/// SHA-256 of exactly the bytes written successfully, encoded as lowercase hexadecimal.
pub content_sha256: String,
}

/// Validate a native local-audio file length before storing bootstrap metadata.
///
/// The caller must obtain this length from the native filesystem descriptor or
/// metadata boundary rather than from renderer-controlled JSON. The function
/// intentionally returns only bounded product messages and never includes a
/// local path or payload content.
pub fn validate_local_audio_file_size(file_size_bytes: u64) -> Result<u64, String> {
if file_size_bytes == 0 {
return Err(LOCAL_AUDIO_READ_ERROR.to_string());
}
if file_size_bytes > MAX_LOCAL_AUDIO_FILE_BYTES {
return Err(LOCAL_AUDIO_TOO_LARGE_ERROR.to_string());
}
Ok(file_size_bytes)
}

fn read_retrying_interrupted(reader: &mut impl Read, buffer: &mut [u8]) -> Result<usize, String> {
loop {
match reader.read(buffer) {
Ok(read) => return Ok(read),
Err(error) if error.kind() == ErrorKind::Interrupted => continue,
Err(_) => return Err(LOCAL_AUDIO_READ_ERROR.to_string()),
}
}
}

fn copy_bounded_local_audio_with_limit<R: Read, W: Write>(
mut reader: R,
writer: &mut W,
max_bytes: u64,
) -> Result<LocalAudioCopyReceipt, String> {
let mut copied = 0_u64;
let mut buffer = [0_u8; 64 * 1024];
let mut content_digest = StreamingSha256::default();

loop {
if copied == max_bytes {
let mut overflow_probe = [0_u8; 1];
let read = read_retrying_interrupted(&mut reader, &mut overflow_probe)?;
if read == 0 {
break;
}
return Err(LOCAL_AUDIO_TOO_LARGE_ERROR.to_string());
}

let remaining = (max_bytes - copied).min(buffer.len() as u64) as usize;
let read = read_retrying_interrupted(&mut reader, &mut buffer[..remaining])?;
if read == 0 {
break;
}
writer
.write_all(&buffer[..read])
.map_err(|_| LOCAL_AUDIO_WRITE_ERROR.to_string())?;
content_digest
.update(&buffer[..read])
.map_err(|_| LOCAL_AUDIO_READ_ERROR.to_string())?;
copied += read as u64;
}

if copied == 0 {
return Err(LOCAL_AUDIO_READ_ERROR.to_string());
}
let content_sha256 = content_digest
.finalize_hex()
.map_err(|_| LOCAL_AUDIO_READ_ERROR.to_string())?;
Ok(LocalAudioCopyReceipt {
file_size_bytes: copied,
content_sha256,
})
}

/// Copy one admitted local-audio stream into a staging writer and return native content identity.
///
/// Security Notes: callers must pass an already-open, OS-authorized source
/// descriptor and a private app-owned staging writer. The helper writes no more
/// than the 100 MiB ceiling, hashes exactly the bytes whose writes succeeded,
/// and, after reaching the ceiling exactly, reads only one probe byte to detect
/// source growth. Source-read and destination-write failures use distinct
/// bounded product errors so storage failures are not misdiagnosed as bad media.
/// The caller must discard the staging artifact on error, synchronize it before
/// publication, and bind the returned receipt only to the artifact that was
/// actually published.
pub fn copy_bounded_local_audio_with_receipt<R: Read, W: Write>(
reader: R,
writer: &mut W,
) -> Result<LocalAudioCopyReceipt, String> {
copy_bounded_local_audio_with_limit(reader, writer, MAX_LOCAL_AUDIO_FILE_BYTES)
}

/// Re-read a published app-owned source and prove that it matches its staging receipt.
///
/// Security Notes: the caller must pass an already-open descriptor for the
/// synchronized, published `source.<extension>` object. This helper opens no
/// path and grants no filesystem authority. The staging receipt is native
/// evidence from the prior bounded copy, so its byte length becomes the tighter
/// publication-read ceiling: the verifier hashes at most that many bytes and
/// reads one additional probe byte to reject growth. It then requires both size
/// and digest to equal the staging receipt. Any invalid expected length, read,
/// growth, truncation, or content mismatch is reported as a bounded
/// project-workspace failure because the selected source already passed
/// admission before publication.
pub fn verify_local_audio_publication_receipt<R: Read>(
reader: R,
expected: &LocalAudioCopyReceipt,
) -> Result<LocalAudioCopyReceipt, String> {
if expected.file_size_bytes == 0 || expected.file_size_bytes > MAX_LOCAL_AUDIO_FILE_BYTES {
return Err(LOCAL_AUDIO_WRITE_ERROR.to_string());
}

let mut sink = std::io::sink();
let actual = copy_bounded_local_audio_with_limit(reader, &mut sink, expected.file_size_bytes)
.map_err(|_| LOCAL_AUDIO_WRITE_ERROR.to_string())?;
if actual != *expected {
return Err(LOCAL_AUDIO_WRITE_ERROR.to_string());
}
Ok(actual)
}

/// Copy one admitted local-audio stream into a staging writer and return its byte count.
///
/// This compatibility adapter preserves the existing desktop call boundary while
/// callers migrate to `copy_bounded_local_audio_with_receipt`. It uses the same
/// bounded copy and content-hash path and discards only the returned digest.
pub fn copy_bounded_local_audio<R: Read, W: Write>(reader: R, writer: &mut W) -> Result<u64, String> {
copy_bounded_local_audio_with_receipt(reader, writer).map(|receipt| receipt.file_size_bytes)
}

#[cfg(test)]
mod tests {
use super::*;
use std::io::{Cursor, Error};

struct FailingWriter;

impl Write for FailingWriter {
fn write(&mut self, _buffer: &[u8]) -> std::io::Result<usize> {
Err(Error::new(ErrorKind::Other, "simulated destination failure"))
}

fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}

struct FailingReader;

impl Read for FailingReader {
fn read(&mut self, _buffer: &mut [u8]) -> std::io::Result<usize> {
Err(Error::new(ErrorKind::Other, "simulated source failure"))
}
}

struct InterruptedThenReader {
bytes: Cursor<Vec<u8>>,
interrupted: bool,
}

impl Read for InterruptedThenReader {
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
if !self.interrupted {
self.interrupted = true;
return Err(Error::from(ErrorKind::Interrupted));
}
self.bytes.read(buffer)
}
}

struct CountingReader {
bytes: Cursor<Vec<u8>>,
bytes_read: usize,
}

impl Read for CountingReader {
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
let read = self.bytes.read(buffer)?;
self.bytes_read += read;
Ok(read)
}
}

#[test]
fn bounded_copy_rejects_stream_growth_without_staging_bytes_past_the_limit() {
let input = Cursor::new(vec![1_u8, 2, 3, 4, 5]);
let mut staged = Vec::new();

let error = copy_bounded_local_audio_with_limit(input, &mut staged, 4)
.expect_err("a source that grows beyond the admitted byte limit must fail closed");

assert_eq!(error, LOCAL_AUDIO_TOO_LARGE_ERROR);
assert_eq!(staged, vec![1_u8, 2, 3, 4]);
}

#[test]
fn bounded_copy_accepts_the_exact_limit_and_reports_content_identity() {
let input = Cursor::new(vec![1_u8, 2, 3, 4]);
let mut staged = Vec::new();

let receipt = copy_bounded_local_audio_with_limit(input, &mut staged, 4)
.expect("the exact encoded-byte limit remains admissible");

assert_eq!(receipt.file_size_bytes, 4);
assert_eq!(
receipt.content_sha256,
"9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"
);
assert_eq!(staged, vec![1_u8, 2, 3, 4]);
}

#[test]
fn bounded_copy_reports_destination_failure_as_workspace_failure() {
let input = Cursor::new(vec![1_u8, 2, 3, 4]);
let mut staged = FailingWriter;

let error = copy_bounded_local_audio_with_limit(input, &mut staged, 4)
.expect_err("a staging write failure must not be reported as a source read failure");

assert_eq!(error, LOCAL_AUDIO_WRITE_ERROR);
}

#[test]
fn bounded_copy_keeps_source_failure_distinct_from_workspace_failure() {
let input = FailingReader;
let mut staged = Vec::new();

let error = copy_bounded_local_audio_with_limit(input, &mut staged, 4)
.expect_err("a source read failure must retain the media-read diagnosis");

assert_eq!(error, LOCAL_AUDIO_READ_ERROR);
assert!(staged.is_empty());
}

#[test]
fn bounded_copy_retries_interrupted_source_reads_without_changing_identity() {
let input = InterruptedThenReader {
bytes: Cursor::new(vec![1_u8, 2, 3, 4]),
interrupted: false,
};
let mut staged = Vec::new();

let receipt = copy_bounded_local_audio_with_limit(input, &mut staged, 4)
.expect("an interrupted source read should be retried");

assert_eq!(receipt.file_size_bytes, 4);
assert_eq!(
receipt.content_sha256,
"9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"
);
assert_eq!(staged, vec![1_u8, 2, 3, 4]);
}

#[test]
fn publication_verification_maps_read_failure_to_workspace_failure() {
let expected = LocalAudioCopyReceipt {
file_size_bytes: 4,
content_sha256:
"9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"
.to_string(),
};

let error = verify_local_audio_publication_receipt(FailingReader, &expected)
.expect_err("published artifact read failure must be a workspace failure");

assert_eq!(error, LOCAL_AUDIO_WRITE_ERROR);
}

#[test]
fn publication_verification_stops_after_expected_size_plus_one_probe_byte() {
let expected = LocalAudioCopyReceipt {
file_size_bytes: 4,
content_sha256:
"9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"
.to_string(),
};
let mut published = CountingReader {
bytes: Cursor::new(vec![1_u8, 2, 3, 4, 5, 6, 7, 8]),
bytes_read: 0,
};

let error = verify_local_audio_publication_receipt(&mut published, &expected)
.expect_err("a grown published artifact must fail without scanning unrelated tail bytes");

assert_eq!(error, LOCAL_AUDIO_WRITE_ERROR);
assert_eq!(published.bytes_read, 5);
}

#[test]
fn publication_verification_rejects_impossible_expected_lengths_without_reading() {
for file_size_bytes in [0, MAX_LOCAL_AUDIO_FILE_BYTES + 1] {
let expected = LocalAudioCopyReceipt {
file_size_bytes,
content_sha256: "00".repeat(32),
};
let mut published = CountingReader {
bytes: Cursor::new(vec![1_u8, 2, 3, 4]),
bytes_read: 0,
};

let error = verify_local_audio_publication_receipt(&mut published, &expected)
.expect_err("an impossible native receipt length must fail before reading");

assert_eq!(error, LOCAL_AUDIO_WRITE_ERROR);
assert_eq!(published.bytes_read, 0);
}
}
}
Loading
Loading