| … | … | @@ -21,7 +21,7 @@ |
| 21 | 21 | import process from 'bare-process' |
| 22 | 22 | import os from 'bare-os' |
| 23 | 23 | import { join } from 'bare-path' |
| 24 | | import { planRecord, selectImages, samePost } from './lib/plan.js' |
| 24 | import { planRecord, selectImages, samePost, MAX_IMAGE_BYTES } from './lib/plan.js' |
| 25 | 25 | import { planProfile, sameProfile } from './lib/profile.js' |
| 26 | 26 | import { FavelaPeer } from './lib/peer.js' |
| 27 | 27 | import { BskySink, POST_COLLECTION } from './lib/bsky.js' |
| … | … | @@ -202,15 +202,37 @@ const blobCache = new Map() // oid -> blob ref. Content-addressed, so this can n |
| 202 | 202 | */ |
| 203 | 203 | async function buildEmbed (peer, bsky, imgs) { |
| 204 | 204 | if (!imgs || !imgs.length) return { embed: null, losses: [] } |
| 205 | const losses0 = [] |
| 205 | 206 | |
| 207 | // ⛔ SELECT, NEVER DOWNSAMPLE (SOCIAL-MODEL §6.6.1). The pyramid was built once at authoring; a |
| 208 | // bridge picks the level nearest its ceiling. Re-encoding here would do the work per delivery on |
| 209 | // every bridge AND produce bytes the author never made — §8's rule, applied to pixels. |
| 210 | // |
| 211 | // ⚠ The peer does the arithmetic against a ceiling WE supply, so there is one ladder rather than |
| 212 | // one per bridge. A blob with no pointer answers with its single level, which is every blob |
| 213 | // written before today. |
| 206 | 214 | const described = [] |
| 207 | 215 | for (const im of imgs) { |
| 216 | let chosen = null |
| 217 | try { chosen = await peer.call('blob-level', { oid: im.oid, maxBytes: MAX_IMAGE_BYTES }) } catch { chosen = null } |
| 218 | if (!chosen || !chosen.level) { |
| 219 | // ⛔ A CLEAR ABSENCE, not a silent drop: "no level fits" and "the fetch failed" are different |
| 220 | // facts and the author is entitled to know which happened. |
| 221 | losses0.push('image omitted — ' + ((chosen && chosen.reason) || 'no level could be chosen')) |
| 222 | continue |
| 223 | } |
| 224 | // ★ THE HONESTY REQUIREMENT. A bridge that quietly serves a smaller picture has altered what |
| 225 | // was published, so the level it chose is reported even when everything worked. |
| 226 | if (chosen.reason && chosen.reason !== 'full size' && chosen.reason !== 'single level') { |
| 227 | losses0.push('image: ' + chosen.reason) |
| 228 | } |
| 208 | 229 | let f = null |
| 209 | | try { f = await peer.call('file-get', { oid: im.oid }) } catch { f = null } |
| 230 | try { f = await peer.call('file-get', { oid: chosen.level.oid }) } catch { f = null } |
| 210 | 231 | described.push({ ...im, path: (f && f.path) || null, mimeType: f && f.mimeType, size: f && f.size }) |
| 211 | 232 | } |
| 212 | 233 | |
| 213 | | const { images, losses } = selectImages(described) |
| 234 | const { images, losses: sizeLosses } = selectImages(described) |
| 235 | const losses = losses0.concat(sizeLosses) |
| 214 | 236 | const out = [] |
| 215 | 237 | for (const d of images) { |
| 216 | 238 | let ref = blobCache.get(d.oid) |