Repository navigation
Description
Problem
A world compile request (ClientFrame::Compile → CompileSpec) may carry two files with the same name. Nothing refuses that, and every stage of pkgs/id/src/world_compile.rs then treats the request differently:
validate_spec(world_compile.rs:146-185) checks the file count, thatfiles[0]ismain.roc, per-name safety, and per-file/total sizes — but never that names are unique.digest_sources(:423-442) hashes all files in the spec, sosource_blake3in the installed module'sModuleProvenance::SourceBuild(:213, :335-343) describes a source set that includes a file which is never compiled.- The staging loop (:218-221) writes each file to
job.path().join(name)in order, so a later same-name file silently overwrites the earlier one. The compiler only ever sees the last file of each name, and themain.rocread back for the platform-path rewrite (:224-228) is the lastmain.roc, not thefiles[0]the validator checked. check_imports(:118-144) scans all ofspec.files, including shadowed files that never reach the compiler — a dead file importing an ungranted platform module refuses a compile whose actual sources are clean.
The session layer passes the wire files straight through (world_session/mod.rs:1205-1212 maps Vec<CompileFile> into CompileSpec with no dedup), so any admin client that assembles a file list programmatically (or a buggy/duplicate-emitting client) can trigger this. Compilation is admin-only, which bounds the blast radius, but the outcome is either a confusing refusal, a silently different module than the admin sent, or — worst — an installed module whose recorded source provenance does not match the sources it was built from.
Evidence
Verified on main 54f862c9 with a probe crate on the laptop (~/git/fleet/probe-2026-10-27-laneB-dupcompile there) driving verbatim copies of check_imports, validate_spec, digest_sources, and sanitize_file_name from world_compile.rs (sha256 8a61af784d1698d96ef1d58ea5341432ebb82dc72fe2cb4053acca460cc3b580), with the staging loop reproduced exactly (std::fs::write standing in for tokio::fs::write, same path and order). Log: hidden_files/probe-2026-10-27-laneB-dupcompile/out.log in the goal workspace.
CTRL validate=Ok staged=[("Helper.roc", "HELPER"), ("main.roc", "MAIN")] digest_match=true
DUP1 validate=Ok staged=[("Helper.roc", "HELPER-SECOND"), ("main.roc", "MAIN")]
DUP1 digest(spec)=d30c2696ed39ad9f digest(staged)=4c07d9907fddb96e match=false
DUP2 validate=Ok files[0]="MAIN-FIRST" disk_main="MAIN-SECOND" staged=[("Helper.roc", "HELPER"), ("main.roc", "MAIN-SECOND")]
DUP3 check_imports=Err Helper.roc imports Chat, which this world does not allow (grant it with `id world caps --grant Chat`; …)
DUP3 staged=[("Helper.roc", "clean\n"), ("main.roc", "MAIN")] (the importing file is not among them)
- CTRL: unique names validate, stage loss-free, and
digest(spec) == digest(staged). - DUP1:
[main.roc, Helper.roc="HELPER-FIRST", Helper.roc="HELPER-SECOND"]validates; the firstHelper.rocis silently lost at staging; the digest recorded as provenance (digest(spec)) differs from the digest of the sources actually compiled (digest(staged)). - DUP2:
[main.roc="MAIN-FIRST", Helper.roc, main.roc="MAIN-SECOND"]validates on the strength offiles[0], but themain.rocread back from the job dir — the one whose platform path is rewritten and whichroc build main.roccompiles — isMAIN-SECOND. - DUP3:
[main.roc, Helper.roc="import Chat\n", Helper.roc="clean\n"]is refused bycheck_importsfor an import in a file that is shadowed and never compiled.
Why it matters
ModuleProvenance is the record of what a running world module was built from; id world verify/doctor-style audits and incident response lean on source_blake3 meaning "these exact sources". A duplicate name quietly breaks that invariant while also discarding one of the admin's files without any error. Every sibling validator in this area refuses out-of-contract input by name (unsafe names, oversize files, ungranted imports); duplicate names are the one contract hole left.
Acceptance criteria
-
validate_specrefuses a spec with duplicate file names, with an error that names the duplicated file (socompile(), which validates before imports/digest/staging at :202-203, refuses before any stage can diverge). - Regression tests in
world_compile.rscover at minimum: a duplicated non-main file, a duplicatedmain.roc, and that a unique-name spec (control) still validates. - Existing behaviour is unchanged for unique-name specs: the current
world_compiletest module keeps passing, including the source-digest order-independence test. - No change to the digest format, the staging layout, or the provenance schema — refusal at validation is the whole fix surface.
Activity
developing-today commentedon Oct 28, 2026
CODE-623
dezren39 commentedon Oct 28, 2026
🤖 Agent note · state: in-progress (guarded self-solve, lifecycle protocol §4)
validate_spec(pkgs/id/src/world_compile.rs), trivially scoped: refuse duplicate file names with an error naming the duplicated file, plus regression tests (duplicated helper, duplicated main.roc, unique-name control).compile()validates before check_imports/digest/staging, so this one check closes all three divergences (silent overwrite, provenance digest mismatch, import scan over shadowed files). No digest-format, staging-layout, or provenance-schema change.creator-b/1593-compile-duplicate-names, targetedworld_compiletests on the laptop, PR + merge when verified.Merge pull request #1594 from developing-today/creator-b/1593-compile…
dezren39 commentedon Oct 28, 2026
🤖 Agent note · state: closed (self-solved and merged this run)
validate_spec(pkgs/id/src/world_compile.rs) now refuses a compile request with duplicate file names, naming the duplicated file; regression testduplicate_file_names_are_refusedcovers a duplicated helper, a duplicatedmain.roc, and a unique-name control.cargo test --lib world_compileon the laptop: 21 passed / 0 failed (20 pre-existing + new test);cargo fmt --checkclean; probe re-run with the patched validator flips DUP1/DUP2 to refusals naming the file, control unchanged (goal hidden_files/probe-2026-10-27-laneB-dupcompile/out-fixed.log). PR CI was still pending at merge; merged under the standing directive on local verification.