-
Notifications
You must be signed in to change notification settings - Fork 1
fix(audio): establish canonical local-audio resource policy #866
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
Draft
seonghobae
wants to merge
416
commits into
develop
Choose a base branch
from
fix/audio-resource-policy-781
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 46 commits
Commits
Show all changes
416 commits
Select commit
Hold shift + click to select a range
6a329fe
style(audio): restore transcription EOF formatting
seonghobae 44a9087
style(audio): restore temporal EOF formatting
seonghobae 31a7b79
style(audio): restore separator EOF formatting
seonghobae 8adcec8
test(audio): align resource policy version regression
seonghobae f294100
test(audio): bind metadata errors to policy version
seonghobae 370a3d5
test(runtime): require native analysis cancellation
seonghobae 0238c67
fix(runtime): add typed analysis cancellation error
seonghobae 7aa8055
fix(runtime): expose cancelled job error contract
seonghobae 4b85582
fix(runtime): cancel active analysis children
seonghobae c4e2f30
docs(runtime): record native cancellation boundary
seonghobae 0a54d60
docs(runtime): record analysis cancellation behavior
seonghobae 12cc6bd
test(runtime): bind cancellation error wire value
seonghobae 36c5605
fix(runtime): declare cancellation command permission
seonghobae a90a7ea
fix(runtime): grant analysis cancellation capability
seonghobae b746f60
fix(runtime): add generated cancellation permission contract
seonghobae 9e5d1d5
test(runtime): bind cancellation command capability
seonghobae 2dac5b6
docs(audio): bind cancellation to Tauri runtime authority
seonghobae 2f254ac
docs(security): bind analysis cancellation IPC authority
seonghobae 1756759
docs(architecture): record Tauri command authority boundary
seonghobae d3f6b53
test(tauri): require cancellation permission in generated schemas
seonghobae a4c1d71
fix(tauri): refresh generated cancellation capability snapshot
seonghobae ae4d4d3
fix(tauri): regenerate cancellation permission schema
seonghobae 3bfe659
fix(tauri): restore complete generated desktop schema
seonghobae a9a7e27
test(tauri): fail on tracked generated schema drift
seonghobae ea97fc0
fix(tauri): avoid build-mutated schema worktree assertion
seonghobae 5e0180e
docs(architecture): align resource admission and cancellation truth
seonghobae 2d21b6e
docs(architecture): sync local-first resource boundary
seonghobae 1161252
docs(doctoring): trace process-tree containment constraints
seonghobae 6b416c0
test(audio): enforce encoded size at decode boundary
seonghobae 6afb2d8
fix(audio): enforce encoded bytes in decode port
seonghobae 959891e
test(audio): reject non-mono decoder output before normalization
seonghobae 65c9b6a
fix(audio): fail closed on non-mono decoder shapes
seonghobae ec6c41e
style(audio): restore canonical Python file terminator
seonghobae b34be05
test(audio): require renderer cancellation bridge
seonghobae aa07f08
fix(audio): expose cancellation through renderer bridge
seonghobae 4fed274
test(audio): cover cancellation bridge races
seonghobae d3a50f2
test(audio): expose terminal cancellation race
seonghobae 7991631
test(audio): pin cancellation acceptance lock
seonghobae 2fba61c
fix(audio): serialize terminal cancellation
seonghobae fced227
docs(audio): record cancellation ordering invariant
seonghobae 23445c1
test(audio): expose queued cancellation cleanup race
seonghobae 4956aac
fix(audio): serialize queued cancellation cleanup
seonghobae c8a4869
fix(score): restore project score root call
seonghobae f402f54
docs: establish product technical gap baseline
seonghobae 3dd62bb
test(tauri): gate cancellation on runtime authority
seonghobae aa1c938
test(tauri): match generic cancellation finalizer
seonghobae db1a18f
test(tauri): parse generated cancellation schemas
seonghobae a8bd64a
test(tauri): validate cancellation schema structure
seonghobae 8f0d89e
style(tauri): format schema contract helper
seonghobae b0384cb
test(audio): reject post-admission decode growth
seonghobae c0b9c2f
fix(audio): bound decoder reads to admitted bytes
seonghobae f995307
test(audio): cover bounded readinto EOF semantics
seonghobae 7152b92
test(audio): cover bounded seek branches
seonghobae 7413f92
docs(audio): doctor bounded decode authority
seonghobae a7282aa
test(audio): reject invalid publication identity JSON
seonghobae 43b889e
fix(audio): validate publication identity deserialization
seonghobae 1789da7
docs(product): refresh audio admission gap baseline
seonghobae 593d44e
test(audio): expose pre-normalization decoder budget gap
seonghobae f606236
fix(audio): reject over-budget decoder buffers before normalization
seonghobae 2299b21
docs(product): record decoder pre-normalization resource guard
seonghobae c040eed
docs(audio): distinguish decoder allocation from canonical artifact b…
seonghobae 24465db
docs(changelog): clarify decoder allocation admission
seonghobae dcfa90b
test(audio): cap finiteness validation temporary memory
seonghobae a98165a
fix(audio): bound finiteness scan temporary memory
seonghobae bfeefe8
docs(audio): record bounded finiteness validation memory
seonghobae f7f5be0
docs(audio): trace bounded finiteness scan memory
seonghobae 6c559ca
docs(audio): record bounded finiteness scan fix
seonghobae 9b5d1e9
test(audio): assert finiteness rejection provenance
seonghobae 8a4d6f0
test(audio): freeze decoder dtype and resampler contract
seonghobae 2d98bfb
fix(audio): pin canonical decode dtype and resampler
seonghobae bcb533f
test(audio): align decode-port contract with pinned resampler
seonghobae 3761c75
fix(audio): satisfy reproducibility regression lint
seonghobae e24d7ce
fix(audio): apply pinned Ruff formatter output
seonghobae 5e303e0
docs(audio): pin canonical decoder reproducibility evidence
seonghobae 815841d
docs(changelog): record canonical decoder pin
seonghobae c60fb7c
docs(audio): narrow decoder reproducibility claim
seonghobae fb000fb
docs(changelog): narrow decoder reproducibility claim
seonghobae da43fea
test(audio): reject decoder views retaining oversized backing memory
seonghobae e2d39f9
fix(audio): reject decoder views with hidden backing memory
seonghobae f80b962
docs(audio): record hidden decoder backing-memory admission gap
seonghobae ab94470
test(audio): require owned PCM for decoder views
seonghobae 1a35277
fix(audio): detach decoder views into owned PCM
seonghobae 44fbdcf
docs(audio): distinguish retained artifact memory from decoder peak RSS
seonghobae eab2c68
docs(audio): record owned canonicalization of decoder views
seonghobae 8fc4da8
docs(audio): doctor decoder view ownership boundary
seonghobae 574e86d
test(audio): reject malformed decoder dtypes
seonghobae 8f7e1a5
fix(audio): fail closed on malformed decoder dtypes
seonghobae 45b1727
docs(audio): record decoder dtype admission boundary
seonghobae bc8aa65
test(audio): prove dtype rejection precedes PCM allocation
seonghobae 25ff54f
test(audio): reject canonicalization budget expansion
seonghobae 2216a1d
fix(audio): budget canonical PCM before allocation
seonghobae 43ce16f
docs(audio): record canonicalization budget boundary
seonghobae 0510b57
test(audio): resolve duplicate resource-policy import
seonghobae 0467043
test(audio): pin allocation rejection provenance
seonghobae 4861e78
test(audio): reject canonical allocation exhaustion
seonghobae c19f60b
fix(audio): normalize canonical allocation exhaustion
seonghobae 664efd0
docs(audio): record allocator-failure claim boundary
seonghobae ba53156
docs(audio): record canonical allocation exhaustion
seonghobae fb94f21
test(audio): distinguish pre-canonical allocation failure
seonghobae cacc982
fix(audio): scope allocator budget mapping to canonical copy
seonghobae 69adec3
docs(audio): distinguish canonical allocation exhaustion
seonghobae 95d9cc7
test(audio): pin allocator exception provenance
seonghobae cd3e0d5
test(audio): reject empty YouTube artifact with correct reason
seonghobae f925e23
fix(audio): preserve YouTube admission failure reason
seonghobae eed3f8e
fix(audio): lease YouTube artifact ownership per video
seonghobae d04d1c1
test(audio): create malformed YouTube output under active lease
seonghobae ef95475
test(audio): isolate same-ID YouTube cache ownership
seonghobae ab7a4c1
fix(audio): bind YouTube cleanup to leased video identity
seonghobae 8facfa0
test(audio): reject noncanonical YouTube completed filename
seonghobae c4c2c9e
fix(audio): require canonical YouTube completed artifact name
seonghobae ef5b223
docs(audio): bind YouTube completion to canonical filename
seonghobae f23ba9f
test(audio): preserve nontransient same-ID cache files
seonghobae 3dc1bb4
test(audio): bound transient sibling sweep
seonghobae b82bfdf
fix(audio): restrict YouTube abort cleanup authority
seonghobae d2b72b9
test(audio): preserve yt-dlp part-fragment cleanup
seonghobae 09bb020
fix(audio): normalize yt-dlp part-fragment cleanup stems
seonghobae be97c73
test(audio): preserve YouTube IDs containing fragment tokens
seonghobae 98366b6
fix(audio): preserve fragment tokens in YouTube video IDs
seonghobae 0e11700
style(audio): restore youtube module terminator
seonghobae 3c39fa7
docs(audio): record YouTube fragment identity boundary
seonghobae 7664e34
docs(audio): record fragment identity cleanup fix
seonghobae c09ec58
test(audio): constrain yt-dlp fragment cleanup to ASCII indices
seonghobae 668d726
fix(audio): match yt-dlp ASCII fragment indices exactly
seonghobae ab569ca
docs(audio): narrow yt-dlp fragment cleanup authority
seonghobae 740c357
docs(audio): trace yt-dlp fragment index grammar
seonghobae 21458ae
docs(audio): record ASCII fragment cleanup contract
seonghobae f551694
test(audio): reject fragment lookalikes ending in part
seonghobae 7a0387f
fix(audio): reject fragment suffix lookalikes before part cleanup
seonghobae af94694
test(audio): require Unix analysis process-group containment
seonghobae 4e0aa5d
fix(audio): contain analysis descendants on Unix
seonghobae 3ece757
docs(audio): record Unix cancellation containment
seonghobae e6f5d36
docs(audio): trace Unix process-group cancellation
seonghobae 55764a1
docs(audio): log Unix descendant cancellation
seonghobae a07be5c
docs(architecture): record Unix analysis containment
seonghobae 143f04b
docs(security): narrow Unix cancellation claim
seonghobae 65c94f7
test(audio): expose YouTube descendant timeout leak
seonghobae 81a4890
fix(audio): contain YouTube import descendants on Unix
seonghobae 9c36cf0
test(audio): verify YouTube containment in shell gate
seonghobae e8b887c
docs(audio): record YouTube descendant containment gap repair
seonghobae 5f7692f
docs(audio): record YouTube subprocess containment
seonghobae 066d891
docs(architecture): include YouTube process containment
seonghobae 76ca2c0
docs(security): include YouTube descendant containment
seonghobae 91b64cf
docs(doctoring): trace YouTube process containment
seonghobae 04e4d5c
test(process): require shared containment owner
seonghobae 25dd7a5
refactor(process): share containment primitives
seonghobae e772af6
docs(process): record shared containment owner
seonghobae e395dc4
docs(gap): mark containment ownership consolidated
seonghobae 650d93f
docs(changelog): record containment consolidation
seonghobae ce96055
docs(architecture): point process containment to shared owner
seonghobae 6619840
docs(security): consolidate subprocess containment owner
seonghobae 5771e9b
test(process): align containment platform guards
seonghobae bb14420
fix(process): align containment platform guards
seonghobae 3b2df5d
test(process): scope core containment regression
seonghobae c1c2106
test(process): scope Tauri containment regression
seonghobae 9b6340a
test(process): reject success-path descendant pipe hang
seonghobae 650ff2f
fix(process): drain residual descendants after parent exit
seonghobae 04ebdf1
docs(product): cover terminal descendant containment
seonghobae 5e1903d
docs(changelog): record terminal descendant cleanup
seonghobae 6d3af83
docs(security): doctor subprocess terminal containment
seonghobae 1c2f9b9
docs(architecture): align subprocess containment truth
seonghobae 26b16ae
docs(security): cover terminal descendant pipe cleanup
seonghobae fb04b95
test(process): reject unbounded import output capture
seonghobae 90cbe99
fix(process): bound captured helper output
seonghobae 6abb294
refactor(process): expose bounded output owner
seonghobae 2b99124
docs(process): bound helper output evidence
seonghobae 0fbe2cf
docs(gap): include bounded subprocess output admission
seonghobae c7334e5
docs(security): bound subprocess capture memory
seonghobae d766304
docs(changelog): record bounded helper output
seonghobae a0d7f13
fix(process): compile Unix group termination
seonghobae 11acaf5
test(audio): reject slow helper after output overflow
seonghobae ab9b8a1
fix(audio): terminate helper on output admission failure
seonghobae 0dc2faf
docs(audio): record prompt output-admission termination
seonghobae 8770768
docs(product): align helper-output admission baseline
seonghobae 170a2e8
docs(changelog): record helper overflow termination
seonghobae 0e27839
test(audio): bound helper timeout polling latency
seonghobae da1652c
fix(audio): clamp process polling to deadline
seonghobae 3dac5d8
docs(audio): record deadline-bounded process polling
seonghobae 06618e0
docs(audio): make process deadline contract code-current
seonghobae fe3c593
docs(audio): record bounded helper deadline polling
seonghobae 5f370f7
test(audio): require one process-output owner
seonghobae b887cae
fix(audio): remove legacy process-output owner
seonghobae 067a600
merge: reconcile concurrent process deadline repair
seonghobae 9b8542f
fix(desktop): restore PDF read trait import
seonghobae ef6fd2d
test(desktop): scope containment guard assertion
seonghobae be3b27c
test(desktop): require reader failure wakeup
seonghobae 5bf6d3f
fix(desktop): wake process owner on output failure
seonghobae daee11d
docs: record reader-failure wakeup contract
seonghobae 89fdf3e
docs: record prompt process-failure wakeup
seonghobae b02062a
docs: make output-failure wakeup code-current
seonghobae 1ae37e7
test(audio): require terminal analysis descendant cleanup
seonghobae f612c04
fix(audio): clean analysis descendants on terminal parent exit
seonghobae 81fe166
docs(audio): record terminal analysis cleanup repair
seonghobae c7cd9eb
docs(changelog): note terminal analysis descendant cleanup
seonghobae 6cdaca2
test(audio): require bounded analysis process streams
seonghobae 240c6b1
fix(audio): centralize bounded analysis stream admission
seonghobae b3c9373
fix(audio): expose bounded stream readers to desktop adapter
seonghobae 50b5f01
fix(audio): bound analysis process streams
seonghobae 494d0b5
test(audio): preserve terminal containment contract
seonghobae 4405049
test(audio): bind containment check to success-path join
seonghobae 7e6a01c
test(audio): require terminal analysis status after process exit
seonghobae 743d7b7
fix(audio): fail closed on non-terminal engine exit
seonghobae f2ee73a
test(audio): bind helper progress to native job identity
seonghobae 57e2ae4
fix(audio): bind analysis progress to native job identity
seonghobae 532471c
test(audio): reject pre-exit terminal analysis publication
seonghobae 7d0f4a7
fix(audio): defer analysis terminal publication until process exit
seonghobae ce3be1f
fix(audio): restore score PDF owner call after protocol repair
seonghobae f8424cb
docs(changelog): record native analysis protocol authority
seonghobae bd3008d
docs(security): doctor analysis subprocess protocol authority
seonghobae 50886a3
docs(product): record native analysis protocol acceptance
seonghobae 476c3a7
test(desktop): reject malformed analysis JSONL
seonghobae b075212
fix(desktop): fail closed on invalid analysis JSONL
seonghobae fb9d3ba
fix(desktop): preserve score removal project authority
seonghobae be6b361
docs(security): record fail-closed analysis JSONL
seonghobae f497cbd
docs(product): record strict analysis JSONL admission
seonghobae 9aad97a
docs(changelog): record fail-closed analysis protocol
seonghobae e3a6a20
test(audio): reject semantically invalid analysis status payloads
seonghobae 2dbab44
fix(audio): validate analysis status payload semantics
seonghobae 81940fc
docs(audio): record status payload admission boundary
seonghobae 182a250
docs(product): align analysis protocol buyer gap
seonghobae a899db8
docs(changelog): record native status semantics
seonghobae e444365
test(audio): preserve exact process JSONL payload whitespace
seonghobae 6258b97
fix(audio): preserve strict process protocol whitespace
seonghobae e07a1a5
docs(audio): record strict JSONL transport boundary
seonghobae 6a8033b
docs(product): keep resource-admission baseline current
seonghobae 64cb603
test(audio): retain protocol rejection after child exit
seonghobae ea9428c
fix(audio): keep protocol rejection in join result
seonghobae 327aafc
docs(audio): record protocol exit-order boundary
seonghobae ea3ba91
docs(changelog): record fail-closed protocol exit ordering
seonghobae 161de8d
test(audio): preserve blank JSONL records for parser rejection
seonghobae f8135e0
fix(audio): preserve empty JSONL records for fail-closed parsing
seonghobae 0f33574
docs(audio): doctor blank JSONL fail-closed transport
seonghobae 1ecb447
docs(audio): record blank JSONL rejection repair
seonghobae 53b57d2
docs(audio): align buyer gap baseline with JSONL record fidelity
seonghobae 082cf63
test(audio): bind analysis requested-at authority
seonghobae a253c09
test(audio): require native requested-at echo
seonghobae 9358035
fix(audio): preserve native analysis request timestamp
seonghobae 70e4d9d
fix(audio): bind analysis status to native request time
seonghobae e43921a
fix(audio): restore score removal project scope
seonghobae b11dff3
docs(audio): record native request-time authority
seonghobae 8fe4337
docs(audio): note native request-time binding
seonghobae 76cd953
docs(audio): align buyer gap with request-time authority
seonghobae 15bf1fb
test(audio): forbid duplicate CLI temporal analysis
seonghobae f3235e8
fix(audio): keep local MIR under orchestration owner
seonghobae 4e36937
docs(audio): record single MIR orchestration owner
seonghobae ea94d79
docs(audio): doctor single orchestration owner
seonghobae 8ae19aa
fix(audio): preserve CLI logging while removing MIR pre-pass
seonghobae e2ca1f2
docs(audio): preserve CLI diagnostics claim boundary
seonghobae eef9461
test(audio): retire deleted CLI MIR pre-pass fixtures
seonghobae 4b64f57
test(audio): cover supported CLI orchestration boundary
seonghobae 572c490
docs(audio): record obsolete pre-pass test repair
seonghobae 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
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,22 @@ | ||
| /// 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_TOO_LARGE_ERROR: &str = | ||
| "Selected audio file exceeds the 100 MiB analysis limit."; | ||
|
|
||
| /// 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) | ||
| } |
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,15 @@ | ||
| //! Pure, GUI-independent logic for the BandScope desktop application. | ||
| //! | ||
| //! The historical desktop-core implementation remains in `lib.rs` as the | ||
| //! compatibility module while bounded resource boundaries are isolated in | ||
| //! auditable modules. Public symbols are re-exported so downstream callers keep | ||
| //! the same crate-root API. | ||
|
|
||
| #[path = "lib.rs"] | ||
| mod runtime_core; | ||
| mod audio_resource; | ||
| mod score_pdf; | ||
|
|
||
| pub use audio_resource::{validate_local_audio_file_size, MAX_LOCAL_AUDIO_FILE_BYTES}; | ||
| pub use runtime_core::*; | ||
| pub use score_pdf::read_validated_score_pdf; |
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,82 @@ | ||
| use crate::{MAX_SCORE_PDF_BYTES, PDF_MAGIC}; | ||
| use std::{fs::File, io::Read, path::Path}; | ||
|
|
||
| const SCORE_READ_ERROR: &str = "Could not read the score PDF."; | ||
| const SCORE_TOO_LARGE_ERROR: &str = "Score PDF is too large (exceeds 25MB limit)."; | ||
| const SCORE_INVALID_PDF_ERROR: &str = "Stored score is not a valid PDF."; | ||
|
|
||
| fn read_validated_pdf_stream( | ||
| reader: &mut impl Read, | ||
| expected_len: u64, | ||
| ) -> Result<Vec<u8>, String> { | ||
| if expected_len > MAX_SCORE_PDF_BYTES { | ||
| return Err(SCORE_TOO_LARGE_ERROR.to_string()); | ||
| } | ||
|
|
||
| // MAX_SCORE_PDF_BYTES is 25 MiB, which fits every supported Rust `usize`. | ||
| let mut bytes = vec![0_u8; expected_len as usize]; | ||
| reader | ||
| .read_exact(&mut bytes) | ||
| .map_err(|_| SCORE_READ_ERROR.to_string())?; | ||
|
|
||
| let mut growth_probe = [0_u8; 1]; | ||
| if reader | ||
| .read(&mut growth_probe) | ||
| .map_err(|_| SCORE_READ_ERROR.to_string())? | ||
| != 0 | ||
| { | ||
| return Err(SCORE_TOO_LARGE_ERROR.to_string()); | ||
| } | ||
|
|
||
| if !bytes.starts_with(PDF_MAGIC) { | ||
| return Err(SCORE_INVALID_PDF_ERROR.to_string()); | ||
| } | ||
|
|
||
| Ok(bytes) | ||
| } | ||
|
|
||
| /// Read one already-authorized stored score without allocating beyond the PDF limit. | ||
| /// | ||
| /// The caller remains responsible for path authority and containment. This helper | ||
| /// opens that resolved path once, snapshots the descriptor length, allocates only | ||
| /// that bounded size, reads exactly that many bytes, and then probes one additional | ||
| /// byte on the same descriptor. A file that was already oversized is rejected | ||
| /// before heap allocation; a file that grows after metadata inspection is rejected | ||
| /// by the one-byte probe without extending the heap buffer beyond the product cap. | ||
| /// Errors intentionally omit the local path and file content. | ||
| pub fn read_validated_score_pdf(path: &Path) -> Result<Vec<u8>, String> { | ||
| let mut file = File::open(path).map_err(|_| SCORE_READ_ERROR.to_string())?; | ||
| let metadata = file | ||
| .metadata() | ||
| .map_err(|_| SCORE_READ_ERROR.to_string())?; | ||
| if !metadata.is_file() { | ||
| return Err(SCORE_READ_ERROR.to_string()); | ||
| } | ||
| read_validated_pdf_stream(&mut file, metadata.len()) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use std::io::Cursor; | ||
|
|
||
| #[test] | ||
| fn stream_rejects_growth_after_the_metadata_length_snapshot() { | ||
| let mut reader = Cursor::new(b"%PDF-extra".to_vec()); | ||
|
|
||
| let error = read_validated_pdf_stream(&mut reader, PDF_MAGIC.len() as u64) | ||
| .expect_err("bytes beyond the metadata snapshot must fail closed"); | ||
|
|
||
| assert_eq!(error, SCORE_TOO_LARGE_ERROR); | ||
| } | ||
|
|
||
| #[test] | ||
| fn stream_rejects_truncation_after_the_metadata_length_snapshot() { | ||
| let mut reader = Cursor::new(PDF_MAGIC.to_vec()); | ||
|
|
||
| let error = read_validated_pdf_stream(&mut reader, (PDF_MAGIC.len() + 1) as u64) | ||
| .expect_err("truncation after the metadata snapshot must fail closed"); | ||
|
|
||
| assert_eq!(error, SCORE_READ_ERROR); | ||
| } | ||
| } |
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,25 @@ | ||
| use bandscope_desktop_core::{validate_local_audio_file_size, MAX_LOCAL_AUDIO_FILE_BYTES}; | ||
|
|
||
| #[test] | ||
| fn local_audio_size_policy_accepts_the_exact_native_bootstrap_ceiling() { | ||
| assert_eq!( | ||
| validate_local_audio_file_size(MAX_LOCAL_AUDIO_FILE_BYTES), | ||
| Ok(MAX_LOCAL_AUDIO_FILE_BYTES) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn local_audio_size_policy_rejects_an_empty_native_bootstrap_source() { | ||
| assert_eq!( | ||
| validate_local_audio_file_size(0), | ||
| Err("Could not read the selected audio file.".to_string()) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn local_audio_size_policy_rejects_a_native_source_above_the_canonical_ceiling() { | ||
| assert_eq!( | ||
| validate_local_audio_file_size(MAX_LOCAL_AUDIO_FILE_BYTES + 1), | ||
| Err("Selected audio file exceeds the 100 MiB analysis limit.".to_string()) | ||
| ); | ||
| } |
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,91 @@ | ||
| use bandscope_desktop_core::{read_validated_score_pdf, MAX_SCORE_PDF_BYTES}; | ||
| use std::io::Write; | ||
| use std::path::PathBuf; | ||
| use std::time::{SystemTime, UNIX_EPOCH}; | ||
|
|
||
| fn unique_test_dir(name: &str) -> PathBuf { | ||
| let suffix = SystemTime::now() | ||
| .duration_since(UNIX_EPOCH) | ||
| .expect("system clock should be after epoch") | ||
| .as_nanos(); | ||
| std::env::temp_dir().join(format!("bandscope-{name}-{suffix}")) | ||
| } | ||
|
|
||
| #[test] | ||
| fn score_pdf_read_returns_only_valid_bounded_pdf_bytes() { | ||
| let root = unique_test_dir("score-read-valid"); | ||
| std::fs::create_dir_all(&root).expect("test directory should be created"); | ||
| let path = root.join("score.pdf"); | ||
| let expected = b"%PDF-1.7\nvalidated body"; | ||
| std::fs::write(&path, expected).expect("valid PDF fixture should be written"); | ||
|
|
||
| let actual = read_validated_score_pdf(&path).expect("valid stored PDF should be readable"); | ||
|
|
||
| assert_eq!(actual, expected); | ||
| let _ = std::fs::remove_dir_all(root); | ||
| } | ||
|
|
||
| #[test] | ||
| fn score_pdf_read_rejects_empty_short_and_wrong_magic_content() { | ||
| let root = unique_test_dir("score-read-invalid"); | ||
| std::fs::create_dir_all(&root).expect("test directory should be created"); | ||
|
|
||
| for (name, content) in [ | ||
| ("empty.pdf", b"".as_slice()), | ||
| ("short.pdf", b"%PD".as_slice()), | ||
| ("wrong.pdf", b"PK\x03\x04 not a PDF".as_slice()), | ||
| ] { | ||
| let path = root.join(name); | ||
| std::fs::write(&path, content).expect("invalid PDF fixture should be written"); | ||
| let error = read_validated_score_pdf(&path).expect_err("invalid PDF must fail closed"); | ||
| assert!( | ||
| error == "Could not read the score PDF." || error == "Stored score is not a valid PDF.", | ||
| "unexpected payload-safe error: {error}" | ||
| ); | ||
| assert!(!error.contains(root.to_string_lossy().as_ref())); | ||
| } | ||
|
|
||
| let _ = std::fs::remove_dir_all(root); | ||
| } | ||
|
|
||
| #[test] | ||
| fn score_pdf_read_rejects_oversized_sparse_file_before_heap_allocation() { | ||
| let root = unique_test_dir("score-read-oversized"); | ||
| std::fs::create_dir_all(&root).expect("test directory should be created"); | ||
| let path = root.join("oversized.pdf"); | ||
| let mut file = std::fs::File::create(&path).expect("oversized PDF fixture should be created"); | ||
| file.write_all(b"%PDF-") | ||
| .expect("PDF magic should be written before extending sparse file"); | ||
| file.set_len(MAX_SCORE_PDF_BYTES + 1) | ||
| .expect("sparse PDF fixture should exceed the product limit"); | ||
| drop(file); | ||
|
|
||
| let error = read_validated_score_pdf(&path).expect_err("oversized PDF must fail closed"); | ||
|
|
||
| assert_eq!(error, "Score PDF is too large (exceeds 25MB limit)."); | ||
| let _ = std::fs::remove_dir_all(root); | ||
| } | ||
|
|
||
| #[cfg(unix)] | ||
| #[test] | ||
| fn score_pdf_read_rejects_non_file_descriptor() { | ||
| let root = unique_test_dir("score-read-directory"); | ||
| std::fs::create_dir_all(&root).expect("test directory should be created"); | ||
|
|
||
| let error = read_validated_score_pdf(&root).expect_err("directory must fail closed"); | ||
|
|
||
| assert_eq!(error, "Could not read the score PDF."); | ||
| assert!(!error.contains(root.to_string_lossy().as_ref())); | ||
| let _ = std::fs::remove_dir_all(root); | ||
| } | ||
|
|
||
| #[test] | ||
| fn score_pdf_read_rejects_missing_file_without_exposing_path() { | ||
| let root = unique_test_dir("score-read-missing"); | ||
| let path = root.join("private-score.pdf"); | ||
|
|
||
| let error = read_validated_score_pdf(&path).expect_err("missing PDF must fail closed"); | ||
|
|
||
| assert_eq!(error, "Could not read the score PDF."); | ||
| assert!(!error.contains("private-score.pdf")); | ||
| } |
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,58 @@ | ||
| import { beforeEach, describe, expect, it, vi } from "vitest"; | ||
| import { importYoutubeUrl, selectLocalAudioSource } from "./analysis"; | ||
|
|
||
| type TauriWindow = Window & { | ||
| __TAURI_INTERNALS__?: unknown; | ||
| __TAURI_INVOKE__?: unknown; | ||
| }; | ||
|
|
||
| const tauriWindow = window as TauriWindow; | ||
| const INVALID_RESOURCE_POLICY_MESSAGE = | ||
| "Selected audio file metadata violates the analysis resource policy."; | ||
|
|
||
| function fractionalBootstrap(projectId: string) { | ||
| return { | ||
| projectId, | ||
| sourceMode: "reference", | ||
| projectRoot: `/tmp/bandscope/projects/${projectId}`, | ||
| cacheRoot: `/tmp/bandscope/cache/${projectId}`, | ||
| tempRoot: `/tmp/bandscope/temp/${projectId}`, | ||
| source: { | ||
| sourcePath: `/tmp/bandscope/${projectId}/input.wav`, | ||
| fileName: "input.wav", | ||
| extension: "wav", | ||
| fileSizeBytes: 1.5 | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| describe("analysis encoded-byte policy parity", () => { | ||
| beforeEach(() => { | ||
| delete tauriWindow.__TAURI_INTERNALS__; | ||
| delete tauriWindow.__TAURI_INVOKE__; | ||
| }); | ||
|
|
||
| it("rejects fractional local-file metadata before project state", async () => { | ||
| tauriWindow.__TAURI_INVOKE__ = vi.fn().mockResolvedValue(fractionalBootstrap("local-project")); | ||
|
|
||
| await expect(selectLocalAudioSource()).resolves.toEqual({ | ||
| ok: false, | ||
| error: { | ||
| code: "invalid_request", | ||
| message: INVALID_RESOURCE_POLICY_MESSAGE | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| it("rejects fractional imported-file metadata before project state", async () => { | ||
| tauriWindow.__TAURI_INVOKE__ = vi.fn().mockResolvedValue(fractionalBootstrap("youtube-project")); | ||
|
|
||
| await expect(importYoutubeUrl("https://youtu.be/4ozX4yFUC34")).resolves.toEqual({ | ||
| ok: false, | ||
| error: { | ||
| code: "invalid_request", | ||
| message: INVALID_RESOURCE_POLICY_MESSAGE | ||
| } | ||
| }); | ||
| }); | ||
| }); |
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.
Uh oh!
There was an error while loading. Please reload this page.