morphit/apps/web/scripts/featured-card-reputation-smoke.ts

115 lines
7.7 KiB
TypeScript

#!/usr/bin/env tsx
/**
* Smoke: featured order cards show the SAME trust signals as normal order
* cards — 🌱 new-trader sprout, ⭐ reputation score, trade count, and the
* truncated posting-key anchor (Ken, 2026-07-08).
*
* Featured cards already rendered through the shared `OrderCard`, so this was
* never a rendering bug: `/v1/featured` simply never joined the reputation and
* identity columns, so the card was handed an order with those fields absent
* and quietly dropped every signal. On exactly the cards a stranger is most
* likely to click.
*
* The dangerous fix would have been to paste the orderbook's aggregate into the
* featured query. That aggregate excludes sock-puppet pairs, coordinated
* pile-ons and review-concentration attackers; a copy that drifted would
* publish inflated reputation on the featured strip only. So the SQL lives in
* `$api/reputationJoin` once, and BOTH endpoints splice it in.
*/
import { readFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const WEB = join(__dirname, '..');
const REPO = join(WEB, '..', '..');
const shared = readFileSync(join(REPO, 'apps', 'indexer', 'src', 'api', 'reputationJoin.ts'), 'utf8');
const featuredApi = readFileSync(join(REPO, 'apps', 'indexer', 'src', 'api', 'featuredOrderbook.ts'), 'utf8');
const orderbookApi = readFileSync(join(REPO, 'apps', 'indexer', 'src', 'api', 'orderbook.ts'), 'utf8');
const clientTypes = readFileSync(join(REPO, 'packages', 'indexer-client', 'src', 'index.ts'), 'utf8');
const featuredUi = readFileSync(join(WEB, 'src', 'lib', 'components', 'FeaturedOrders.svelte'), 'utf8');
const orderCard = readFileSync(join(WEB, 'src', 'lib', 'components', 'OrderCard.svelte'), 'utf8');
let pass = 0;
let fail = 0;
function check(name: string, ok: boolean): void {
if (ok) {
pass++;
console.log(` \u2713 ${name}`);
} else {
fail++;
console.error(` \u2717 ${name}`);
}
}
// ─── ONE definition of the aggregate ─────────────────────────────────
check('shared reputationJoin module exists', /export function feedbackAggregateJoin/.test(shared));
// Strip `${...}` interpolations AND `--` SQL comments before looking for a
// bind placeholder: the aggregate's comments mention "$0.20 each", which is
// prose, not a parameter.
const sharedSql = shared
.replace(/\$\{[^}]*\}/g, '')
.split('\n')
.map((l) => l.replace(/--.*$/, ''))
.join('\n');
check('it is parameter-free (splices into any query safely)', !/\$\d/.test(sharedSql));
// Check the SQL, not the prose. The module's docblock NAMES all four tables, so
// `shared.includes('review_concentration')` passes even after someone deletes
// the clause from the query — proven by tamper-testing. Strip comments first.
const sqlOnly = shared
.replace(/\/\*[\s\S]*?\*\//g, '') // JS block comments
.split('\n')
.filter((l) => !l.trim().startsWith('*') && !l.trim().startsWith('//'))
.map((l) => l.replace(/--.*$/, '')) // SQL line comments
.join('\n');
for (const guard of ['suspicious_reciprocity', 'related_accounts', 'one_way_pile_on', 'review_concentration']) {
check(`the shared aggregate SQL still excludes ${guard}`, new RegExp(`FROM ${guard}\\b`).test(sqlOnly));
}
check('untethered feedback (no order_permlink) is still excluded', /fb\.order_permlink IS NOT NULL/.test(sqlOnly));
check('the composite score is computed in ONE place', /computeReputationScore/.test(shared) && /export function reputationFieldsFromRow/.test(shared));
// ─── both endpoints consume it ───────────────────────────────────────
check('orderbook.ts consumes the shared fragment (no private copy)', /feedbackAggregateJoin\('o'\)/.test(orderbookApi) && /accountsJoin\('o', 'a'\)/.test(orderbookApi));
// Scoped to the <=3 winning bidders: /v1/featured is polled by every homepage
// visitor, so aggregating the whole feedback table for three rows is real cost.
// Scoping only ADDS `AND fb.subject IN (...)` — it cannot relax an exclusion.
check('featured endpoint joins the shared aggregate, scoped to the winning bidders', /feedbackAggregateJoin\('o', 'SELECT bidder FROM winning_bids'\)/.test(featuredApi));
check('featured endpoint joins accounts (posting key + first trade)', /accountsJoin\('o', 'a'\)/.test(featuredApi));
check('featured endpoint selects the reputation columns', /reputationSelectColumns\('o', 'a'\)/.test(featuredApi));
check('featured endpoint maps them onto the order (sprout/score/count/key)', /\.\.\.reputationFieldsFromRow\(r\)/.test(featuredApi));
check('featured row type carries the reputation columns', /interface FeaturedRow extends ReputationRow/.test(featuredApi));
check('neither endpoint re-derives the aggregate inline', (orderbookApi.match(/suspicious_reciprocity/g) ?? []).length === 0 && (featuredApi.match(/suspicious_reciprocity/g) ?? []).length === 0);
// ─── the card can render them ────────────────────────────────────────
check('FeaturedOrders hands the whole order to OrderCard', /<OrderCard[\s\S]{0,80}order=\{o\}/.test(featuredUi));
check('OrderCard always renders the identity row (featured only changes the frame)', /<OrderPosterIdentity \{order\}/.test(orderCard) && !/\{#if !featured\}[\s\S]{0,120}<OrderPosterIdentity/.test(orderCard));
// ─── the featured payload is a COMPLETE OrderRecord ──────────────────
// FeaturedSlot.order is TYPED as OrderRecord. Before cp442 the endpoint sent a
// subset, so the type was lying to every consumer (the web app, the MCP server,
// any third party reading indexer-client).
check('featured carries asset_network (a USDT order must name its chain)', /o\.asset_network/.test(featuredApi) && /asset_network: r\.asset_network/.test(featuredApi));
check('featured carries created_at', /created_at: r\.created_at\.toISOString\(\)/.test(featuredApi));
check('featured carries engagement_24h via the SHARED engagement join (scoped)', /engagementJoin\('o', 'SELECT bidder FROM winning_bids'\)/.test(featuredApi) && /engagement_24h: r\.engagement_24h/.test(featuredApi));
check('orderbook.ts consumes the shared engagement join too (UNscoped — it lists many accounts)', /engagementJoin\('o'\)/.test(orderbookApi) && !/engagementJoin\('o',/.test(orderbookApi));
// ─── the network chip reaches the featured cards ─────────────────────
const chip = readFileSync(join(WEB, 'src', 'lib', 'orders', 'networkChip.ts'), 'utf8');
check('network-chip derivation is a shared helper', /export function networkChipFor/.test(chip));
check('it refuses to guess an unrecognised network', /return null;\s*\n\}/.test(chip));
check('FeaturedOrders passes a networkChip to OrderCard', /\{@const networkChip = networkChipFor\(o, \$_\)\}/.test(featuredUi) && /\{networkChip\}/.test(featuredUi));
const orderbookPage = readFileSync(join(WEB, 'src', 'routes', '[lang]', 'orderbook', '+page.svelte'), 'utf8');
check('the orderbook page uses the same helper (no inline ternary copy)', /networkChipFor\(o, \$_\)/.test(orderbookPage) && !/usdtRowNetwork/.test(orderbookPage));
// ─── slot-count comments no longer stale ─────────────────────────────
check('client types no longer claim "Currently 5" slots', !/Currently 5/.test(clientTypes));
console.log('');
if (fail === 0) {
console.log(`\u2713 all ${pass} featured-card-reputation scenarios passed`);
} else {
console.error(`\u2717 ${fail} of ${pass + fail} featured-card-reputation checks FAILED`);
process.exit(1);
}