import fs from 'fs-extra'; import path from 'node:path'; import type { CliArgs } from '../../types.js'; import { EVIDENCE_SCHEMA_VERSION, createRunId, sha256Bytes, writeJsonFile, type EvidenceEnvelope, type EvidenceState } from '../../evidence/contract.js'; interface ParsedCapture { rows: Record[]; malformedLines: number[]; bytes: string; } interface VerificationDetails extends Record { input: { path: string; capture_manifest_path: string | null; row_count: number; malformed_line_count: number; }; matcher: { targets: string[]; anchor: string; confirm: string | null; exact_identifier_fields: string[]; }; observations: { fired_targets: string[]; anchor_fired: boolean; confirm_found: boolean | null; }; capture_complete: boolean | null; } function values(value: unknown): string[] { const raw = Array.isArray(value) ? value : value === undefined ? [] : [value]; return raw .flatMap(item => String(item).split(',')) .map(item => item.trim()) .filter(Boolean); } function asRecord(value: unknown): Record | null { return value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record : null; } function fired(row: Record, target: string): boolean { const data = asRecord(row['data']) ?? {}; if (row['name'] === target || data['name'] === target) return true; const parameters = Array.isArray(data['parameters']) ? data['parameters'] : []; return parameters.some(parameter => { const item = asRecord(parameter); if (!item) return false; return ['eventIdn', 'commandIdn', 'targetAction'].includes(String(item['name'])) && item['value'] === target; }); } function hasConfirm(row: Record, confirm: string): boolean { const data = asRecord(row['data']) ?? {}; const message = `${typeof row['message'] === 'string' ? row['message'] : ''} ${typeof data['message'] === 'string' ? data['message'] : ''}`; return message.includes(confirm); } async function parseJsonl(filePath: string): Promise { const bytes = await fs.readFile(filePath, 'utf8'); const rows: Record[] = []; const malformedLines: number[] = []; for (const [index, sourceLine] of bytes.split(/\r?\n/u).entries()) { const line = sourceLine.trim(); if (!line) continue; try { const parsed = JSON.parse(line) as unknown; const record = asRecord(parsed); if (!record) malformedLines.push(index + 1); else rows.push(record); } catch { malformedLines.push(index + 1); } } return { rows, malformedLines, bytes }; } interface CaptureManifestValidation { complete: boolean | null; valid: boolean; issues: string[]; } async function validateCaptureManifest( manifestPath: string | null, inputPath: string, parsedCapture: ParsedCapture ): Promise { if (!manifestPath) return { complete: null, valid: true, issues: [] }; try { const parsed = JSON.parse(await fs.readFile(manifestPath, 'utf8')) as unknown; const manifest = asRecord(parsed); if (!manifest) return { complete: false, valid: false, issues: ['manifest root is not an object'] }; const details = manifest ? asRecord(manifest['details']) : null; const evidence = Array.isArray(manifest['evidence']) ? manifest['evidence'] : []; const captureEvidence = evidence .map(asRecord) .find(item => item?.['kind'] === 'analytics_log_jsonl') ?? null; const issues: string[] = []; if (manifest['schema_version'] !== EVIDENCE_SCHEMA_VERSION) issues.push('unsupported or missing schema_version'); if (manifest['result_kind'] !== 'log_capture') issues.push('result_kind must be log_capture'); if (!details || typeof details['complete'] !== 'boolean') issues.push('details.complete must be boolean'); if (!details || details['row_count'] !== parsedCapture.rows.length) issues.push('details.row_count does not match capture'); if (!captureEvidence) { issues.push('analytics_log_jsonl evidence locator is missing'); } else { if (captureEvidence['sha256'] !== sha256Bytes(parsedCapture.bytes)) issues.push('capture SHA-256 does not match'); if (captureEvidence['row_count'] !== parsedCapture.rows.length) issues.push('evidence row_count does not match capture'); if (typeof captureEvidence['path'] !== 'string' || path.resolve(String(captureEvidence['path'])) !== path.resolve(inputPath)) { issues.push('evidence path does not identify the verified capture'); } } return { complete: details && typeof details['complete'] === 'boolean' ? details['complete'] : false, valid: issues.length === 0, issues }; } catch (error: unknown) { return { complete: false, valid: false, issues: [`manifest could not be read as JSON: ${error instanceof Error ? error.message : String(error)}`] }; } } export async function handleVerifyEventsCommand(args: CliArgs): Promise { const input = args._[1] ? path.resolve(String(args._[1])) : ''; const targets = values(args.target); const anchor = args.anchor ? String(args.anchor).trim() : ''; const confirm = args.confirm === undefined ? null : String(args.confirm); if (!input || targets.length === 0 || !anchor) { console.error('Usage: newo verify-events --target --anchor [--confirm ] [--capture-manifest ] [--manifest ]'); process.exitCode = 3; return; } if (!(await fs.pathExists(input))) { console.error(`Evidence capture not found: ${input}`); process.exitCode = 3; return; } const startedAt = new Date().toISOString(); const parsed = await parseJsonl(input); const explicitCaptureManifest = args['capture-manifest'] ? path.resolve(String(args['capture-manifest'])) : null; const siblingCaptureManifest = `${input}.manifest.json`; if (explicitCaptureManifest && !(await fs.pathExists(explicitCaptureManifest))) { console.error(`Capture manifest not found: ${explicitCaptureManifest}`); process.exitCode = 3; return; } const captureManifestPath = explicitCaptureManifest ?? ((await fs.pathExists(siblingCaptureManifest)) ? siblingCaptureManifest : null); const manifestValidation = await validateCaptureManifest(captureManifestPath, input, parsed); if (explicitCaptureManifest && !manifestValidation.valid) { console.error(`Invalid capture manifest: ${manifestValidation.issues.join('; ')}`); process.exitCode = 3; return; } const captureComplete = manifestValidation.valid ? manifestValidation.complete : false; const firedTargets = targets.filter(target => parsed.rows.some(row => fired(row, target))); const anchorFired = parsed.rows.some(row => fired(row, anchor)); const confirmFound = confirm === null || confirm === '' ? null : parsed.rows.some(row => hasConfirm(row, confirm)); const targetConfirmed = firedTargets.length > 0 && (confirmFound === null || confirmFound); const knownIncomplete = parsed.malformedLines.length > 0 || parsed.rows.length === 0 || captureComplete === false; let state: EvidenceState; if (targetConfirmed) state = 'PASS'; else if (knownIncomplete) state = 'INCONCLUSIVE'; else if (anchorFired) state = 'FAIL'; else state = 'INCONCLUSIVE'; const limitations: string[] = []; if (captureComplete === null) limitations.push('No compatible capture manifest attested pagination/cap completeness; the positive-control anchor remains the only completeness signal.'); if (captureComplete === false) limitations.push('The capture manifest marks the input partial, capped, unsettled, or otherwise incomplete; absence cannot produce FAIL.'); if (!manifestValidation.valid) limitations.push(`The capture manifest failed integrity validation: ${manifestValidation.issues.join('; ')}.`); if (parsed.malformedLines.length > 0) limitations.push(`Malformed JSONL lines (${parsed.malformedLines.join(', ')}) make negative evidence unsafe.`); if (parsed.rows.length === 0) limitations.push('Empty input cannot produce negative product evidence.'); if (firedTargets.length > 0 && confirmFound === false) limitations.push('A target identifier fired, but the required confirm substring was absent.'); const result: EvidenceEnvelope = { schema_version: EVIDENCE_SCHEMA_VERSION, result_kind: 'event_verification', run_id: createRunId(), phase: 'judge', state, fault_owner: state === 'INCONCLUSIVE' ? 'harness' : state === 'FAIL' ? 'product' : null, retryable: state === 'INCONCLUSIVE' ? true : state === 'FAIL' ? false : null, ...(state === 'INCONCLUSIVE' ? { fault_message: 'Capture completeness or positive-control evidence is insufficient.' } : state === 'FAIL' ? { fault_message: 'Target event was absent from an anchor-qualified capture.' } : {}), evidence: [{ kind: 'analytics_log_jsonl', path: input, sha256: sha256Bytes(parsed.bytes), media_type: 'application/x-ndjson', row_count: parsed.rows.length }], side_effects: [], cleanup_owner: 'none', timestamps: { started_at: startedAt, completed_at: new Date().toISOString() }, limitations, details: { input: { path: input, capture_manifest_path: captureManifestPath, row_count: parsed.rows.length, malformed_line_count: parsed.malformedLines.length }, matcher: { targets, anchor, confirm, exact_identifier_fields: ['name', 'data.name', 'data.parameters[eventIdn|commandIdn|targetAction].value'] }, observations: { fired_targets: firedTargets, anchor_fired: anchorFired, confirm_found: confirmFound }, capture_complete: captureComplete } }; const outputManifest = args.manifest ? path.resolve(String(args.manifest)) : null; if (outputManifest) await writeJsonFile(outputManifest, result); console.log(JSON.stringify(result, null, 2)); process.exitCode = state === 'PASS' ? 0 : state === 'FAIL' ? 1 : 2; }