Files
psf-memo/psf-memo-indexer/acceptance/lib/runner-worker.js
T
Chris Troutner 30c0949d3f Harden and review post-heights-index refactor
Architectural review of the refactorer's postHeights index work. Extract a
shared assemblePostPage helper and a ListUseCase base class to remove DRY
duplication in the post-list use cases. Build the gherkin-mutator runner
adapter for psf-memo-db and psf-memo-indexer. Add boundary and tie-break
tests to kill all surviving language mutations across the changed files
(post-query, pagination, list use cases, indexer post handler).

Mutation: all changed source files fully killed. DRY: clean. CRAP <= 6.
Soft Gherkin acceptance mutation documented survivors (see review report).

By architect.
2026-08-26 11:39:12 -07:00

74 lines
2.2 KiB
JavaScript

/*
Persistent runner adapter for the APS gherkin-mutator (psf-memo-indexer).
The mutator starts this process (once per worker) and sends mutation jobs
over newline-delimited JSON on stdin. Each job carries the path to a mutated
feature JSON IR; this worker evaluates it through the same acceptance runtime
and step handlers used by the normal acceptance pipeline and replies with the
runner outcome.
Protocol (mutator-spec.md):
request: { "id", "feature_json", "generated_dir", "work_dir" }
response: { "id", "outcome", "output", "error", "duration" }
outcome: test_success | test_failure | infrastructure_error
test_failure (acceptance failed) -> mutation killed
test_success (acceptance passed) -> mutation survived
stdout is reserved for JSON responses. The indexer libraries log to stdout
via console.log, so that logging is redirected to stderr below; otherwise the
mutator would read non-JSON lines as worker responses.
*/
import readline from 'node:readline'
import fs from 'node:fs'
console.log = (...args) => console.error('[worker]', ...args)
const { runFeature } = await import('./runtime.js')
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
})
rl.on('line', async (line) => {
const started = Date.now()
const respond = (payload) => {
process.stdout.write(`${JSON.stringify(payload)}\n`)
}
let job
try {
job = JSON.parse(line)
} catch (err) {
respond({ id: 'unknown', outcome: 'infrastructure_error', output: '', error: `bad job: ${err.message}`, duration: Date.now() - started })
return
}
try {
const ir = JSON.parse(fs.readFileSync(job.feature_json, 'utf8'))
const report = await runFeature(ir)
respond({
id: job.id,
outcome: report.failures === 0 ? 'test_success' : 'test_failure',
output: report.results.map((r) => `${r.status} ${r.name}`).join('\n'),
error: '',
duration: Date.now() - started
})
} catch (err) {
respond({
id: job.id,
outcome: 'infrastructure_error',
output: '',
error: err.message,
duration: Date.now() - started
})
}
})
rl.on('close', () => {
process.exit(0)
})