openvibers/openvibe.blog Commits

Blog desks: one AI run per post row and attempt (a rewritten post never replays the old run) (#59)

OP OpenVibers committed 29 days ago · 9a20d7d9f5 · 2 files, +6 −4

Changes

ddiff --git a/server/desks/desks.js b/server/desks/desks.js
iindex 7b6645d..d40fdb8 100644
---- a/server/desks/desks.js
++++ b/server/desks/desks.js
@@@ -33,6 +33,8 @@ const num = (v) => (v == null ? null : Number(v));
const slugify = (s) => String(s || '').normalize('NFKD').replace(/[̀-ͯ]/g, '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 70).replace(/-+$/, '');
const ymd = (ms) => new Date(ms).toISOString().slice(0, 10);
const newId = () => `dsk_${crypto.randomBytes(10).toString('hex')}`;
++// One AI run per post row and attempt: a post written again (a new row) is a new run, never a replay of the old one.
++const runKey = (row) => `blog.desk:${row.id}:${row.attempts}`;
// Source numbers the AI was told not to write into its text: "(Source 3)", "(Sources 1, 2)", "[4]". The page links them.
const SOURCE_REFS = /\s*(?:\((?:sources?|items?)\s*[\d\s,&and-]+\)|\[\d+(?:\s*,\s*\d+)*\])/gi;
@@@ -181,7 +183,7 @@ function createDesks({ config, store, fetchImpl = globalThis.fetch, log = consol
...(it.published_at ? { published_at: String(it.published_at).slice(0, 40) } : {}),
snippet: snippetOf(it),
})),
-- }, { target: { service: 'blog', type: 'desk', id: `${desk.key}:${ymd(period.from)}` }, idempotencyKey: `blog.desk:${desk.key}:${ymd(period.from)}:${row.attempts}` });
++ }, { target: { service: 'blog', type: 'desk', id: `${desk.key}:${ymd(period.from)}` }, idempotencyKey: runKey(row) });
const out = r.output || {};
const gaps = (Array.isArray(out.gaps) ? out.gaps : []).map((g) => clean(g, 300)).filter(Boolean).slice(0, 20);
const all = Array.isArray(out.sections) ? out.sections : [];
@@@ -210,7 +212,7 @@ function createDesks({ config, store, fetchImpl = globalThis.fetch, log = consol
try {
const q = catalog.questionFor(done);
const r = await ai.run('blog.write_satire', { topic: q.topic, angle: q.angle }, {
-- target: { service: 'blog', type: 'desk', id: `${desk.key}:${ymd(period.to)}` }, idempotencyKey: `blog.desk:${desk.key}:${ymd(period.to)}:${row.attempts}`,
++ target: { service: 'blog', type: 'desk', id: `${desk.key}:${ymd(period.to)}` }, idempotencyKey: runKey(row),
});
const out = r.output || {};
const markdown = String(out.body_markdown || '').trim().slice(0, 12000);
ddiff --git a/test/desks.test.js b/test/desks.test.js
iindex 52b8245..2269a90 100644
---- a/test/desks.test.js
++++ b/test/desks.test.js
@@@ -113,7 +113,7 @@ function stub(handler) {
assert.deepStrictEqual(n.input.sources.map((s) => s.source_id), ['n3', 'n2', 'n1'], 'the period\'s items of its own sources, newest first (n0 is older, x1 is another source)');
assert.strictEqual(n.input.period, 'Wednesday 14 October 2026');
assert.ok(n.input.sources[0].snippet.includes('New at the source.'));
-- assert.strictEqual(n.idempotency_key, 'blog.desk:news:2026-10-14:1');
++ assert.match(n.idempotency_key, /^blog\.desk:dsk_[0-9a-f]{20}:1$/);
assert.deepStrictEqual(byWorkflow('blog.write_satire').map((x) => x.input), [catalog.questionFor(0)]);
news = await desks.bySlug('news', '2026-10-14');
assert.strictEqual(news.status, 'published');
@@@ -164,7 +164,7 @@ function stub(handler) {
mode = 'stub';
await desks.compose();
row = await t.ctx.store.db.maybe("SELECT * FROM blog_desk_posts WHERE desk = 'food' AND slug = '2026-10-19'");
-- assert.strictEqual(runs.filter((x) => x.input.desk === 'Food safety digest').pop().idempotency_key, 'blog.desk:food:2026-10-19:2');
++ assert.strictEqual(runs.filter((x) => x.input.desk === 'Food safety digest').pop().idempotency_key, `blog.desk:${row.id}:2`);
assert.deepStrictEqual([row.status, row.synthetic, row.hidden_reason], ['hidden', true, 'Written by the stub provider (synthetic text).']);
mode = 'ok';
});