From feff782455bcb1d7d020898a07f0054e22906358 Mon Sep 17 00:00:00 2001
From: Felipe Sauer <120697114+felipesauer@users.noreply.github.com>
Date: Tue, 8 Sep 2026 16:30:25 -0400
Subject: [PATCH] The pin says which voice answered
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The guard that walks the surface asking which paths stamp a run has one way of
being wrong it cannot see: a synthesised line the declaration could not answer
for is refused before any action runs, and it then reads as a path that stamps
nothing. A pin was written for that, and it looked for the literal `Usage: mnema`.
Half of the premise under it is false. The misuse voice does carry the usage line
the help prints — the same text, `usageOf(command)` — but `Usage: ` is added
OUTSIDE it by commander's help formatter, and the misuse voice indents the line
with no prefix at all. Measured against the built binary, `mnema task`,
`mnema decision
`, `mnema link a b --rel` and `mnema completion powershell`
each came back `asked: false` AND `parserAnswered: false` at once, which is
exactly the silent misclassification the pin exists to catch. The pin saw the
help and none of the seven codes the surface words.
So the discriminant now calls the function the writing calls. `usageOf` is
exported with the reason on it, and one function in the walk's own support module
answers whether the parser spoke — off that function, so it cannot drift from the
line it looks for. It covers the parser's third answer too: a code nobody worded
carries no usage line at all and takes the same shape as a verb's own refusal, so
it is told apart by the code's namespace, which is an identifier and not prose.
Both sites that measured a path built their row from four copied fields; they now
build it through one function, which is the shape that let the classification be
wrong in two places at once.
What falsified the premise is asserted rather than only written down, and it is
asserted beside what is present: the case reads the usage line off `usageOf` and
requires it on the stream, then requires no `Usage:` there. An absence on its own
goes vacuous the day the invocation stops printing, because an empty stream
carries no prefix either.
And the rule has a second site the pin's own file did not know about.
`outside-a-project-the-surface-says-so.test.ts` classifies the same three paths as
answered by the parser, through a hand-kept table whose reason was three copies of
a sentence nothing read — a reconciliation of names can say a path refused
something else first without ever reading what it said. That reason is now the
one entry in the table a guard can check, asserted in both directions: a path
declared as answered by the parser has to have been, and no path that missed the
project may look like one.
---
packages/code/src/wiring/misuse.ts | 16 +-
...very-file-has-a-test-that-names-it.test.ts | 2 +-
...side-a-project-the-surface-says-so.test.ts | 49 ++++-
.../tests/support/the-line-a-path-takes.ts | 53 +++++-
...-refused-run-is-refused-everywhere.test.ts | 168 +++++++++++++++---
5 files changed, 255 insertions(+), 33 deletions(-)
diff --git a/packages/code/src/wiring/misuse.ts b/packages/code/src/wiring/misuse.ts
index 7808eaae..c887131d 100644
--- a/packages/code/src/wiring/misuse.ts
+++ b/packages/code/src/wiring/misuse.ts
@@ -361,8 +361,20 @@ function spoken(command: Command): string {
.join(' ');
}
-/** The line to type, exactly as this command's own `--help` prints it. */
-function usageOf(command: Command): string {
+/**
+ * The line to type, exactly as this command's own `--help` prints it.
+ *
+ * EXPORTED BECAUSE A GUARD READS IT TO TELL THE TWO VOICES APART, and it has to be this
+ * function rather than a copy. Both of the parser's answers carry this line — the help
+ * heads it with commander's `Usage: `, and the second line of a misuse above is it,
+ * indented — and a verb's own refusal never carries it, so it is what says the PARSER
+ * answered a line and not the path it names. A guard that retyped the text would go on
+ * passing while matching nothing the day the shape changed, which is what happened:
+ * `the-refused-run-is-refused-everywhere.test.ts` looked for `Usage: mnema` and therefore
+ * saw the help and NONE of the seven misuse wordings, because the prefix is the help
+ * formatter's and not this line's.
+ */
+export function usageOf(command: Command): string {
return command.createHelp().commandUsage(command);
}
diff --git a/packages/code/tests/every-file-has-a-test-that-names-it.test.ts b/packages/code/tests/every-file-has-a-test-that-names-it.test.ts
index 9ee33fe5..7845bae4 100644
--- a/packages/code/tests/every-file-has-a-test-that-names-it.test.ts
+++ b/packages/code/tests/every-file-has-a-test-that-names-it.test.ts
@@ -157,7 +157,7 @@ const TEST_TREE: readonly TestSource[] = READABLE.flatMap((pkg) =>
* and leaves 2145, which clears that floor and reddens nothing else in this file. Restate
* this number when the tree gains an import, which is the point of writing it down.
*/
-const CLAUSES_IN_THE_TREE = 2421;
+const CLAUSES_IN_THE_TREE = 2422;
const { importedBy, witnessedBy, unresolved } = witnessing(PRODUCTION, TEST_TREE, codeOnly);
diff --git a/packages/code/tests/outside-a-project-the-surface-says-so.test.ts b/packages/code/tests/outside-a-project-the-surface-says-so.test.ts
index 4ea4874e..01def49f 100644
--- a/packages/code/tests/outside-a-project-the-surface-says-so.test.ts
+++ b/packages/code/tests/outside-a-project-the-surface-says-so.test.ts
@@ -67,6 +67,7 @@ import {
pathsNamedInTables,
SERVES_A_CONNECTION,
SERVES_OR_REACHES_OUT,
+ theParserAnswered,
theSurface,
} from './support/the-line-a-path-takes.js';
import { held } from './support/the-record-held.js';
@@ -148,6 +149,25 @@ const SAYS_NO_TAIL_HOLDS_EVENTS: Readonly> = {
const NO_TAIL_TO_WITNESS =
'Refused (NO_TAIL): there is no tail here to witness — nothing has been recorded in these trees';
+/**
+ * THE ONE REASON IN THAT TABLE THAT IS NOT PROSE, because it is the only one a guard can
+ * ask the surface about.
+ *
+ * A reconciliation of NAMES can say a path refused something else first without ever
+ * reading what it said, so every reason in the table beside it is a note to a reader. This
+ * one is checked: `support/the-line-a-path-takes.ts` tells the parser's answer from a
+ * verb's own — off the product's own `usageOf`, which both of the parser's answers carry —
+ * and the case below asserts it for each path declared with this string, and asserts it is
+ * FALSE for every path that landed in box 1.
+ *
+ * It was written as three copies of a sentence nothing read. What that shape cost was
+ * measured next door, in the guard that classifies the same three paths for the run pin:
+ * it looked for the literal `Usage: mnema`, which the help carries and the misuse voice
+ * does not, and so it saw none of the seven codes the surface words.
+ */
+const THE_PARSER_ANSWERED_FIRST =
+ 'a group whose bare form routes nothing: the parser answers with usage';
+
/**
* A path whose own refusal arrives before the project is ever missed — with what it says.
*
@@ -160,9 +180,9 @@ const NO_TAIL_TO_WITNESS =
* because what a verb SAYS is not a coverage slice's to change.
*/
const REFUSES_SOMETHING_ELSE_FIRST: Readonly> = {
- run: 'a group whose bare form routes nothing: the parser answers with usage',
- key: 'a group whose bare form routes nothing: the parser answers with usage',
- tail: 'a group whose bare form routes nothing: the parser answers with usage',
+ run: THE_PARSER_ANSWERED_FIRST,
+ key: THE_PARSER_ANSWERED_FIRST,
+ tail: THE_PARSER_ANSWERED_FIRST,
'run end': 'with no id and no MNEMA_RUN there is no session named, which it says first',
show: 'FINDING: it answers "No record here" where there is no record at all',
'skill export': 'FINDING: it answers "No skill here" where there is no record at all',
@@ -175,6 +195,8 @@ interface Answered {
readonly path: string;
readonly failed: boolean;
readonly said: string;
+ /** Whether the PARSER answered rather than the path — read off the declaration. */
+ readonly parserAnswered: boolean;
}
/** What a declaration table tolerates, and what it does not — checked both ways. */
@@ -217,7 +239,13 @@ describe('outside a project the surface says so', () => {
},
};
await run(lineFor(routed, NOTHING_FOUNDED), io);
- answered.push({ path: routed.path, failed, said: [...err, ...out].join('\n') });
+ const said = [...err, ...out].join('\n');
+ answered.push({
+ path: routed.path,
+ failed,
+ said,
+ parserAnswered: theParserAnswered(routed, said),
+ });
}
} finally {
process.chdir(restore.cwd);
@@ -277,6 +305,19 @@ describe('outside a project the surface says so', () => {
stale: [],
});
+ // AND THE ONE REASON IN BOX 4 THAT IS CHECKABLE IS CHECKED, both ways. A path declared
+ // as answered by the parser has to have been, and — the half that keeps it from being
+ // a discriminant that says yes to everything — no path in box 1 may look like one.
+ // Three copies of that sentence were three notes to a reader before this.
+ const byTheParser = answered.filter((one) => one.parserAnswered).map((one) => one.path);
+ expect([...byTheParser].sort()).toEqual(
+ Object.entries(REFUSES_SOMETHING_ELSE_FIRST)
+ .filter(([, why]) => why === THE_PARSER_ANSWERED_FIRST)
+ .map(([path]) => path)
+ .sort(),
+ );
+ expect(missedTheProject.filter((one) => one.parserAnswered).map((one) => one.path)).toEqual([]);
+
// The four boxes are the whole surface, and box 1 holds most of it — a walk that
// stopped finding paths would leave every reconciliation above passing over nothing.
expect(answered.length).toBe(theSurface().length - Object.keys(SERVES_A_CONNECTION).length);
diff --git a/packages/code/tests/support/the-line-a-path-takes.ts b/packages/code/tests/support/the-line-a-path-takes.ts
index 56752b7b..f6f18a10 100644
--- a/packages/code/tests/support/the-line-a-path-takes.ts
+++ b/packages/code/tests/support/the-line-a-path-takes.ts
@@ -32,7 +32,7 @@
import type { Argument, Command, Option } from 'commander';
import { buildProgram, type CliIo } from '../../src/cli.js';
import { valuesDeclaredOn } from '../../src/wiring/enumerated.js';
-import { everyCommandOf, pathOf } from '../../src/wiring/misuse.js';
+import { everyCommandOf, pathOf, usageOf } from '../../src/wiring/misuse.js';
/** One invocable command path, and the declaration that says what it takes. */
export interface Routed {
@@ -240,6 +240,57 @@ export function lineFor(routed: Routed, fixture: Fixture): string[] {
return words;
}
+// ---------------------------------------------------------------------------
+// Which voice answered a line
+// ---------------------------------------------------------------------------
+
+/**
+ * The shape a usage error takes when the parser raised a code the product has no wording
+ * for: the code, then the message it came with.
+ *
+ * It is the third of the parser's three answers and the only one that carries no usage
+ * line, so {@link theParserAnswered} would miss it on the line's shape alone. The
+ * discriminant is the CODE's namespace and not the sentence — `commander.` is an
+ * identifier, and the sentence after it is a dependency's English.
+ *
+ * `wiring/report.ts` frames a refusal this way for the product's own codes too
+ * (`Refused (UNPROVEN_RUN): …`), which is exactly why the prefix has to be matched WITH
+ * the namespace: a verb's own no takes the same shape.
+ */
+export const A_CODE_NOBODY_WORDED = 'Refused (commander.';
+
+/**
+ * WHETHER THE PARSER ANSWERED A LINE, rather than the path the line names.
+ *
+ * A caller that walks the surface asking what each path DID has one way of being wrong
+ * that it cannot see: a synthesised line the declaration could not answer for is refused
+ * before any action runs, and it then looks exactly like a path that does not do the
+ * thing being measured. So the parser's answer has to be identifiable, and this is the
+ * one place that says how.
+ *
+ * IT IS THE PRODUCT'S OWN FUNCTION THAT IS READ, not a copy of its text.
+ * `wiring/misuse.ts` gives every command of the program one voice for a misuse and puts
+ * {@link usageOf} under the sentence; commander heads the help with `Usage: ` and the same
+ * line. So the line to type is what both of the parser's answers carry and a verb's own
+ * refusal never does — and calling `usageOf` is what keeps this from drifting from the
+ * shape it is looking for.
+ *
+ * WHAT THAT COST WHEN IT WAS A LITERAL, since this function exists because of it. The
+ * guard used to look for `Usage: mnema`, which the help carries and the misuse voice does
+ * NOT: the prefix is commander's help formatter's, added outside `usageOf`. All seven
+ * worded codes therefore read as "the parser did not answer" — measured against the built
+ * binary, `mnema task`, `mnema decision `, `mnema link a b --rel` and
+ * `mnema completion powershell` each print their sentence and an indented usage line with
+ * no `Usage:` anywhere on it.
+ *
+ * `said` is what went to the stream a refusal goes to. Both of the parser's answers are
+ * written there — the misuse voice by `speakUsageErrors`, and the help shown INSTEAD of an
+ * error by commander itself.
+ */
+export function theParserAnswered(routed: Routed, said: string): boolean {
+ return said.includes(usageOf(routed.command)) || said.includes(A_CODE_NOBODY_WORDED);
+}
+
/** Every table here that names a path, for a caller reconciling them against the walk. */
export function pathsNamedInTables(): string[] {
return [
diff --git a/packages/code/tests/the-refused-run-is-refused-everywhere.test.ts b/packages/code/tests/the-refused-run-is-refused-everywhere.test.ts
index 84cc2b04..9cda7cfa 100644
--- a/packages/code/tests/the-refused-run-is-refused-everywhere.test.ts
+++ b/packages/code/tests/the-refused-run-is-refused-everywhere.test.ts
@@ -73,6 +73,17 @@
* are no others, because a fourth would be a synthesised line the declaration could not
* answer for.
*
+ * THE PIN ITSELF WAS HALF BLIND WHEN IT WAS WRITTEN, and the correction is the shape worth
+ * keeping. It looked for the literal `Usage: mnema`, on the premise that the misuse voice
+ * carries the usage line the help prints. The TEXT is the same and the LINE is not: `Usage: `
+ * is added outside `usageOf` by commander's help formatter, so the pin saw the help and none
+ * of the seven codes the surface words — `mnema task`, `mnema decision `,
+ * `mnema link a b --rel` and `mnema completion powershell` each came back `asked: false` AND
+ * `parserAnswered: false`, which is exactly the silent misclassification the pin is for. It
+ * now reads the product's own `usageOf` (`support/the-line-a-path-takes.ts`), covers the
+ * parser's third answer — a code nobody worded, which carries no usage line — by its code's
+ * namespace, and the case that names all three is what fails if the literal comes back.
+ *
* WHAT IS NOT GUARDED AT ALL, and it is the order. The set of paths that ask is total only
* while `pinnedRun()` is reached before any refusal that depends on there being a project.
* Two shapes on this surface already return earlier than it does — `parseScope(...) ===
@@ -86,13 +97,15 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { PROJECT_DIR } from '@mnema/core';
-import { Command } from 'commander';
+import { Command, CommanderError } from 'commander';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { type CliIo, run } from '../src/cli.js';
import { renderPlain } from '../src/presentation/plain.js';
+import { misuseReport, usageOf } from '../src/wiring/misuse.js';
import { PIN_REFUSED, pinnedRunResolver, RUN_ENV } from '../src/wiring/run-pin.js';
import { mergeAForeignTail } from './support/a-tail-from-another-machine.js';
import {
+ A_CODE_NOBODY_WORDED,
type Fixture,
lineFor,
NOTHING_FOUNDED,
@@ -101,6 +114,7 @@ import {
type Routed,
routes,
SERVES_OR_REACHES_OUT,
+ theParserAnswered,
theSurface,
} from './support/the-line-a-path-takes.js';
import { held } from './support/the-record-held.js';
@@ -154,8 +168,20 @@ const NO_SUCH_SESSION = 'Refused (UNKNOWN_RUN)';
/** A well-formed id no record holds — the pin a stale shell carries. */
const A_RUN_NOBODY_OPENED = '00000000-0000-7000-8000-000000000000';
-/** What the surface puts on the stream when the PARSER, not a verb, answered. */
-const THE_PARSER_ANSWERED = 'Usage: mnema';
+/**
+ * WHAT SAYS THE PARSER ANSWERED is `support/the-line-a-path-takes.ts`'s, and it was a
+ * literal here.
+ *
+ * The literal was `Usage: mnema`, and the premise under it — written in the field's doc
+ * below — was that the second line of the misuse voice is the usage line the help prints,
+ * so a scan for the help's usage line would see both of the parser's answers. HALF OF
+ * THAT IS FALSE: the text is the same, but `Usage: ` is added OUTSIDE it by commander's
+ * help formatter, and the misuse voice indents `usageOf(command)` with no prefix at all.
+ * So the literal saw the help and none of the seven worded codes.
+ *
+ * What replaced it reads the product's own {@link usageOf}, so it cannot drift from the
+ * line it looks for, and it lives beside the walk because two files ask this question.
+ */
// ---------------------------------------------------------------------------
// What is accused
@@ -175,14 +201,33 @@ interface Refused {
*
* A synthesised line the declaration could not answer for is refused before any action
* of this surface runs, and it would then be counted as a path that does not stamp a
- * run, silently. It is measured off the usage line, which is the one thing only a parser
- * refusal puts on the stream: `wiring/misuse.ts` gives every command of the program one
- * voice for a misuse, and the second line of every one of those is the `usage()` the help
- * prints. A verb's own refusal never carries it.
+ * run, silently. What says so is {@link theParserAnswered}, which reads the product's
+ * own `usageOf` rather than a copy of its text; the case below names the parser's three
+ * answers and asserts this field over each of them.
*/
readonly parserAnswered: boolean;
}
+/**
+ * WHAT ONE LINE DID, in the terms both passes read — one function, because two sites read
+ * them.
+ *
+ * The two were four fields copied, and the copy is how the defect this pass closes stayed
+ * invisible: the classification lived in each site's expression, so neither could be
+ * wrong on its own and both were. A field added here reaches both, and a site that
+ * measured a path without going through this cannot exist.
+ */
+function whatItDid(routed: Routed, outcome: Outcome): Refused {
+ const said = outcome.err.join('\n');
+ return {
+ path: routed.path,
+ asked: said.includes(THE_PIN_REFUSED),
+ failed: outcome.failed,
+ printed: outcome.out,
+ parserAnswered: theParserAnswered(routed, said),
+ };
+}
+
/**
* Every path that asked for a run and then did not behave as a refusal — BY NAME.
*
@@ -300,14 +345,7 @@ async function everyPathUnderARefusedRun(): Promise {
const where = join(own, 'at', routed.path.replace(/ /g, '-'));
mkdirSync(where, { recursive: true });
process.chdir(where);
- const outcome = await mnema(lineFor(routed, NOTHING_FOUNDED));
- measured.push({
- path: routed.path,
- asked: outcome.err.join('\n').includes(THE_PIN_REFUSED),
- failed: outcome.failed,
- printed: outcome.out,
- parserAnswered: outcome.err.join('\n').includes(THE_PARSER_ANSWERED),
- });
+ measured.push(whatItDid(routed, await mnema(lineFor(routed, NOTHING_FOUNDED))));
}
return measured;
} finally {
@@ -455,6 +493,77 @@ describe('the refused run is refused everywhere', () => {
expect(bothHalvesSeen.length).toBe(15);
}, 300_000);
+ it('tells the parser’s three answers from a verb’s own, and names what the literal missed', async () => {
+ // THE PIN'S OWN NON-VACUITY, and the reason it is a case rather than a comment: the
+ // classification above only ever reports three paths, and it reported them for a
+ // literal that could not have seen four of the parser's seven codes. So each of the
+ // parser's answers is provoked BY NAME here, and so is a verb's own no beside them.
+ process.env.HOME = join(sandbox, 'home');
+ process.env.XDG_DATA_HOME = join(sandbox, 'data');
+ process.env[RUN_ENV] = A_RUN_NOBODY_OPENED;
+ process.chdir(sandbox);
+ const at = (path: string): Routed => theSurface().find((one) => one.path === path) as Routed;
+
+ // ONE · THE MISUSE VOICE, at the four lines measured against the built binary when
+ // this was found. Each is a line the DECLARATION could not answer for, which is the
+ // shape the pin exists to catch, and the literal `Usage: mnema` saw none of them: the
+ // prefix is commander's help formatter's, and this voice never goes through it.
+ const misused: readonly (readonly [string, readonly string[], string])[] = [
+ ['task', ['task'], 'a required positional given nothing'],
+ ['decision', ['decision', 'a title'], 'the SECOND positional given nothing'],
+ ['link', ['link', 'a', 'b', '--rel'], 'a flag that takes a value given none'],
+ ['completion', ['completion', 'powershell'], 'a value the declaration’s parser refuses'],
+ ];
+ for (const [path, argv, why] of misused) {
+ const outcome = await mnema(argv);
+ const said = whatItDid(at(path), outcome);
+ const where = `mnema ${argv.join(' ')} — ${why}`;
+ expect(said.parserAnswered, where).toBe(true);
+ // And it is not the pin resolver that spoke, which is the whole reason a lie here
+ // is silent: `asked: false` on its own reads as a verb that stamps no run.
+ expect(said.asked, where).toBe(false);
+ expect(said.failed, where).toBe(true);
+
+ // WHAT FALSIFIED THE PREMISE, asserted on the SAME output rather than only written
+ // in a comment: this voice carries the usage line WITHOUT the help's prefix. A
+ // guard that goes back to looking for the prefix is red here and above.
+ //
+ // The absence is asserted BESIDE what is present, in this order, because an
+ // absence alone goes vacuous the day the invocation stops printing: an empty
+ // stream contains no `Usage:` either. The line to type is what has to be there.
+ const stream = outcome.err.join('\n');
+ expect(stream, where).toContain(` ${usageOf(at(path).command)}`);
+ expect(stream, where).not.toContain('Usage:');
+ }
+
+ // TWO · THE HELP SHOWN INSTEAD OF AN ERROR — the answer the literal DID see, kept
+ // asserted so the repair is not read as having moved the boundary.
+ const bare = await mnema(['run']);
+ expect(theParserAnswered(at('run'), bare.err.join('\n'))).toBe(true);
+ expect(bare.err.join('\n')).toContain('Usage: mnema run');
+
+ // THREE · A CODE NOBODY WORDED, which is the one of the three that carries no usage
+ // line at all: it comes out as the code and the message, the same shape a verb's own
+ // refusal takes. So it is told apart by the code's NAMESPACE, and the line it is read
+ // off is the product's, built here because no invocation of this surface raises a code
+ // the file does not word (`one-voice-for-a-no.test.ts` owns that half).
+ const unworded = misuseReport({
+ command: at('task').command,
+ error: new CommanderError(1, 'commander.somethingNobodyWorded', 'whatever it says'),
+ typed: [],
+ }).map((line) => renderPlain(line));
+ expect(unworded.length).toBe(1);
+ expect(theParserAnswered(at('task'), unworded.join('\n'))).toBe(true);
+ expect(unworded[0]).toContain(A_CODE_NOBODY_WORDED);
+
+ // AND THE CONTRAST, without which every line above would pass on a discriminant that
+ // answers `true` to anything: a line the DECLARATION answered for, refused by the pin
+ // resolver, carries neither the usage line nor a commander code.
+ const refused = whatItDid(at('link'), await mnema(lineFor(at('link'), NOTHING_FOUNDED)));
+ expect(refused.asked).toBe(true);
+ expect(refused.parserAnswered).toBe(false);
+ }, 60_000);
+
it('accuses a path that asks and forgets to fail — on a program of its own', async () => {
// THE MECHANISM'S NON-VACUITY, and it is the shape the trunk cannot show: with the
// surface honest, the two cases above only ever say "nothing is accused". So a program
@@ -509,14 +618,7 @@ describe('the refused run is refused everywhere', () => {
const line = lineFor(routed, NOTHING_FOUNDED);
const port = capture();
await aProgramOfItsOwn(port.io).parseAsync(line, { from: 'user' });
- const outcome = port.outcome();
- measured.push({
- path: routed.path,
- asked: outcome.err.join('\n').includes(THE_PIN_REFUSED),
- failed: outcome.failed,
- printed: outcome.out,
- parserAnswered: outcome.err.join('\n').includes(THE_PARSER_ANSWERED),
- });
+ measured.push(whatItDid(routed, port.outcome()));
}
// Both were found to ask — so the derivation sees a command nothing told it about —
@@ -527,10 +629,26 @@ describe('the refused run is refused everywhere', () => {
// The accusation's other two edges, on rows of its own: a path that never asked is not
// judged, and one that reported after being refused is.
expect(
- accused([{ path: 'never-asks', asked: false, failed: false, printed: ['a report'] }]),
+ accused([
+ {
+ path: 'never-asks',
+ asked: false,
+ failed: false,
+ printed: ['a report'],
+ parserAnswered: false,
+ },
+ ]),
).toEqual([]);
expect(
- accused([{ path: 'talks-anyway', asked: true, failed: true, printed: ['a report'] }]),
+ accused([
+ {
+ path: 'talks-anyway',
+ asked: true,
+ failed: true,
+ printed: ['a report'],
+ parserAnswered: false,
+ },
+ ]),
).toEqual(['talks-anyway: printed a report after the run was refused']);
}, 60_000);
});