Duplicate of #1669 (same finding, keyed there); the deferred-finding marker moved to #1669 so retries reuse that identity.
Source: #1667 at d3133d6
Location: src/core/runtime/client_asset_ledger.js:258-276
Severity: minor
Evidence
Reproduced independently at d3133d6 and on a clean origin/master (0bd0035) worktree: trees {a:"", b:"hello"} and {a:"f:b\nhello"} both digest 561a36c026fd225bfef889ab7b15796c46e4d8aba852f55615e7248d6b02573c under digestClientAsset, byte-identical on both heads, so this PR neither introduces nor worsens it. A crafted edit that preserves the digest lets pruneOneAsset reach recorded.has(digest) === true (src/core/runtime/client_assets.js:864) and fs.rm the user's edited copy, and lets the refresh report it unchanged.
Expected behavior
Two distinct trees never digest equal; a user-edited copy always moves the digest and is withheld from the prune.
Why deferral is safe
Safe outside this PR, and not on pre-existence alone: the obvious repair (frame the byte length) changes every digest the hasher produces, invalidating every digest recorded in client-assets.json and marking every installed asset asset_edited on the next boot, which is the exact migration hazard Accepted LLP 0401 #migration (llp/0401:61) cites when rejecting the copier-side alternative, so the repair needs its own migration decision (versioned digest prefix, one-boot re-record, or an accepted asset_edited wave) rather than a hunk inside a one-issue symlink fix; and the delete-path consequence is reachable only through a deliberately crafted self-edit to a copy the user already owns, never by accident or remotely. Already filed as issue #1669 with neutral:fix; reuse that identity.
Acceptance condition
A test stages two distinct skill trees whose current digests collide (the pair above suffices) and asserts they digest differently after the fix, plus whatever the chosen migration decides happens to a client-assets.json written by the previous release.
Evidenced behavioral defect; eligible for automatic repair.
Duplicate of #1669 (same finding, keyed there); the deferred-finding marker moved to #1669 so retries reuse that identity.
Source: #1667 at d3133d6
Location: src/core/runtime/client_asset_ledger.js:258-276
Severity: minor
Evidence
Reproduced independently at d3133d6 and on a clean origin/master (0bd0035) worktree: trees {a:"", b:"hello"} and {a:"f:b\nhello"} both digest 561a36c026fd225bfef889ab7b15796c46e4d8aba852f55615e7248d6b02573c under digestClientAsset, byte-identical on both heads, so this PR neither introduces nor worsens it. A crafted edit that preserves the digest lets pruneOneAsset reach recorded.has(digest) === true (src/core/runtime/client_assets.js:864) and fs.rm the user's edited copy, and lets the refresh report it unchanged.
Expected behavior
Two distinct trees never digest equal; a user-edited copy always moves the digest and is withheld from the prune.
Why deferral is safe
Safe outside this PR, and not on pre-existence alone: the obvious repair (frame the byte length) changes every digest the hasher produces, invalidating every digest recorded in client-assets.json and marking every installed asset asset_edited on the next boot, which is the exact migration hazard Accepted LLP 0401 #migration (llp/0401:61) cites when rejecting the copier-side alternative, so the repair needs its own migration decision (versioned digest prefix, one-boot re-record, or an accepted asset_edited wave) rather than a hunk inside a one-issue symlink fix; and the delete-path consequence is reachable only through a deliberately crafted self-edit to a copy the user already owns, never by accident or remotely. Already filed as issue #1669 with neutral:fix; reuse that identity.
Acceptance condition
A test stages two distinct skill trees whose current digests collide (the pair above suffices) and asserts they digest differently after the fix, plus whatever the chosen migration decides happens to a client-assets.json written by the previous release.
Evidenced behavioral defect; eligible for automatic repair.