openvibers/openvibe.blog Commits
Blog desks: one AI run per post row and attempt (a rewritten post never replays the old run) (#59)
Changes
@@@ -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 = consoltry {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);@@@ -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';});
@@ -33,6 +33,8 @@ const num = (v) => (v == null ? null : Number(v));
@@ -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 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 ymd = (ms) => new Date(ms).toISOString().slice(0, 10);
const newId = () => `dsk_${crypto.randomBytes(10).toString('hex')}`;
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.
// 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;
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
@@ -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) } : {}),
...(it.published_at ? { published_at: String(it.published_at).slice(0, 40) } : {}),
snippet: snippetOf(it),
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 out = r.output || {};
const gaps = (Array.isArray(out.gaps) ? out.gaps : []).map((g) => clean(g, 300)).filter(Boolean).slice(0, 20);
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 : [];
const all = Array.isArray(out.sections) ? out.sections : [];
@@ -210,7 +212,7 @@ function createDesks({ config, store, fetchImpl = globalThis.fetch, log = consol
@@ -210,7 +212,7 @@ function createDesks({ config, store, fetchImpl = globalThis.fetch, log = consol
try {
try {
const q = catalog.questionFor(done);
const q = catalog.questionFor(done);
const r = await ai.run('blog.write_satire', { topic: q.topic, angle: q.angle }, {
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 out = r.output || {};
const markdown = String(out.body_markdown || '').trim().slice(0, 12000);
const markdown = String(out.body_markdown || '').trim().slice(0, 12000);
@@ -113,7 +113,7 @@ function stub(handler) {
@@ -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.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.strictEqual(n.input.period, 'Wednesday 14 October 2026');
assert.ok(n.input.sources[0].snippet.includes('New at the source.'));
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)]);
assert.deepStrictEqual(byWorkflow('blog.write_satire').map((x) => x.input), [catalog.questionFor(0)]);
news = await desks.bySlug('news', '2026-10-14');
news = await desks.bySlug('news', '2026-10-14');
assert.strictEqual(news.status, 'published');
assert.strictEqual(news.status, 'published');
@@ -164,7 +164,7 @@ function stub(handler) {
@@ -164,7 +164,7 @@ function stub(handler) {
mode = 'stub';
mode = 'stub';
await desks.compose();
await desks.compose();
row = await t.ctx.store.db.maybe("SELECT * FROM blog_desk_posts WHERE desk = 'food' AND slug = '2026-10-19'");
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).']);
assert.deepStrictEqual([row.status, row.synthetic, row.hidden_reason], ['hidden', true, 'Written by the stub provider (synthetic text).']);
mode = 'ok';
mode = 'ok';
});
});