openvibers/openvibe.meme Commits
Classics publish on their score, text and deny list; a re-gate with no AI (#34)
Changes
nnew file mode 100644@@@ -0,0 +1,54 @@++#!/usr/bin/env node++'use strict';++++/**++ * Re-gate the hidden classics with today's rule (server/ai/gate.js evaluateClassic), from the judgement each one already++ * has: no AI is called again. A classic that clears the rule now is published through the store (the origin check and++ * the Search document come with it) and the decision is a status event; the rest stay hidden with their reasons.++ *++ * node scripts/regate-classics.js --dry-run # report only++ * node scripts/regate-classics.js++ */++process.env.NODE_ENV = process.env.NODE_ENV || 'development';++require('dotenv').config();++++const { load } = require('../server/config');++const { openStore } = require('../server/db');++const { createStore } = require('../server/store');++const { createSearchIndex } = require('../server/search');++const { evaluateClassic } = require('../server/ai/gate');++++async function regate({ s, entries, dryRun = false, minScore = 0.75 }) {++ const rows = await s.db.many(`SELECT e.slug, e.title, j.score, j.title AS j_title, j.summary, j.origin, j.spread, j.examples, j.denied, j.looks, j.reason++ FROM meme_entries e JOIN meme_judgements j ON j.entry_id = e.id++ WHERE e.kind = 'classic' AND e.visibility <> 'public' ORDER BY e.slug`);++ const out = { checked: rows.length, published: [], kept: 0, refused: [] };++ for (const r of rows) {++ const judgement = { score: r.score, title: r.j_title, summary: r.summary, origin: r.origin, spread: r.spread, examples: r.examples, denied: r.denied, looks: r.looks };++ const decision = evaluateClassic({ judgement, minScore, entry: { title: r.title } });++ if (!decision.publish) { out.kept++; continue; }++ if (dryRun) { out.published.push(r.slug); continue; }++ const done = await entries.publish(r.slug, { changedBy: 'service:meme:regate' });++ if (!done.ok) { out.refused.push(`${r.slug}: ${done.code || done.message || 'refused'}`); continue; }++ await entries.logDecision({++ entryId: done.entry.id, decision: 'publish', fromStatus: 'classic', toStatus: 'classic', fromVisibility: 'hidden', toVisibility: 'public',++ score: decision.score, reason: `re-gated on 10-31's classic rule: the AI scored it ${Number(decision.score).toFixed(2)}`, changedBy: 'service:meme:regate',++ });++ out.published.push(r.slug);++ }++ return out;++}++++module.exports = { regate };++++if (require.main === module) {++ (async () => {++ const config = load();++ const s = await openStore(config);++ const entries = createStore(s, { search: createSearchIndex({ config, s }) });++ const out = await regate({ s, entries, dryRun: process.argv.includes('--dry-run') });++ console.log(`[regate-classics] checked ${out.checked}, published ${out.published.length}, kept ${out.kept}, refused ${out.refused.length}`);++ if (out.refused.length) console.log(out.refused.slice(0, 10).join('\n'));++ await s.close();++ })().catch((err) => { console.error(err); process.exit(1); });++}@@@ -67,7 +67,10 @@ function evaluate({ entry = null, citations = [], judgement = null, gate = {} }/*** The encyclopedia's rule. A classic has no uses to count: it is published when the AI scored it as a real, widely-- * used meme and its own text is clean (no looks, no denied word, nothing denied in what it cites).++ * used meme, it is written (a title, a summary, where it started, how it spread) and nothing it cites is denied.++ * Two bars of the candidates' rule do not apply (10-31): two example uses is how a chat catchphrase proves itself, not++ * a meme Wikipedia already documents; and the owner lifted the looks rule that day (AI may describe people; only++ * explicit content stays gated, which the deny list keeps).*/function evaluateClassic({ judgement = null, minScore = 0.75, entry = null } = {}) {const reasons = [];@@@ -77,14 +80,11 @@ function evaluateClassic({ judgement = null, minScore = 0.75, entry = null } = {if (!Number.isFinite(score)) reasons.push('no score');else if (score < minScore) reasons.push(`scored ${score.toFixed(2)}, under ${minScore.toFixed(2)}`);if (judgement.denied) reasons.push('a denied source');-- if (judgement.looks) reasons.push('the text names looks');if (!judgement.title) reasons.push('no title');if (!judgement.summary) reasons.push('no summary');if (!judgement.origin) reasons.push('no origin');if (!judgement.spread) reasons.push('no spread');-- if (!Array.isArray(judgement.examples) || judgement.examples.length < 2) reasons.push('fewer than two examples');}-- if (entry && entry.title && hasLooks(entry.title)) reasons.push('the title names looks');return {publish: reasons.length === 0,reasons,nnew file mode 100644@@@ -0,0 +1,20 @@++'use strict';++// The classics' rule (10-31): a classic Wikipedia documents is published on its score, its written text and a clean++// deny list. It needs no example uses (that is how a chat catchphrase proves itself) and is not held for describing++// people (the owner lifted the looks rule that day). A low score, a denied source or missing text still keep it hidden.++const assert = require('assert');++const fs = require('fs');++const path = require('path');++const { evaluateClassic } = require('../server/ai/gate');++++const ok = { score: 0.9, title: 'Flex Tape', summary: 'An infomercial that became a meme.', origin: '2017 infomercial', spread: 'Reaction videos', examples: [], denied: false, looks: false };++assert.strictEqual(evaluateClassic({ judgement: ok }).publish, true, 'no examples needed for a classic');++assert.strictEqual(evaluateClassic({ judgement: { ...ok, looks: true }, entry: { title: 'The man in the hat' } }).publish, true, 'describing people no longer holds a classic back');++assert.strictEqual(evaluateClassic({ judgement: { ...ok, score: 0.5 } }).publish, false, 'a low score still does');++assert.strictEqual(evaluateClassic({ judgement: { ...ok, denied: true } }).publish, false, 'a denied source still does');++assert.strictEqual(evaluateClassic({ judgement: { ...ok, summary: '' } }).publish, false, 'missing text still does');++++const script = fs.readFileSync(path.join(__dirname, '..', 'scripts/regate-classics.js'), 'utf8');++assert.ok(!/createMemeAi|judge\.judge\(/.test(script), 'the re-gate calls no AI');++assert.match(script, /entries\.publish\(r\.slug/, 'and publishes through the store (origin check, Search document)');++console.log('classic gate: score, text and deny list; no examples or looks bar');
new file mode 100644
new file mode 100644
@@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
+#!/usr/bin/env node
+'use strict';
+
+/**
+ * Re-gate the hidden classics with today's rule (server/ai/gate.js evaluateClassic), from the judgement each one already
+ * has: no AI is called again. A classic that clears the rule now is published through the store (the origin check and
+ * the Search document come with it) and the decision is a status event; the rest stay hidden with their reasons.
+ *
+ * node scripts/regate-classics.js --dry-run # report only
+ * node scripts/regate-classics.js
+ */
+process.env.NODE_ENV = process.env.NODE_ENV || 'development';
+require('dotenv').config();
+
+const { load } = require('../server/config');
+const { openStore } = require('../server/db');
+const { createStore } = require('../server/store');
+const { createSearchIndex } = require('../server/search');
+const { evaluateClassic } = require('../server/ai/gate');
+
+async function regate({ s, entries, dryRun = false, minScore = 0.75 }) {
+ const rows = await s.db.many(`SELECT e.slug, e.title, j.score, j.title AS j_title, j.summary, j.origin, j.spread, j.examples, j.denied, j.looks, j.reason
+ FROM meme_entries e JOIN meme_judgements j ON j.entry_id = e.id
+ WHERE e.kind = 'classic' AND e.visibility <> 'public' ORDER BY e.slug`);
+ const out = { checked: rows.length, published: [], kept: 0, refused: [] };
+ for (const r of rows) {
+ const judgement = { score: r.score, title: r.j_title, summary: r.summary, origin: r.origin, spread: r.spread, examples: r.examples, denied: r.denied, looks: r.looks };
+ const decision = evaluateClassic({ judgement, minScore, entry: { title: r.title } });
+ if (!decision.publish) { out.kept++; continue; }
+ if (dryRun) { out.published.push(r.slug); continue; }
+ const done = await entries.publish(r.slug, { changedBy: 'service:meme:regate' });
+ if (!done.ok) { out.refused.push(`${r.slug}: ${done.code || done.message || 'refused'}`); continue; }
+ await entries.logDecision({
+ entryId: done.entry.id, decision: 'publish', fromStatus: 'classic', toStatus: 'classic', fromVisibility: 'hidden', toVisibility: 'public',
+ score: decision.score, reason: `re-gated on 10-31's classic rule: the AI scored it ${Number(decision.score).toFixed(2)}`, changedBy: 'service:meme:regate',
+ });
+ out.published.push(r.slug);
+ }
+ return out;
+}
+
+module.exports = { regate };
+
+if (require.main === module) {
+ (async () => {
+ const config = load();
+ const s = await openStore(config);
+ const entries = createStore(s, { search: createSearchIndex({ config, s }) });
+ const out = await regate({ s, entries, dryRun: process.argv.includes('--dry-run') });
+ console.log(`[regate-classics] checked ${out.checked}, published ${out.published.length}, kept ${out.kept}, refused ${out.refused.length}`);
+ if (out.refused.length) console.log(out.refused.slice(0, 10).join('\n'));
+ await s.close();
+ })().catch((err) => { console.error(err); process.exit(1); });
+}
@@ -67,7 +67,10 @@ function evaluate({ entry = null, citations = [], judgement = null, gate = {} }
@@ -67,7 +67,10 @@ function evaluate({ entry = null, citations = [], judgement = null, gate = {} }
/**
/**
* The encyclopedia's rule. A classic has no uses to count: it is published when the AI scored it as a real, widely
* The encyclopedia's rule. A classic has no uses to count: it is published when the AI scored it as a real, widely
- * used meme and its own text is clean (no looks, no denied word, nothing denied in what it cites).
+ * used meme, it is written (a title, a summary, where it started, how it spread) and nothing it cites is denied.
+ * Two bars of the candidates' rule do not apply (10-31): two example uses is how a chat catchphrase proves itself, not
+ * a meme Wikipedia already documents; and the owner lifted the looks rule that day (AI may describe people; only
+ * explicit content stays gated, which the deny list keeps).
*/
*/
function evaluateClassic({ judgement = null, minScore = 0.75, entry = null } = {}) {
function evaluateClassic({ judgement = null, minScore = 0.75, entry = null } = {}) {
const reasons = [];
const reasons = [];
@@ -77,14 +80,11 @@ function evaluateClassic({ judgement = null, minScore = 0.75, entry = null } = {
@@ -77,14 +80,11 @@ function evaluateClassic({ judgement = null, minScore = 0.75, entry = null } = {
if (!Number.isFinite(score)) reasons.push('no score');
if (!Number.isFinite(score)) reasons.push('no score');
else if (score < minScore) reasons.push(`scored ${score.toFixed(2)}, under ${minScore.toFixed(2)}`);
else if (score < minScore) reasons.push(`scored ${score.toFixed(2)}, under ${minScore.toFixed(2)}`);
if (judgement.denied) reasons.push('a denied source');
if (judgement.denied) reasons.push('a denied source');
- if (judgement.looks) reasons.push('the text names looks');
if (!judgement.title) reasons.push('no title');
if (!judgement.title) reasons.push('no title');
if (!judgement.summary) reasons.push('no summary');
if (!judgement.summary) reasons.push('no summary');
if (!judgement.origin) reasons.push('no origin');
if (!judgement.origin) reasons.push('no origin');
if (!judgement.spread) reasons.push('no spread');
if (!judgement.spread) reasons.push('no spread');
- if (!Array.isArray(judgement.examples) || judgement.examples.length < 2) reasons.push('fewer than two examples');
}
}
- if (entry && entry.title && hasLooks(entry.title)) reasons.push('the title names looks');
return {
return {
publish: reasons.length === 0,
publish: reasons.length === 0,
reasons,
reasons,
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+'use strict';
+// The classics' rule (10-31): a classic Wikipedia documents is published on its score, its written text and a clean
+// deny list. It needs no example uses (that is how a chat catchphrase proves itself) and is not held for describing
+// people (the owner lifted the looks rule that day). A low score, a denied source or missing text still keep it hidden.
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+const { evaluateClassic } = require('../server/ai/gate');
+
+const ok = { score: 0.9, title: 'Flex Tape', summary: 'An infomercial that became a meme.', origin: '2017 infomercial', spread: 'Reaction videos', examples: [], denied: false, looks: false };
+assert.strictEqual(evaluateClassic({ judgement: ok }).publish, true, 'no examples needed for a classic');
+assert.strictEqual(evaluateClassic({ judgement: { ...ok, looks: true }, entry: { title: 'The man in the hat' } }).publish, true, 'describing people no longer holds a classic back');
+assert.strictEqual(evaluateClassic({ judgement: { ...ok, score: 0.5 } }).publish, false, 'a low score still does');
+assert.strictEqual(evaluateClassic({ judgement: { ...ok, denied: true } }).publish, false, 'a denied source still does');
+assert.strictEqual(evaluateClassic({ judgement: { ...ok, summary: '' } }).publish, false, 'missing text still does');
+
+const script = fs.readFileSync(path.join(__dirname, '..', 'scripts/regate-classics.js'), 'utf8');
+assert.ok(!/createMemeAi|judge\.judge\(/.test(script), 'the re-gate calls no AI');
+assert.match(script, /entries\.publish\(r\.slug/, 'and publishes through the store (origin check, Search document)');
+console.log('classic gate: score, text and deny list; no examples or looks bar');