Flagged by the code-reviewer during PR #64 (parallel rendering). Severity: LOW — requires a pathological tutorial id; pre-existing, but #62's concurrency makes it actively dangerous.
Problem
Per-render paths are keyed by ${id}${suffix} where suffix = .${lang}:
workDir = .forge/<id><suffix>, output = <outDir>/<id><suffix>.mp4 (packages/cli/src/render.ts).
So a tutorial with id foo rendered for lang es → .forge/foo.es, and a different tutorial whose id is literally foo.es rendered source-only → also .forge/foo.es. discoverTutorials rejects duplicate ids, but foo and foo.es are distinct ids, so both pass.
Serially this was a benign sequential overwrite; under --render-concurrency > 1 (#62) it's two renders writing the same dir/file simultaneously → corruption.
Note: this is distinct from the duplicate-language collision already fixed in #64 (langs are de-duped in buildRenderJobs).
Options
- Key the work dir by
(id, lang) separately (e.g. .forge/<id>/<lang|_source>), or
- Detect the collision across the job list and error with a clear message.
Acceptance
Flagged by the code-reviewer during PR #64 (parallel rendering). Severity: LOW — requires a pathological tutorial id; pre-existing, but #62's concurrency makes it actively dangerous.
Problem
Per-render paths are keyed by
${id}${suffix}wheresuffix = .${lang}:workDir = .forge/<id><suffix>,output = <outDir>/<id><suffix>.mp4(packages/cli/src/render.ts).So a tutorial with id
foorendered for langes→.forge/foo.es, and a different tutorial whose id is literallyfoo.esrendered source-only → also.forge/foo.es.discoverTutorialsrejects duplicate ids, butfooandfoo.esare distinct ids, so both pass.Serially this was a benign sequential overwrite; under
--render-concurrency > 1(#62) it's two renders writing the same dir/file simultaneously → corruption.Note: this is distinct from the duplicate-language collision already fixed in #64 (langs are de-duped in
buildRenderJobs).Options
(id, lang)separately (e.g..forge/<id>/<lang|_source>), orAcceptance