Found while writing v0.9.7 release adversarial tests (packages/opencode/test/skill/release-v0.9.7-adversarial.test.ts).
mergeOverlay (packages/opencode/src/memory/prompt.ts:69-80) has two narrow fragilities, both currently unreachable via the production hydrate/refresh path (which always supplies well-formed RemoteMemoryBlocks via toBlock()), but present in the exported function itself:
- Duplicate ids within the remote list are not deduplicated against each other. The dedup check only compares new remote entries against local keys (
localKeys.has(...)); it never checks a remote entry against remote entries already pushed from the same list. Two remote records sharing one non-sibling scope+id (a corrupted or duplicated overlay fetch) both survive into the merged output.
- A remote block missing
updated throws a TypeError out of the final sort (b.updated.localeCompare(a.updated)) instead of degrading gracefully. toBlock() always guarantees updated via a fallback chain ending in new Date().toISOString(), so this isn't reachable today — but a future caller building RemoteMemoryBlock by hand, or a corrupted overlay cache, would hit it. The memory-read tool's outer try/catch happens to downgrade this to an error response rather than crashing the process, but that's accidental defense-in-depth one layer up, not a property of mergeOverlay itself.
Both are pinned by FINDING:-prefixed tests in the adversarial test file (documenting current behavior, not asserting it's correct). Deferred from v0.9.7 because neither is reachable via the real production path today — worth a hardening pass regardless, since mergeOverlay is exported and its contract implicitly promises a 'deterministic, non-crashing merge' that doesn't fully hold for malformed input.
Found while writing v0.9.7 release adversarial tests (packages/opencode/test/skill/release-v0.9.7-adversarial.test.ts).
mergeOverlay(packages/opencode/src/memory/prompt.ts:69-80) has two narrow fragilities, both currently unreachable via the productionhydrate/refreshpath (which always supplies well-formedRemoteMemoryBlocks viatoBlock()), but present in the exported function itself:localKeys.has(...)); it never checks a remote entry against remote entries already pushed from the same list. Two remote records sharing one non-sibling scope+id (a corrupted or duplicated overlay fetch) both survive into the merged output.updatedthrows a TypeError out of the final sort (b.updated.localeCompare(a.updated)) instead of degrading gracefully.toBlock()always guaranteesupdatedvia a fallback chain ending innew Date().toISOString(), so this isn't reachable today — but a future caller buildingRemoteMemoryBlockby hand, or a corrupted overlay cache, would hit it. The memory-read tool's outer try/catch happens to downgrade this to an error response rather than crashing the process, but that's accidental defense-in-depth one layer up, not a property ofmergeOverlayitself.Both are pinned by
FINDING:-prefixed tests in the adversarial test file (documenting current behavior, not asserting it's correct). Deferred from v0.9.7 because neither is reachable via the real production path today — worth a hardening pass regardless, sincemergeOverlayis exported and its contract implicitly promises a 'deterministic, non-crashing merge' that doesn't fully hold for malformed input.