| … | … | @@ -77,11 +77,30 @@ export class OverlayRepoStore { |
| 77 | 77 | try { |
| 78 | 78 | fs.mkdirSync(join(this._recordsDir, collection), { recursive: true }) |
| 79 | 79 | const r = await this._overlay.fetchFile(oid, { destPath: disk }) |
| 80 | | if (!r) return null |
| 81 | | } catch { return null } |
| 80 | if (!r) { this._logFetchMiss(collection, rkey, oid, 'fetchFile returned null — no connected peer served this oid'); return null } |
| 81 | } catch (err) { this._logFetchMiss(collection, rkey, oid, (err && err.message) || String(err)); return null } |
| 82 | 82 | return this.get(collection, rkey) |
| 83 | 83 | } |
| 84 | 84 | |
| 85 | // Byte-resolution failures used to be swallowed entirely (`catch { return null }`), which made a |
| 86 | // record whose bytes never replicated indistinguishable from any ordinary miss. That matters because |
| 87 | // this path GATES SIGNING: the forward-only sign gate skips while a converged pointer's bytes are |
| 88 | // unresolved, so a permanent miss silently stops a co-owner signing at all, with no diagnosable |
| 89 | // cause. Observed on wj62 — one record unresolved for 75+ hours with nothing logged anywhere. |
| 90 | // Deduped per oid, because the resolve loop calls this once per record on every list(). |
| 91 | _logFetchMiss (collection, rkey, oid, reason) { |
| 92 | if (!this._fetchMisses) this._fetchMisses = new Set() |
| 93 | const k = String(oid) |
| 94 | if (this._fetchMisses.has(k)) return |
| 95 | this._fetchMisses.add(k) |
| 96 | try { |
| 97 | console.error(JSON.stringify({ |
| 98 | msg: 'overlay byte-resolution FAILED — pointer converged but bytes unavailable (blocks signing)', |
| 99 | did: this._did, key: `${collection}/${rkey}`, oid: k.slice(0, 16), reason |
| 100 | })) |
| 101 | } catch {} |
| 102 | } |
| 103 | |
| 85 | 104 | async get (collection, rkey) { |
| 86 | 105 | const raw = await this.getRaw(collection, rkey) |
| 87 | 106 | return raw ? raw.record : null |