Priority: P2 build ownership, reproducibility and edit/compile cost. Source-confirmed repeated work and a shared-output race window; no corrupted release or benchmark reproduced.
Evidence at #707 3d955ff998eef120cd398f163c613a54e3383ccb
crates/tracedecay-cli/build-support/source_provenance.rs::watch_paths correctly watches the Git identity and repository-wide source inputs so the product commit/dirty state stays current.
crates/tracedecay-cli/build.rs::main/embed_dashboard invokes npm run build on every ordinary checkout-mode build-script run, not only after a frontend input changes. A Rust/test/docs/source-provenance edit can therefore rerun the frontend build. The opt-in skip path verifies an explicitly supplied output digest; it does not change the default invalidation coupling.
- Preparation runs
npm ci only when node_modules is absent. A directory left from an older lockfile satisfies this condition even after Cargo notices a changed package-lock.json; normal compilation does not prove the dependency installation matches the current lockfile.
dashboard/rsbuild.config.ts writes one checkout-global app-dist with cleanDistPath: true.
- Build.rs validates that directory and computes its digest, then generates
include_bytes! expressions pointing back into ../../dashboard/app-dist. rustc opens those paths later. A separate Cargo target directory/build or direct frontend build can clean/replace them between validation and inclusion. Cargo's lock for one target directory is not an owner for all writers of this shared frontend output. dashboard_manifest.rs checks paths and symlinks but does not retain a snapshot of their bytes.
Smaller ownership model
Separate frontend dependency preparation/building from the CLI's frequently invalidated provenance step. Reuse the existing npm scripts, manifest, bundle digest and distribution staging; do not invent another build daemon, global cache manager, source inventory or redundant manifest format.
The producer should build the intended frontend inputs/dependency lock once into an owned output. The consumer should embed one immutable bundle for the lifetime of its Rust compilation. An output owned by that build (for example a staged OUT_DIR subtree) is sufficient; a new workspace crate is not required. Compute/verify the digest of the actual staged bytes and generate includes to those same bytes. Merely copying from an actively rewritten directory file-by-file is not a coherent source snapshot: use producer completion/exclusion or an immutable producer artifact while staging.
Keep exact source-provenance invalidation. Do not 'fix' repeated npm work by omitting Git/tracked-input watches, lying about the dirty bit, removing the frontend from the binary, or silently accepting stale/empty assets. Bundle-byte integrity and provenance of the frontend source used to generate those bytes are distinct: an expected output digest must come from the applicable build/distribution owner, not from hashing whatever old directory happens to exist.
Provide one explicit dependency preparation path that honors package-lock changes. It need not run npm ci on every Rust edit. Honor the existing locked package manager workflow and fail clearly when preparation is missing/inconsistent rather than introducing network access as a fallback in a reproducible consumer build.
Keep deterministic cold/warm frontend output requirements. The current disabled Rsbuild persistent cache has a documented reproducibility reason; enabling it blindly is not this fix. Packaged crates must still embed their staged bundle without requiring a source checkout or npm installation.
Acceptance
- A Rust-only/provenance-only change updates the compiled product identity without invoking a frontend rebuild when its actual inputs are unchanged. Measure process invocations and clean/incremental build time separately.
- A frontend source/configuration/lockfile change prepares the new bundle and embeds its matching digest; a stale node_modules directory alone cannot attest the new dependency closure.
- Pause one build after bundle validation/staging, rebuild/clean the producer directory from another target, then resume rustc: the first build either consumes its original complete staged bundle or refuses; never mixed bytes under the old cache tag.
- Two isolated CARGO_TARGET_DIR builds cannot delete or rewrite each other's compiler inputs through shared app-dist cleanup.
- Missing files, invalid paths, symlink escapes, digest mismatch and incomplete production assets still fail closed. Preserve the existing good manifest tests; add one real preparation-to-embedding boundary test instead of a duplicate path-validator suite.
- Normal packaged/offline consumption remains independent of Node tooling, with the advertised installed dashboard available.
Related #1055 removes logo generation from the same build script; #1053 owns the vendor diff split. This issue is authored build-graph simplification, not vendored code or a claim that moving files alone saves executable bytes. No repository builds, browser tests or concurrent-build reproduction were executed during this review. Keep #707 draft.
Priority: P2 build ownership, reproducibility and edit/compile cost. Source-confirmed repeated work and a shared-output race window; no corrupted release or benchmark reproduced.
Evidence at #707
3d955ff998eef120cd398f163c613a54e3383ccbcrates/tracedecay-cli/build-support/source_provenance.rs::watch_pathscorrectly watches the Git identity and repository-wide source inputs so the product commit/dirty state stays current.crates/tracedecay-cli/build.rs::main/embed_dashboardinvokesnpm run buildon every ordinary checkout-mode build-script run, not only after a frontend input changes. A Rust/test/docs/source-provenance edit can therefore rerun the frontend build. The opt-in skip path verifies an explicitly supplied output digest; it does not change the default invalidation coupling.npm cionly whennode_modulesis absent. A directory left from an older lockfile satisfies this condition even after Cargo notices a changedpackage-lock.json; normal compilation does not prove the dependency installation matches the current lockfile.dashboard/rsbuild.config.tswrites one checkout-globalapp-distwithcleanDistPath: true.include_bytes!expressions pointing back into../../dashboard/app-dist. rustc opens those paths later. A separate Cargo target directory/build or direct frontend build can clean/replace them between validation and inclusion. Cargo's lock for one target directory is not an owner for all writers of this shared frontend output.dashboard_manifest.rschecks paths and symlinks but does not retain a snapshot of their bytes.Smaller ownership model
Separate frontend dependency preparation/building from the CLI's frequently invalidated provenance step. Reuse the existing npm scripts, manifest, bundle digest and distribution staging; do not invent another build daemon, global cache manager, source inventory or redundant manifest format.
The producer should build the intended frontend inputs/dependency lock once into an owned output. The consumer should embed one immutable bundle for the lifetime of its Rust compilation. An output owned by that build (for example a staged OUT_DIR subtree) is sufficient; a new workspace crate is not required. Compute/verify the digest of the actual staged bytes and generate includes to those same bytes. Merely copying from an actively rewritten directory file-by-file is not a coherent source snapshot: use producer completion/exclusion or an immutable producer artifact while staging.
Keep exact source-provenance invalidation. Do not 'fix' repeated npm work by omitting Git/tracked-input watches, lying about the dirty bit, removing the frontend from the binary, or silently accepting stale/empty assets. Bundle-byte integrity and provenance of the frontend source used to generate those bytes are distinct: an expected output digest must come from the applicable build/distribution owner, not from hashing whatever old directory happens to exist.
Provide one explicit dependency preparation path that honors package-lock changes. It need not run npm ci on every Rust edit. Honor the existing locked package manager workflow and fail clearly when preparation is missing/inconsistent rather than introducing network access as a fallback in a reproducible consumer build.
Keep deterministic cold/warm frontend output requirements. The current disabled Rsbuild persistent cache has a documented reproducibility reason; enabling it blindly is not this fix. Packaged crates must still embed their staged bundle without requiring a source checkout or npm installation.
Acceptance
Related #1055 removes logo generation from the same build script; #1053 owns the vendor diff split. This issue is authored build-graph simplification, not vendored code or a claim that moving files alone saves executable bytes. No repository builds, browser tests or concurrent-build reproduction were executed during this review. Keep #707 draft.