Files
core2026/app/game-api/test/mutationEvidenceManifest.test.ts
T

148 lines
5.5 KiB
TypeScript

import { existsSync, readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
import { appRouter } from '../src/router.js';
const workspaceRoot = fileURLToPath(new URL('../../../', import.meta.url));
const manifestPath = path.join(workspaceRoot, 'docs/architecture/game-api-mutation-evidence.tsv');
const columns = [
'route',
'owner_boundary',
'ref_basis',
'actor_source',
'strongest_evidence',
'evidence_path',
'remaining_gap',
] as const;
type Column = (typeof columns)[number];
type ManifestRow = Record<Column, string>;
const allowedOwnerBoundaries = new Set([
'durable-journal',
'engine-owned',
'explicit-no-realtime-consumer',
'external-upload',
'mixed-saga',
'operational',
'read-only-mutation-transport',
'redis-projection',
'separate-access-journal',
'session-only',
]);
const allowedRefBases = new Set(['direct-endpoint', 'domain-command', 'core-only', 'read-only-transport']);
const allowedActorSources = new Set([
'gateway-token-user',
'optional-session-db-general',
'session-admin-role',
'session-user',
'session-user-db-general',
'session-user-engine-general',
]);
const allowedEvidenceLevels = new Set(['dynamic-ref', 'actual-db', 'redis', 'endpoint-unit', 'source-only']);
const expectedOwnerCounts: Record<string, number> = {
'durable-journal': 19,
'engine-owned': 38,
'explicit-no-realtime-consumer': 7,
'external-upload': 1,
'mixed-saga': 9,
operational: 3,
'read-only-mutation-transport': 2,
'redis-projection': 6,
'separate-access-journal': 1,
'session-only': 1,
};
const parseManifest = (): ManifestRow[] => {
const [header, ...lines] = readFileSync(manifestPath, 'utf8').trimEnd().split(/\r?\n/u);
if (header !== columns.join('\t')) {
throw new Error(`Unexpected mutation evidence manifest header: ${header ?? '<empty>'}`);
}
return lines.map((line, index) => {
const values = line.split('\t');
if (values.length !== columns.length || values.some((value) => value.length === 0)) {
throw new Error(`Invalid mutation evidence row at line ${index + 2}.`);
}
return Object.fromEntries(columns.map((column, valueIndex) => [column, values[valueIndex]])) as ManifestRow;
});
};
interface RuntimeProcedureDef {
type: string;
}
const readRuntimeProcedureDef = (procedure: unknown): RuntimeProcedureDef => {
if (typeof procedure !== 'function') {
throw new Error('Mounted tRPC procedure is not callable.');
}
const definition: unknown = Reflect.get(procedure, '_def');
if (typeof definition !== 'object' || definition === null) {
throw new Error('Mounted tRPC procedure has no runtime definition.');
}
const type: unknown = Reflect.get(definition, 'type');
if (typeof type !== 'string') {
throw new Error('Mounted tRPC procedure has an unexpected runtime definition.');
}
return { type };
};
const mountedMutationNames = (): string[] =>
Object.entries(appRouter._def.procedures)
.filter(([, procedure]) => readRuntimeProcedureDef(procedure).type === 'mutation')
.map(([name]) => name)
.sort();
describe('game-api mutation evidence manifest', () => {
it('lists every mounted mutation exactly once', () => {
const rows = parseManifest();
const manifestRoutes = rows.map(({ route }) => route);
expect(rows).toHaveLength(87);
expect(new Set(manifestRoutes).size).toBe(manifestRoutes.length);
expect(manifestRoutes).toEqual([...manifestRoutes].sort());
expect(manifestRoutes).toEqual(mountedMutationNames());
});
it('retains the bounded ownership taxonomy and allowed evidence vocabulary', () => {
const rows = parseManifest();
const ownerCounts = Object.fromEntries(
[...allowedOwnerBoundaries].map((owner) => [
owner,
rows.filter(({ owner_boundary }) => owner_boundary === owner).length,
])
);
expect(ownerCounts).toEqual(expectedOwnerCounts);
for (const row of rows) {
expect(allowedOwnerBoundaries.has(row.owner_boundary), row.route).toBe(true);
expect(allowedRefBases.has(row.ref_basis), row.route).toBe(true);
expect(allowedActorSources.has(row.actor_source), row.route).toBe(true);
expect(allowedEvidenceLevels.has(row.strongest_evidence), row.route).toBe(true);
expect(row.remaining_gap, row.route).toMatch(/^[a-z0-9-]+$/u);
expect(row.evidence_path.startsWith('app/') || row.evidence_path.startsWith('tools/')).toBe(true);
expect(existsSync(path.join(workspaceRoot, row.evidence_path)), row.route).toBe(true);
}
});
it('records the only unauthenticated mutation boundaries explicitly', () => {
const rowsByRoute = new Map(parseManifest().map((row) => [row.route, row]));
expect(rowsByRoute.get('auth.exchangeGatewayToken')?.actor_source).toBe('gateway-token-user');
expect(rowsByRoute.get('public.recordAccess')?.actor_source).toBe('optional-session-db-general');
const otherPublicActors = [...rowsByRoute]
.filter(([route]) => route !== 'auth.exchangeGatewayToken' && route !== 'public.recordAccess')
.filter(([, row]) => !row.actor_source.startsWith('session-'))
.map(([route]) => route);
expect(otherPublicActors).toEqual([]);
});
});