Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

keeps: Datomic-aware piece lookup + upsert so new pieces are keepable

Since KIDLISP_DATOMIC=on is enabled on lith, new pieces get stored by
store-kidlisp via the Datomic sidecar (store-kidlisp-datomic.mjs), not in
MongoDB's `kidlisp` collection. Any Mongo-only read path then reports
"Piece '$code' not found" when the user tries to keep/rebake/sync a
freshly-created piece. Reported against $r94 today (seen in the DB as
present via /api/store-kidlisp?code=r94, absent when queried directly in
Mongo).

Adds backend/kidlisp-read.mjs :: loadKidlispPiece(database, code):
- Reads from the sidecar when the feature flag is on.
- Overlays the Mongo row on top so rebake-era fields (ipfsMedia,
mediaHistory, kept, tezos, pendingRebake) keep working for pieces
already kept under the legacy flow.
- Falls back to a direct Mongo read when Datomic is off or the sidecar
call fails.

Wires the helper into every keep-flow entrypoint that previously read
Mongo directly:
- keep-prepare.mjs (initial validate)
- keep-prepare-background.mjs (bake pipeline)
- keep-confirm.mjs (mint confirm)
- keep-update.mjs (on-chain metadata sync)
- keep-update-confirm.mjs (sync confirm)

Also marks the Mongo updateOne calls in keep-prepare-background,
keep-confirm, and keep-update-confirm as { upsert: true } so the first
bake/mint/sync of a Datomic-only piece creates a Mongo row to receive
ipfsMedia / kept / tezos fields, instead of silently no-op'ing because
no matching document exists.

Leaves kidlisp-keep.mjs alone — it queries the unrelated `kidlisp-codes`
legacy collection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+75 -16
+44
system/backend/kidlisp-read.mjs
··· 1 + // kidlisp-read.mjs — Datomic-aware piece lookup. 2 + // 3 + // When KIDLISP_DATOMIC=on, new pieces land in Datomic via store-kidlisp 4 + // (routed through store-kidlisp-datomic.mjs), NOT in MongoDB. Callers like 5 + // keep-prepare that still read Mongo directly miss those new pieces and 6 + // return "Piece '$code' not found" for freshly-created work. 7 + // 8 + // This helper mirrors the store-kidlisp pattern: prefer the sidecar when 9 + // Datomic is enabled (authoritative source for identity + source), then 10 + // overlay the MongoDB doc if present so caller-specific fields like 11 + // ipfsMedia, mediaHistory, kept, tezos, and pendingRebake — which still 12 + // live in Mongo — continue to work for rebake / sync flows. 13 + 14 + import { sidecar, kidlispDatomicEnabled } from "./kidlisp-sidecar.mjs"; 15 + 16 + /** 17 + * Fetch a kidlisp piece by its short code. 18 + * 19 + * @param {object} database - { db } from backend/database.mjs :: connect(). 20 + * @param {string} code - Short piece code without the leading `$`. 21 + * @returns {Promise<object|null>} Piece doc or null. 22 + */ 23 + export async function loadKidlispPiece(database, code) { 24 + if (!code) return null; 25 + const collection = database.db.collection("kidlisp"); 26 + 27 + if (kidlispDatomicEnabled()) { 28 + try { 29 + const datomic = await sidecar.lookupByCode(code); 30 + if (datomic) { 31 + // Authoritative fields come from Datomic. Merge any MongoDB row on 32 + // top so rebake-era fields (ipfsMedia, kept, tezos, pendingRebake, 33 + // mediaHistory) keep flowing through. Missing Mongo row just means 34 + // this is a brand-new piece that has never been kept yet. 35 + const mongo = await collection.findOne({ code }); 36 + return mongo ? { ...datomic, ...mongo } : { ...datomic }; 37 + } 38 + } catch (err) { 39 + console.warn(`kidlisp-read: sidecar lookupByCode(${code}) failed — falling back to Mongo:`, err?.message || err); 40 + } 41 + } 42 + 43 + return collection.findOne({ code }); 44 + }
+9 -4
system/netlify/functions/keep-confirm.mjs
··· 7 7 import { authorize } from "../../backend/authorization.mjs"; 8 8 import { connect } from "../../backend/database.mjs"; 9 9 import { respond } from "../../backend/http.mjs"; 10 + import { loadKidlispPiece } from "../../backend/kidlisp-read.mjs"; 10 11 import { getKeepsContractAddress, LEGACY_KEEPS_CONTRACT } from "../../backend/tezos-keeps-contract.mjs"; 11 12 import { mirrorRecordMint } from "../../backend/kidlisp-dual-write.mjs"; 12 13 ··· 240 241 requestedVersion: contractVersion, 241 242 }); 242 243 243 - // Find the kidlisp record 244 + // Find the kidlisp record (Datomic-aware; new pieces live only in Datomic) 244 245 const collection = database.db.collection("kidlisp"); 245 - const record = await collection.findOne({ code: cleanPiece }); 246 + const record = await loadKidlispPiece(database, cleanPiece); 246 247 247 248 if (!record) { 248 249 await database.disconnect(); ··· 339 340 unsetOps.pendingKeep = ""; 340 341 } 341 342 343 + // upsert: pieces that live only in Datomic (KIDLISP_DATOMIC=on) don't 344 + // yet have a Mongo row on first keep. Writing on upsert creates one so 345 + // ipfsMedia / kept persist. 342 346 const updateResult = await collection.updateOne( 343 347 { code: cleanPiece }, 344 348 { 345 349 $set: setOps, 346 350 ...(Object.keys(unsetOps).length > 0 ? { $unset: unsetOps } : {}), 347 - } 351 + }, 352 + { upsert: true } 348 353 ); 349 354 350 - if (updateResult.modifiedCount === 0 && updateResult.matchedCount === 0) { 355 + if (updateResult.modifiedCount === 0 && updateResult.matchedCount === 0 && !updateResult.upsertedId) { 351 356 console.warn(`❌ Failed to update piece ${cleanPiece}`); 352 357 await database.disconnect(); 353 358 return respond(500, { error: "Failed to record mint" });
+10 -3
system/netlify/functions/keep-prepare-background.mjs
··· 7 7 // Invoked by keep-prepare.mjs after job creation and validation. 8 8 9 9 import { connect } from "../../backend/database.mjs"; 10 + import { loadKidlispPiece } from "../../backend/kidlisp-read.mjs"; 10 11 import { analyzeKidLisp } from "../../backend/kidlisp-analyzer.mjs"; 11 12 import { getKeepsContractAddress, LEGACY_KEEPS_CONTRACT } from "../../backend/tezos-keeps-contract.mjs"; 12 13 import { handleFor } from "../../backend/authorization.mjs"; ··· 527 528 contractVersion = VERSION_BY_PROFILE[contractProfile] || null; 528 529 } 529 530 const col = database.db.collection("kidlisp"); 530 - const piece = await col.findOne({ code: pieceName }); 531 + // Datomic-aware read so new pieces (which only live in Datomic when 532 + // KIDLISP_DATOMIC=on) are visible to the bake pipeline. 533 + const piece = await loadKidlispPiece(database, pieceName); 531 534 532 535 if (!piece) { 533 536 await markJobFailed(jobId, `Piece '$${pieceName}' not found`, "validate"); ··· 738 741 }, 739 742 }; 740 743 } 741 - await col.updateOne({ code: pieceName }, updateOps); 744 + // upsert: pieces that live only in Datomic don't have a Mongo row yet. 745 + // Write one on first bake so ipfsMedia / mediaHistory / kept can land 746 + // and subsequent keep operations (rebake, confirm, sync) work. 747 + await col.updateOne({ code: pieceName }, updateOps, { upsert: true }); 742 748 await mirrorIpfsMedia(pieceName, updateOps.$set.ipfsMedia); 743 749 } 744 750 ··· 758 764 }; 759 765 await col.updateOne( 760 766 { code: pieceName }, 761 - { $set: { pendingRebake: rebakePayload } } 767 + { $set: { pendingRebake: rebakePayload } }, 768 + { upsert: true } 762 769 ); 763 770 await mirrorPendingRebake(pieceName, rebakePayload); 764 771 const mintStatus = await checkMintStatus(pieceName, CONTRACT_ADDRESS);
+3 -3
system/netlify/functions/keep-prepare.mjs
··· 11 11 12 12 import { authorize, handleFor, hasAdmin } from "../../backend/authorization.mjs"; 13 13 import { connect } from "../../backend/database.mjs"; 14 + import { loadKidlispPiece } from "../../backend/kidlisp-read.mjs"; 14 15 import { getKeepsContractAddress, LEGACY_KEEPS_CONTRACT } from "../../backend/tezos-keeps-contract.mjs"; 15 16 import { 16 17 upsertJob, ··· 94 95 // ── Auth ──────────────────────────────────────────────────────────── 95 96 const user = await authorize(event.headers); 96 97 97 - // ── Load piece from DB ───────────────────────────────────────────── 98 + // ── Load piece from DB (Datomic-aware; new pieces live only in Datomic) ─ 98 99 const database = await connect(); 99 - const collection = database.db.collection("kidlisp"); 100 - const piece = await collection.findOne({ code: pieceName }); 100 + const piece = await loadKidlispPiece(database, pieceName); 101 101 if (!piece) return jsonResponse(404, { error: `Piece '$${pieceName}' not found` }); 102 102 103 103 // ── Check mint status ──────────────────────────────────────────────
+6 -4
system/netlify/functions/keep-update-confirm.mjs
··· 7 7 import { authorize, hasAdmin } from "../../backend/authorization.mjs"; 8 8 import { connect } from "../../backend/database.mjs"; 9 9 import { respond } from "../../backend/http.mjs"; 10 + import { loadKidlispPiece } from "../../backend/kidlisp-read.mjs"; 10 11 import { getKeepsContractAddress, LEGACY_KEEPS_CONTRACT } from "../../backend/tezos-keeps-contract.mjs"; 11 12 import { mirrorRecordMint } from "../../backend/kidlisp-dual-write.mjs"; 12 13 ··· 179 180 }); 180 181 const normalizedTokenId = normalizeTokenId(tokenId); 181 182 182 - // Find the kidlisp record 183 + // Find the kidlisp record (Datomic-aware) 183 184 const collection = database.db.collection("kidlisp"); 184 - const record = await collection.findOne({ code: cleanPiece }); 185 + const record = await loadKidlispPiece(database, cleanPiece); 185 186 186 187 if (!record) { 187 188 await database.disconnect(); ··· 232 233 [`tezos.contracts.${effectiveContract}.pendingArtifactUri`]: "", 233 234 [`tezos.contracts.${effectiveContract}.pendingThumbnailUri`]: "", 234 235 } 235 - } 236 + }, 237 + { upsert: true } 236 238 ); 237 239 238 - if (updateResult.modifiedCount === 0 && updateResult.matchedCount === 0) { 240 + if (updateResult.modifiedCount === 0 && updateResult.matchedCount === 0 && !updateResult.upsertedId) { 239 241 console.warn(`❌ Failed to update piece ${cleanPiece}`); 240 242 await database.disconnect(); 241 243 return respond(500, { error: "Failed to record update" });
+3 -2
system/netlify/functions/keep-update.mjs
··· 8 8 9 9 import { authorize, hasAdmin } from "../../backend/authorization.mjs"; 10 10 import { connect } from "../../backend/database.mjs"; 11 + import { loadKidlispPiece } from "../../backend/kidlisp-read.mjs"; 11 12 import { getKeepsContractAddress, LEGACY_KEEPS_CONTRACT } from "../../backend/tezos-keeps-contract.mjs"; 12 13 import { mirrorRecordMint } from "../../backend/kidlisp-dual-write.mjs"; 13 14 import { stream } from "@netlify/functions"; ··· 218 219 const CONTRACT_ADDRESS = await getKeepsContractAddress({ network: NETWORK, fallback: LEGACY_KEEPS_CONTRACT }); 219 220 console.log(`🪙 KEEP-UPDATE: Using contract ${CONTRACT_ADDRESS}`); 220 221 221 - // Get piece data from database first 222 + // Get piece data from database (Datomic-aware) 222 223 database = await connect(); 223 224 const collection = database.db.collection("kidlisp"); 224 - const pieceDoc = await collection.findOne({ code: pieceName }); 225 + const pieceDoc = await loadKidlispPiece(database, pieceName); 225 226 226 227 if (!pieceDoc) { 227 228 await send("error", { error: `Piece '$${pieceName}' not found` });