openvibers/openvibe.rent Commits
Rent: the listing screen shows only signs a quote from the listing backs (#51)
Changes
@@@ -46,14 +46,25 @@ function screenInput(row) {};}--/** Bound and validate the workflow's output: unknown signs are dropped; `review` without a sign is `allow`. */--function readOutput(output) {++const norm = (t) => String(t == null ? '' : t).toLowerCase().replace(/[\u2018\u2019]/g, "'").replace(/[\u201c\u201d]/g, '"').replace(/\s+/g, ' ').trim();++++/**++ * Bound, validate and ground the workflow's output in the listing it read. A quote counts only when its words really++ * are in the listing's title or description (a model may paraphrase or invent one), and a sign counts only when a++ * grounded quote is labelled with it: staff never read a sign the text does not show. (On its first production run++ * the local model named all six signs for a text that showed three.) With no grounded sign the answer is allow.++ */++function readOutput(output, row = null) {const o = output && typeof output === 'object' ? output : {};-- const signals = [...new Set((Array.isArray(o.signals) ? o.signals : []).filter((x) => SIGNALS.includes(x)))];-- const action = o.action === 'review' && signals.length ? 'review' : 'allow';-- const spans = (Array.isArray(o.spans) ? o.spans : []).slice(0, 10)-- .filter((x) => x && typeof x.quote === 'string' && x.quote.trim())++ const text = row ? norm(`${row.title || ''}\n${row.description || ''}`) : null;++ const spans = (Array.isArray(o.spans) ? o.spans : [])++ .filter((x) => x && typeof x.quote === 'string' && norm(x.quote))++ .filter((x) => !text || text.includes(norm(x.quote)))++ .slice(0, 10).map((x) => ({ quote: x.quote.trim().slice(0, 300), signal: SIGNALS.includes(x.signal) ? x.signal : null }));++ const backed = new Set(spans.map((x) => x.signal).filter(Boolean));++ const signals = SIGNALS.filter((x) => backed.has(x));++ const action = o.action === 'review' && signals.length ? 'review' : 'allow';const rationale = typeof o.rationale === 'string' ? o.rationale.trim().slice(0, 1000) : '';return { action, signals: action === 'review' ? signals : [], spans: action === 'review' ? spans : [], rationale };}@@@ -86,7 +97,7 @@ function createScreen({ s, ai = null, log = console }) {log.warn(`[Screen] listing ${id}: ${(err && err.message) || err}`);return 'failed';}-- const out = readOutput(run && run.output);++ const out = readOutput(run && run.output, row);await store.putCheck(s, { listingId: row.id, contentHash: hash, ...out, runId: (run && run.runId) || null });if (out.action === 'review') log.log(`[Screen] listing ${row.id} flagged for staff: ${out.signals.join(', ')}`);return out.action;@@@ -25,7 +25,18 @@ const SCAM = 'I am abroad for work so I cannot show it. Send the deposit by wirereturn {runId: `run_${calls.length}`,output: scam-- ? { action: 'review', signals: ['owner_unavailable', 'payment_before_viewing', 'unusual_payment', 'made_up'], rationale: 'Pay by wire before any viewing; the owner is away.', spans: [{ quote: 'Send the deposit by wire transfer today', signal: 'payment_before_viewing' }] }++ ? {++ action: 'review', rationale: 'Pay by wire before any viewing; the owner is away.',++ // Named but unbacked (pressure, personal_data_request), unknown (made_up), and one invented quote:++ // only the signs a quote from the listing stands behind reach staff.++ signals: ['owner_unavailable', 'payment_before_viewing', 'unusual_payment', 'pressure', 'personal_data_request', 'made_up'],++ spans: [++ { quote: 'Send the deposit by wire transfer today', signal: 'payment_before_viewing' },++ { quote: 'by wire transfer', signal: 'unusual_payment' },++ { quote: 'I am abroad for work so I cannot show it.', signal: 'owner_unavailable' },++ { quote: 'Pay in bitcoin within the hour', signal: 'pressure' },++ ],++ }: { action: 'allow', signals: ['none'], rationale: '', spans: [] },};},@@@ -59,7 +70,8 @@ const SCAM = 'I am abroad for work so I cannot show it. Send the deposit by wireawait t.ctx.screen.settle();const c = await store.getCheck(s, scam.id);assert.strictEqual(c.action, 'review');-- assert.deepStrictEqual(c.signals, ['owner_unavailable', 'payment_before_viewing', 'unusual_payment'], 'an unknown sign is dropped');++ assert.deepStrictEqual(c.signals, ['payment_before_viewing', 'unusual_payment', 'owner_unavailable'], 'only signs a real quote backs: unknown, unbacked and invented ones are dropped');++ assert.strictEqual(c.spans.length, 3, 'the invented quote is gone');assert.strictEqual(await store.countFlagged(s), 1);assert.strictEqual((await t.get(`/api/v1/listings/${scam.id}`)).status, 200, 'still public: a flag only asks staff to look');const page = await t.get('/staff?state=flagged', { as: ada });@@@ -110,13 +122,16 @@ const SCAM = 'I am abroad for work so I cannot show it. Send the deposit by wireassert.strictEqual((await store.getCheck(s, late.id)).action, 'allow');});-- await check('the output is bounded: review without a known sign is allow; quotes are trimmed and capped', () => {-- assert.deepStrictEqual(readOutput({ action: 'review', signals: ['none'] }), { action: 'allow', signals: [], spans: [], rationale: '' });-- assert.strictEqual(readOutput(null).action, 'allow');-- const many = readOutput({ action: 'review', signals: ['pressure'], spans: Array.from({ length: 30 }, () => ({ quote: ` ${'x'.repeat(400)} `, signal: 'nope' })) });++ await check('the output is grounded and bounded: a sign needs a quote from the listing; quotes are trimmed and capped', () => {++ const row = { title: 'Room', description: `Act fast, ${'x'.repeat(400)} today only.` };++ assert.deepStrictEqual(readOutput({ action: 'review', signals: ['none'] }, row), { action: 'allow', signals: [], spans: [], rationale: '' });++ assert.strictEqual(readOutput(null, row).action, 'allow');++ assert.strictEqual(readOutput({ action: 'review', signals: ['pressure'], spans: [{ quote: 'Wire it now', signal: 'pressure' }] }, row).action, 'allow', 'an invented quote backs nothing');++ const real = readOutput({ action: 'review', signals: ['pressure'], spans: [{ quote: 'ACT FAST', signal: 'pressure' }, { quote: 'today only', signal: 'nope' }] }, row);++ assert.deepStrictEqual([real.action, real.signals, real.spans.length, real.spans[1].signal], ['review', ['pressure'], 2, null], 'case and spacing do not matter');++ const many = readOutput({ action: 'review', spans: Array.from({ length: 30 }, () => ({ quote: ` ${'x'.repeat(400)} `, signal: 'pressure' })) }, row);assert.strictEqual(many.spans.length, 10);assert.strictEqual(many.spans[0].quote.length, 300);-- assert.strictEqual(many.spans[0].signal, null);});await check('without OpenVibe.AI nothing is screened and nothing starts', async () => {
@@ -46,14 +46,25 @@ function screenInput(row) {
@@ -46,14 +46,25 @@ function screenInput(row) {
};
};
}
}
-/** Bound and validate the workflow's output: unknown signs are dropped; `review` without a sign is `allow`. */
-function readOutput(output) {
+const norm = (t) => String(t == null ? '' : t).toLowerCase().replace(/[\u2018\u2019]/g, "'").replace(/[\u201c\u201d]/g, '"').replace(/\s+/g, ' ').trim();
+
+/**
+ * Bound, validate and ground the workflow's output in the listing it read. A quote counts only when its words really
+ * are in the listing's title or description (a model may paraphrase or invent one), and a sign counts only when a
+ * grounded quote is labelled with it: staff never read a sign the text does not show. (On its first production run
+ * the local model named all six signs for a text that showed three.) With no grounded sign the answer is allow.
+ */
+function readOutput(output, row = null) {
const o = output && typeof output === 'object' ? output : {};
const o = output && typeof output === 'object' ? output : {};
- const signals = [...new Set((Array.isArray(o.signals) ? o.signals : []).filter((x) => SIGNALS.includes(x)))];
- const action = o.action === 'review' && signals.length ? 'review' : 'allow';
- const spans = (Array.isArray(o.spans) ? o.spans : []).slice(0, 10)
- .filter((x) => x && typeof x.quote === 'string' && x.quote.trim())
+ const text = row ? norm(`${row.title || ''}\n${row.description || ''}`) : null;
+ const spans = (Array.isArray(o.spans) ? o.spans : [])
+ .filter((x) => x && typeof x.quote === 'string' && norm(x.quote))
+ .filter((x) => !text || text.includes(norm(x.quote)))
+ .slice(0, 10)
.map((x) => ({ quote: x.quote.trim().slice(0, 300), signal: SIGNALS.includes(x.signal) ? x.signal : null }));
.map((x) => ({ quote: x.quote.trim().slice(0, 300), signal: SIGNALS.includes(x.signal) ? x.signal : null }));
+ const backed = new Set(spans.map((x) => x.signal).filter(Boolean));
+ const signals = SIGNALS.filter((x) => backed.has(x));
+ const action = o.action === 'review' && signals.length ? 'review' : 'allow';
const rationale = typeof o.rationale === 'string' ? o.rationale.trim().slice(0, 1000) : '';
const rationale = typeof o.rationale === 'string' ? o.rationale.trim().slice(0, 1000) : '';
return { action, signals: action === 'review' ? signals : [], spans: action === 'review' ? spans : [], rationale };
return { action, signals: action === 'review' ? signals : [], spans: action === 'review' ? spans : [], rationale };
}
}
@@ -86,7 +97,7 @@ function createScreen({ s, ai = null, log = console }) {
@@ -86,7 +97,7 @@ function createScreen({ s, ai = null, log = console }) {
log.warn(`[Screen] listing ${id}: ${(err && err.message) || err}`);
log.warn(`[Screen] listing ${id}: ${(err && err.message) || err}`);
return 'failed';
return 'failed';
}
}
- const out = readOutput(run && run.output);
+ const out = readOutput(run && run.output, row);
await store.putCheck(s, { listingId: row.id, contentHash: hash, ...out, runId: (run && run.runId) || null });
await store.putCheck(s, { listingId: row.id, contentHash: hash, ...out, runId: (run && run.runId) || null });
if (out.action === 'review') log.log(`[Screen] listing ${row.id} flagged for staff: ${out.signals.join(', ')}`);
if (out.action === 'review') log.log(`[Screen] listing ${row.id} flagged for staff: ${out.signals.join(', ')}`);
return out.action;
return out.action;
@@ -25,7 +25,18 @@ const SCAM = 'I am abroad for work so I cannot show it. Send the deposit by wire
@@ -25,7 +25,18 @@ const SCAM = 'I am abroad for work so I cannot show it. Send the deposit by wire
return {
return {
runId: `run_${calls.length}`,
runId: `run_${calls.length}`,
output: scam
output: scam
- ? { action: 'review', signals: ['owner_unavailable', 'payment_before_viewing', 'unusual_payment', 'made_up'], rationale: 'Pay by wire before any viewing; the owner is away.', spans: [{ quote: 'Send the deposit by wire transfer today', signal: 'payment_before_viewing' }] }
+ ? {
+ action: 'review', rationale: 'Pay by wire before any viewing; the owner is away.',
+ // Named but unbacked (pressure, personal_data_request), unknown (made_up), and one invented quote:
+ // only the signs a quote from the listing stands behind reach staff.
+ signals: ['owner_unavailable', 'payment_before_viewing', 'unusual_payment', 'pressure', 'personal_data_request', 'made_up'],
+ spans: [
+ { quote: 'Send the deposit by wire transfer today', signal: 'payment_before_viewing' },
+ { quote: 'by wire transfer', signal: 'unusual_payment' },
+ { quote: 'I am abroad for work so I cannot show it.', signal: 'owner_unavailable' },
+ { quote: 'Pay in bitcoin within the hour', signal: 'pressure' },
+ ],
+ }
: { action: 'allow', signals: ['none'], rationale: '', spans: [] },
: { action: 'allow', signals: ['none'], rationale: '', spans: [] },
};
};
},
},
@@ -59,7 +70,8 @@ const SCAM = 'I am abroad for work so I cannot show it. Send the deposit by wire
@@ -59,7 +70,8 @@ const SCAM = 'I am abroad for work so I cannot show it. Send the deposit by wire
await t.ctx.screen.settle();
await t.ctx.screen.settle();
const c = await store.getCheck(s, scam.id);
const c = await store.getCheck(s, scam.id);
assert.strictEqual(c.action, 'review');
assert.strictEqual(c.action, 'review');
- assert.deepStrictEqual(c.signals, ['owner_unavailable', 'payment_before_viewing', 'unusual_payment'], 'an unknown sign is dropped');
+ assert.deepStrictEqual(c.signals, ['payment_before_viewing', 'unusual_payment', 'owner_unavailable'], 'only signs a real quote backs: unknown, unbacked and invented ones are dropped');
+ assert.strictEqual(c.spans.length, 3, 'the invented quote is gone');
assert.strictEqual(await store.countFlagged(s), 1);
assert.strictEqual(await store.countFlagged(s), 1);
assert.strictEqual((await t.get(`/api/v1/listings/${scam.id}`)).status, 200, 'still public: a flag only asks staff to look');
assert.strictEqual((await t.get(`/api/v1/listings/${scam.id}`)).status, 200, 'still public: a flag only asks staff to look');
const page = await t.get('/staff?state=flagged', { as: ada });
const page = await t.get('/staff?state=flagged', { as: ada });
@@ -110,13 +122,16 @@ const SCAM = 'I am abroad for work so I cannot show it. Send the deposit by wire
@@ -110,13 +122,16 @@ const SCAM = 'I am abroad for work so I cannot show it. Send the deposit by wire
assert.strictEqual((await store.getCheck(s, late.id)).action, 'allow');
assert.strictEqual((await store.getCheck(s, late.id)).action, 'allow');
});
});
- await check('the output is bounded: review without a known sign is allow; quotes are trimmed and capped', () => {
- assert.deepStrictEqual(readOutput({ action: 'review', signals: ['none'] }), { action: 'allow', signals: [], spans: [], rationale: '' });
- assert.strictEqual(readOutput(null).action, 'allow');
- const many = readOutput({ action: 'review', signals: ['pressure'], spans: Array.from({ length: 30 }, () => ({ quote: ` ${'x'.repeat(400)} `, signal: 'nope' })) });
+ await check('the output is grounded and bounded: a sign needs a quote from the listing; quotes are trimmed and capped', () => {
+ const row = { title: 'Room', description: `Act fast, ${'x'.repeat(400)} today only.` };
+ assert.deepStrictEqual(readOutput({ action: 'review', signals: ['none'] }, row), { action: 'allow', signals: [], spans: [], rationale: '' });
+ assert.strictEqual(readOutput(null, row).action, 'allow');
+ assert.strictEqual(readOutput({ action: 'review', signals: ['pressure'], spans: [{ quote: 'Wire it now', signal: 'pressure' }] }, row).action, 'allow', 'an invented quote backs nothing');
+ const real = readOutput({ action: 'review', signals: ['pressure'], spans: [{ quote: 'ACT FAST', signal: 'pressure' }, { quote: 'today only', signal: 'nope' }] }, row);
+ assert.deepStrictEqual([real.action, real.signals, real.spans.length, real.spans[1].signal], ['review', ['pressure'], 2, null], 'case and spacing do not matter');
+ const many = readOutput({ action: 'review', spans: Array.from({ length: 30 }, () => ({ quote: ` ${'x'.repeat(400)} `, signal: 'pressure' })) }, row);
assert.strictEqual(many.spans.length, 10);
assert.strictEqual(many.spans.length, 10);
assert.strictEqual(many.spans[0].quote.length, 300);
assert.strictEqual(many.spans[0].quote.length, 300);
- assert.strictEqual(many.spans[0].signal, null);
});
});
await check('without OpenVibe.AI nothing is screened and nothing starts', async () => {
await check('without OpenVibe.AI nothing is screened and nothing starts', async () => {