Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion examples/audiobook-curator/src/components/library-shelf.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface InspectionShelfProps {
}

const maximumCards = 20;
const maximumCallouts = 20;

interface RemainingFilesProps {
readonly count: number;
Expand Down Expand Up @@ -53,9 +54,12 @@ export const InventoryShelf = ({ receipt }: InventoryShelfProps) => (
<FileCard {...fileCardModel(file)} key={file.path} />
))}
<RemainingFiles count={receipt.files.length} />
{receipt.errors.map((row, index) => (
{receipt.errors.slice(0, maximumCallouts).map((row, index) => (
<Callout key={`${row.path}:${String(index)}`} tone="error">{`${row.path}: ${row.error}`}</Callout>
))}
{receipt.errors.length > maximumCallouts
? <Agent.Markdown>{`_+${String(receipt.errors.length - maximumCallouts)} more probe errors retained in the structured receipt._`}</Agent.Markdown>
: null}
</>
);

Expand Down
5 changes: 2 additions & 3 deletions examples/audiobook-curator/src/evidence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,8 @@ const pythonMatcher = (python: string, process: MediaProcess): AcousticMatcher =
const result = await process(python, ['-c', script, source, sample, String(options.chunkSeconds), options.verbose ? '1' : '0'], { signal: options.signal });
const line = result.stdout.split(/\r?\n/u).findLast((candidate) => candidate.startsWith(resultMarker));
if (line === undefined) throw new CuratorError('Audiolocate emitted no structured result.');
const parsed = JSON.parse(line.slice(resultMarker.length)) as JsonObject;
asRecord(parsed);
return Object.freeze(parsed);
const parsed = JSON.parse(line.slice(resultMarker.length)) as JsonValue;
return Object.freeze(asRecord(parsed)) as JsonObject;
} catch (error) {
throw new CuratorError(`Audiolocate is optional; install it for ${python}, or inject an acoustic matcher. ${errorMessage(error, '')}`.trim());
}
Expand Down
26 changes: 26 additions & 0 deletions examples/audiobook-curator/tests/evidence-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,32 @@ describe('optional identity evidence parity', () => {
expect(receipt).toMatchObject({ exitCode: 0, verifiedRecording: true });
});

it('normalizes non-object Audiolocate results to an empty fingerprint record', async () => {
const root = await mkdtemp(join(tmpdir(), 'curator-audiolocate-non-object-'));
roots.push(root);
const file = join(root, 'book.m4b');
await writeFile(file, 'book');

for (const output of ['null', '[1,2]']) {
const process: MediaProcess = async () => ({
stderr: '',
stdout: `__AGENT_BUNDLE_AUDIOLOCATE_RESULT__${output}\n`,
});
const receipt = await verifyAudibleSample({ asin: 'ASIN', file, sampleUrl: 'https://sample.example/book.mp3' }, {
audiolocatePython: 'python-test',
http: async () => Buffer.from('sample'),
process,
});

expect(receipt).toMatchObject({
exitCode: 2,
fingerprint: {},
verifiedRecording: false,
});
expect(Array.isArray(receipt.fingerprint)).toBe(false);
}
});

it('deduplicates ranked ASINs and isolates candidate failures', async () => {
const root = await mkdtemp(join(tmpdir(), 'curator-identify-'));
roots.push(root);
Expand Down
33 changes: 32 additions & 1 deletion examples/audiobook-curator/tests/route-unit/routes.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mkdir, mkdtemp, readFile, rm } from 'node:fs/promises';
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

Expand Down Expand Up @@ -110,6 +110,37 @@ it('renders composed inspection and inventory tool documents with unchanged valu
}
});

it('caps rendered inventory probe errors while retaining the complete receipt', async () => {
const directory = await mkdtemp(join(tmpdir(), 'curator-route-unit-inventory-errors-'));
const previousPath = process.env['PATH'];
try {
const source = join(directory, 'library');
await mkdir(source);
await Promise.all(Array.from({ length: 25 }, (_, index) =>
writeFile(join(source, `broken-${String(index)}.mp3`), 'not audio')));
process.env['PATH'] = directory;

const rendered = await renderRoute('tool:curator/inventory_sources', {
input: { source, strict: true },
});
const receipt = rendered.document.value as {
readonly errors: readonly unknown[];
};
if (rendered.document.root.kind !== 'result') throw new Error('expected inventory result document');
const errorCallouts = rendered.document.root.children.filter((node) => node.kind === 'error');

expect(receipt.errors).toHaveLength(25);
expect(errorCallouts).toHaveLength(20);
expectDocument(rendered)
.toContainMarkdown('_+5 more probe errors retained in the structured receipt._')
.toHaveValue(receipt);
} finally {
if (previousPath === undefined) delete process.env['PATH'];
else process.env['PATH'] = previousPath;
await rm(directory, { force: true, recursive: true });
}
});

it('renders the library-audit CLI route with in-flight progress and the canonical receipt (#102 stage 3)', async () => {
const directory = await mkdtemp(join(tmpdir(), 'curator-route-unit-audit-'));
try {
Expand Down
Loading