/* * ★ A twin model the kernel refuses reaches the reader as a named refusal, not INTERNAL_SERVER_ERROR. * * 2026-10-10: `twinReplay(miratek)` failed with the MES kernel's English sentence "MES twin requires a * production declaration" in the shell's global notice. The kernel now names it * (`twin-model-no-production-spec`, with `params`); the engine turns a named kernel error into a shell * `Refusal` wherever it loads or replays a model, and the resolvers that reach those places speak it. */ import { test } from 'node:test' import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import { Refusal, speakRefusal } from '@things-factory/shell' import { asModelRefusal, refusingModel } from '../server/engine/model-refusal.ts' /* The shape the kernel throws (`MesKernel.loadTwinModel`, twin-kernel 02f117f). */ function kernelRefusal(): Error { return Object.assign(new Error('MES twin requires a production declaration (ProductionSpec): …'), { code: 'twin-model-no-production-spec', params: {} }) } test('a named kernel error becomes a Refusal that says which twin', () => { const r = asModelRefusal(kernelRefusal(), 'miratek') assert.ok(r instanceof Refusal) assert.equal((r as Refusal).code, 'twin-model-no-production-spec') assert.deepEqual((r as Refusal).params, { instanceId: 'miratek' }) assert.match((r as Refusal).message, /requires a production declaration/, 'the English sentence stays as the fallback') }) test('an error without a name passes through untouched', () => { const plain = new Error('boom') assert.equal(asModelRefusal(plain, 'x'), plain) const codeOnly = Object.assign(new Error('x'), { code: 'ENOENT' }) assert.equal(asModelRefusal(codeOnly, 'x'), codeOnly, 'a code without params is a system error, not a refusal') assert.throws(() => refusingModel('x', () => { throw plain }), e => e === plain) }) test('spoken, it is a refusal in the reader language under error.', () => { let askedKey = '' const t = (key: string, opts?: any) => ((askedKey = key), `${opts.instanceId}: no production declaration`) const spoken = speakRefusal(asModelRefusal(kernelRefusal(), 'miratek') as Refusal, { t }) assert.equal(askedKey, 'error.twin-model-no-production-spec') assert.equal(spoken.message, 'miratek: no production declaration') assert.equal(spoken.extensions.code, 'BAD_USER_INPUT', 'not INTERNAL_SERVER_ERROR') assert.deepEqual(spoken.extensions.refusal, { code: 'twin-model-no-production-spec', params: { instanceId: 'miratek' } }) }) const ENGINE = readFileSync(new URL('../server/engine/twin-engine.ts', import.meta.url), 'utf8') test('every place the engine loads or replays a model goes through refusingModel', () => { const code = ENGINE.split('\n').filter(l => !/^\s*(\*|\/\/|\/\*)/.test(l)) const roads = code.filter(l => /\.loadTwinModel\(|\breplay(From|WithCheckpoint|Segments)?\(/.test(l)) assert.ok(roads.length >= 3, 'the engine still loads models somewhere') for (const l of roads) { /* a replay call that wraps onto the next line is inside a refusingModel opened on the line above */ const i = code.indexOf(l) assert.ok(/refusingModel\(/.test(l) || /refusingModel\([^)]*\(\)\s*=>\s*$/.test(code[i - 1]), `not wrapped: ${l.trim()}`) } }) test('the resolvers that reach those places speak refusals', () => { for (const f of [ 'twin-journal/twin-journal-query.ts', 'twin-model/twin-model-query.ts', 'twin-model/twin-model-item-query.ts', 'twin-lifecycle/twin-lifecycle-mutation.ts', 'twin-forecast/twin-forecast-query.ts' ]) { const src = readFileSync(new URL(`../server/service/${f}`, import.meta.url), 'utf8') assert.match(src, /@Resolver\(\)\n@UseMiddleware\(SpeakRefusals\)\nexport class/, f) } const ref = readFileSync(new URL('../server/service/reference/reference-resolver.ts', import.meta.url), 'utf8') assert.match(ref, /e instanceof Refusal \? refusalKey\(e\.code\)/, 'the import result speaks the same key') })