Harden Scattermind rank feedback bridge
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn } from 'node:child_process';
|
||||
|
||||
const port = 43045 + Math.floor(Math.random() * 1000);
|
||||
const base = `http://127.0.0.1:${port}`;
|
||||
const server = spawn(process.execPath, ['server.js'], {
|
||||
cwd: new URL('..', import.meta.url),
|
||||
env: { ...process.env, PORT: String(port), APPWRITE_ENDPOINT: '', APPWRITE_PROJECT_ID: '', APPWRITE_API_KEY: '' },
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
let output = '';
|
||||
server.stdout.on('data', chunk => { output += chunk; });
|
||||
server.stderr.on('data', chunk => { output += chunk; });
|
||||
|
||||
async function waitForServer() {
|
||||
const deadline = Date.now() + 6000;
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const response = await fetch(`${base}/api/rank-feedback`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ optionsText: '- one\n- two' }),
|
||||
});
|
||||
if (response.status < 500) return;
|
||||
} catch {
|
||||
await new Promise(resolve => setTimeout(resolve, 120));
|
||||
}
|
||||
}
|
||||
throw new Error(`server did not become ready:\n${output}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await waitForServer();
|
||||
const response = await fetch(`${base}/api/rank-feedback`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
schema: 'prioritix-feature-set-v1',
|
||||
sourceName: 'Scattermind',
|
||||
artifactId: 'snapshot_123',
|
||||
snapshotTitle: 'Tiny shop idea clarity pass',
|
||||
idea: 'Scattermind clarified a small product idea. Ranker must defend the next build order, not create a dashboard.',
|
||||
context: 'Solo builder. Need a rank-ready build order after Snapshot / Concept Map. Avoid accounts, workspaces, and team voting.',
|
||||
mode: 'mvp',
|
||||
featureSet: {
|
||||
features: [
|
||||
{ id: 'bridge-contract', title: 'Snapshot to Ranker feature-set contract', description: 'Convert Concept Map next moves into a rank-ready feature set with provenance.' },
|
||||
{ id: 'build-order-preview', title: 'Build order preview', description: 'Show do first, validate next, defer, and park with reasons.' },
|
||||
{ id: 'workspace', title: 'Accounts and saved workspaces', description: 'Full dashboard with auth, workspace collaboration, team voting, and sync.' },
|
||||
{ id: 'billing', title: 'Subscription billing layer', description: 'Pricing, checkout, invoices, account plans, and admin controls.' },
|
||||
{ id: 'export', title: 'Exportable decision brief', description: 'Simple brief for sharing the defended build order.' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
});
|
||||
assert.equal(response.status, 200);
|
||||
const data = await response.json();
|
||||
assert.equal(data.ok, true);
|
||||
assert.equal(data.input.provenance.artifactId, 'snapshot_123');
|
||||
assert.equal(data.input.provenance.source, 'Scattermind');
|
||||
assert.equal(data.ranked.length, 5);
|
||||
assert.deepEqual(Object.keys(data.buildOrder), ['doFirst', 'validateNext', 'defer', 'park']);
|
||||
assert.equal(data.ranked[0].id, data.buildOrder.doFirst[0]);
|
||||
assert.notEqual(data.ranked[0].id, 'workspace', 'dashboard swamp must not win the bridge fixture');
|
||||
assert.ok(['defer', 'park'].includes(data.ranked.find(item => item.id === 'workspace').lane.id));
|
||||
assert.ok(['defer', 'park'].includes(data.ranked.find(item => item.id === 'billing').lane.id));
|
||||
assert.match(data.brief.summary, /nearest follow-up|strongest signal/i);
|
||||
console.log(JSON.stringify({ ok: true, top: data.ranked[0].id, provenance: data.input.provenance, buildOrder: data.buildOrder }, null, 2));
|
||||
} finally {
|
||||
server.kill('SIGTERM');
|
||||
}
|
||||
Reference in New Issue
Block a user