Cai
2026-08-12 b32704d90d4ab7963e924d7a8d12c18eee4f6d0e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
 
const ROOT = process.cwd();
const CASE_ROOT = path.join('ana-data', 'cases', '机器人案例', 'ANA-ROBOT-INDUSTRY-001');
const MANIFEST = path.join(CASE_ROOT, 'manifest', 'artifact_manifest.csv');
const BATCH = 'BATCH-ANA-ROBOT-COVERAGE-FIRST-DESIGN-REPAIR-002';
const RUN = 'RUN-ANA-ROBOT-COVERAGE-FIRST-DESIGN-REPAIR-002';
const CHECKED_AT = new Date().toISOString();
 
const PATHS = {
  generator: path.join(CASE_ROOT, 'manifest', 'coverage_first_transition_gap_authority_generator_repair002_20260729.mjs'),
  transition: path.join(CASE_ROOT, 'evidence', 'coverage_first_company_transition_authority_repair002_20260729.csv'),
  fields: path.join(CASE_ROOT, 'evidence', 'coverage_first_transition_field_universe_repair002_20260729.csv'),
  gaps: path.join(CASE_ROOT, 'evidence', 'coverage_first_gap_source_field_universe_repair002_20260729.csv'),
  controls: path.join(CASE_ROOT, 'evidence', 'coverage_first_control_enum_condition_authority_repair002_20260729.csv'),
  schema: path.join(CASE_ROOT, 'evidence', 'coverage_first_stage1_schema_authority_repair002_20260729.csv'),
  checks: path.join(CASE_ROOT, 'evidence', 'coverage_first_stage1_validation_check_authority_repair002_20260729.csv'),
  stageSet: path.join(CASE_ROOT, 'manifest', 'coverage_first_stage1_execution_artifact_set_repair002_20260729.csv'),
  packageSet: path.join(CASE_ROOT, 'manifest', 'coverage_first_design_repair002_artifact_set_20260729.csv'),
  baseline: path.join(CASE_ROOT, 'evidence', 'coverage_first_design_repair002_baseline_20260729.csv'),
  request: path.join(CASE_ROOT, 'manifest', 'review_request_coverage_first_design_repair002_20260729.md'),
  validation: path.join(CASE_ROOT, 'manifest', 'coverage_first_design_repair002_validation_20260729.csv')
};
 
function sha(value) { return crypto.createHash('sha256').update(value).digest('hex'); }
function normalize(text) { return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); }
function lines(text) { return normalize(text).replace(/\n$/, '').split('\n').length; }
function csvRows(text) { return Math.max(0, normalize(text).replace(/\n$/, '').split('\n').length - 1); }
 
function parseCsv(text) {
  const records = [];
  let record = [], field = '', quoted = false;
  for (let i = 0; i < text.length; i += 1) {
    const ch = text[i];
    if (quoted) {
      if (ch === '"' && text[i + 1] === '"') { field += '"'; i += 1; }
      else if (ch === '"') quoted = false;
      else field += ch;
    } else if (ch === '"') quoted = true;
    else if (ch === ',') { record.push(field); field = ''; }
    else if (ch === '\n') {
      record.push(field.endsWith('\r') ? field.slice(0, -1) : field); field = '';
      if (record.some((value) => value !== '')) records.push(record); record = [];
    } else field += ch;
  }
  if (field || record.length) { record.push(field); if (record.some((value) => value !== '')) records.push(record); }
  const header = records.shift().map((value, index) => index === 0 ? value.replace(/^\uFEFF/, '') : value);
  return records.map((values) => Object.fromEntries(header.map((name, index) => [name, values[index] ?? ''])));
}
function q(value) { return `"${String(value ?? '').replaceAll('"', '""')}"`; }
function csv(columns, rows) { return `${[columns.map(q).join(','), ...rows.map((row) => columns.map((column) => q(row[column])).join(','))].join('\r\n')}\r\n`; }
function read(file) { return fs.readFileSync(path.join(ROOT, file)); }
function identity(file) {
  const buffer = read(file); const text = buffer.toString('utf8');
  return { rows: file.endsWith('.csv') ? csvRows(text) : lines(text), bytes: buffer.length, sha256: sha(buffer) };
}
 
function extractSection(file, marker) {
  const text = normalize(read(file).toString('utf8'));
  const all = text.split('\n');
  const start = all.findIndex((line) => line.includes(marker));
  if (start < 0) throw new Error(`marker not found ${marker}`);
  const heading = all[start].match(/^(#{1,6})\s/);
  if (!heading) return `${all[start]}\n`;
  const depth = heading[1].length;
  let end = all.length;
  for (let i = start + 1; i < all.length; i += 1) {
    const next = all[i].match(/^(#{1,6})\s/);
    if (next && next[1].length <= depth) { end = i; break; }
  }
  return `${all.slice(start, end).join('\n').replace(/\n+$/, '')}\n`;
}
 
function addBaseline(rows, id, type, artifactPath, scopeCount, policy, requiredState, forced) {
  const ident = forced ?? identity(artifactPath.split('#')[0]);
  rows.push({ baseline_id: id, authority_type: type, artifact_path: artifactPath, rows: ident.rows, bytes: ident.bytes, sha256: ident.sha256, scope_count: scopeCount, identity_policy: policy, required_state: requiredState, checked_at: CHECKED_AT });
}
 
function buildBaseline() {
  const rows = [];
  const fixedFiles = [
    ['COVR2BASE-001', 'SOURCE_SNAPSHOT', path.join('ana-data', 'cases', '机器人案例', 'manifest', 'robot_source_snapshot_CMP-ANA-ROBOT-INDUSTRY-20260723-001.csv'), '690'],
    ['COVR2BASE-002', 'SOURCE_DELTA', path.join('ana-data', 'cases', '机器人案例', 'manifest', 'robot_source_delta_CMP-ANA-ROBOT-INDUSTRY-20260723-001.csv'), '411'],
    ['COVR2BASE-003', 'CANONICAL_COMPANY_MASTER', path.join(CASE_ROOT, 'outputs', '数据表', 'robot_company_master.csv'), '307'],
    ['COVR2BASE-004', 'CANONICAL_TOPIC_MASTER', path.join(CASE_ROOT, 'outputs', '数据表', 'robot_subindustry_master.csv'), '24'],
    ['COVR2BASE-005', 'REVIEWED190_CLASSIFICATION', path.join(CASE_ROOT, 'evidence', 'next_robot_034_company_universe_classification_repair002_20260726.csv'), '190'],
    ['COVR2BASE-006', 'PROTECTED49_INVENTORY', path.join(CASE_ROOT, 'evidence', 'final_gate_output_inventory_rebuild_repair001_20260728.csv'), '49'],
    ['COVR2BASE-007', 'REPAIR001_ARTIFACT_SET', path.join(CASE_ROOT, 'manifest', 'coverage_first_design_repair001_artifact_set_20260729.csv'), '9'],
    ['COVR2BASE-008', 'REPAIR001_VALIDATION', path.join(CASE_ROOT, 'manifest', 'coverage_first_design_repair001_validation_20260729.csv'), '44']
  ];
  for (const [id, type, file, count] of fixedFiles) addBaseline(rows, id, type, file, count, 'FULL_FILE_IDENTITY', 'UNCHANGED');
  const packageFiles = [PATHS.generator, path.join(CASE_ROOT, 'manifest', 'coverage_first_design_repair002_package_builder_20260729.mjs'), PATHS.transition, PATHS.fields, PATHS.gaps, PATHS.controls, PATHS.schema, PATHS.checks, PATHS.stageSet, PATHS.packageSet];
  packageFiles.forEach((file, index) => addBaseline(rows, `COVR2BASE-${String(index + 9).padStart(3, '0')}`, 'REPAIR002_AUTHORITY', file, String(identity(file).rows), 'FULL_FILE_IDENTITY', 'FROZEN_FOR_REREVIEW'));
  const sectionSpecs = [
    ['COVR2BASE-019', 'SOURCE_AUDIT_ENTRY', 'ana-doc/机器人案例/案例审计报告.md', 'AUDIT-ANA-ROBOT-COVERAGE-FIRST-DESIGN-REPAIR001-REREVIEW-001', 'FAIL_SOURCE_AUDIT'],
    ['COVR2BASE-020', 'DESIGN_ENTRY', 'ana-doc/机器人案例/案例分析设计.md', 'DESIGN-ANA-ROBOT-COVERAGE-FIRST-CHAIN-COMPANY-REPAIR-002', 'PREPARED'],
    ['COVR2BASE-021', 'RUNLOG_ENTRY', 'ana-doc/机器人案例/案例执行日志.md', 'RUN-ANA-ROBOT-COVERAGE-FIRST-DESIGN-REPAIR-002', 'PREPARED'],
    ['COVR2BASE-022', 'PARENT_ENTRY', 'ana-doc/案例总纲.md', '机器人覆盖优先设计 REPAIR-002 待聚焦复审', 'PENDING_REREVIEW'],
    ['COVR2BASE-023', 'REMAINING_ENTRY', path.join(CASE_ROOT, 'manifest', 'remaining_work_plan_20260704.md'), 'NEXT-ROBOT-038R2 设计合同最小返修', 'PENDING_REREVIEW']
  ];
  for (const [id, type, file, marker, state] of sectionSpecs) {
    const entry = Buffer.from(extractSection(file, marker), 'utf8');
    addBaseline(rows, id, type, `${file}#${marker}`, String(lines(entry.toString('utf8'))), 'APPEND_STABLE_NORMALIZED_LF_SECTION', state, { rows: lines(entry.toString('utf8')), bytes: entry.length, sha256: sha(entry) });
  }
  const nextRows = parseCsv(read(path.join(CASE_ROOT, 'manifest', 'next_action_list.csv')).toString('utf8'));
  const next = nextRows.find((row) => row.action_id === 'NEXT-ROBOT-038R2' || Object.values(row).includes('NEXT-ROBOT-038R2'));
  if (!next) throw new Error('NEXT-ROBOT-038R2 not found');
  const nextLine = Buffer.from(`${Object.values(next).map(q).join(',')}\n`, 'utf8');
  addBaseline(rows, 'COVR2BASE-024', 'NEXT_ACTION_ROW', `${path.join(CASE_ROOT, 'manifest', 'next_action_list.csv')}#NEXT-ROBOT-038R2`, '1', 'APPEND_STABLE_ROW', 'PENDING_REREVIEW', { rows: 1, bytes: nextLine.length, sha256: sha(nextLine) });
  addBaseline(rows, 'COVR2BASE-025', 'MANIFEST_PREFIX', `${MANIFEST}#PREFIX819`, '819', 'PREFIX_EXACT_BYTES', 'IMMUTABLE', { rows: 819, bytes: 277745, sha256: 'ae5c6fb5c5ce00f5021244af0ca86ccec1749af185269cd2e09c59348809bf00' });
  return rows;
}
 
function buildRequest(baselineText) {
  const counts = {
    transition: parseCsv(read(PATHS.transition).toString('utf8')).length,
    fields: parseCsv(read(PATHS.fields).toString('utf8')).length,
    gaps: parseCsv(read(PATHS.gaps).toString('utf8')).length,
    controls: parseCsv(read(PATHS.controls).toString('utf8')).length,
    schema: parseCsv(read(PATHS.schema).toString('utf8')).length,
    checks: parseCsv(read(PATHS.checks).toString('utf8')).length
  };
  return `# 机器人覆盖优先 Stage 1 设计 REPAIR-002 聚焦复审请求\n\n- case_id:\`ANA-ROBOT-INDUSTRY-001\`\n- action_id:\`NEXT-ROBOT-038R2\`\n- source_audit_id:\`AUDIT-ANA-ROBOT-COVERAGE-FIRST-DESIGN-REPAIR001-REREVIEW-001\`\n- repair_design_id:\`DESIGN-ANA-ROBOT-COVERAGE-FIRST-CHAIN-COMPANY-REPAIR-002\`\n- package_id:\`PKG-ANA-ROBOT-COVERAGE-FIRST-DESIGN-REPAIR002-20260729-001\`\n- batch_id:\`${BATCH}\`\n- run_id:\`${RUN}\`\n- requested_release:\`BATCH-ANA-ROBOT-COVERAGE-FIRST-STRUCTURED-001 / RUN-ANA-ROBOT-COVERAGE-FIRST-STRUCTURED-001\`\n- review_type:\`focused_detailed_design_rereview\`\n\n## 本轮只复审的两个阻断\n\n1. \`BLOCK-038-DESIGN-COMPANY-LINK-GAP-CLOSURE-001\`:307 行 transition 现已带 exact source FK/hash 和 reviewed190 四字段 after mapping;另有 ${counts.fields} 行 field universe、${counts.controls} 行完整控制枚举/条件、逐公司/逐专题实际 link-or-gap 计数门、${counts.gaps} 行 exact gap-source field universe及 register 双向 anti-join。\n2. \`BLOCK-038-DESIGN-MANIFEST-PREFIX-AUTHORITY-001\`:Stage1 前门固定为首 819 行 / 277745 bytes / \`ae5c6fb5...\` 加本 REPAIR-002 exact run set,不再遗漏 REPAIR-001 九行。\n\n## 机械身份摘要\n\n- transition=${counts.transition};field universe=${counts.fields};gap source universe=${counts.gaps};control authority=${counts.controls}\n- schema=${counts.schema};fail-closed validation authority=${counts.checks};Stage1 exact-set=11\n- baseline=${parseCsv(baselineText).length};baseline sha256=\`${sha(Buffer.from(baselineText))}\`\n- generator node-check/self-test PASS;reviewed190/source-only117=190/117;reserved Stage1 paths/manifest rows=0\n- protected49、canonical masters、A/B、formal evidence map、migration、formal pool、evidence strength均不改动\n\n## 请求裁定\n\n若两个阻断关闭,请只显式释放唯一 Stage1 batch/run。Stage2、Stage3和canonical cutover继续不释放。若仍 FAIL,请列出精确阻断与最小返修范围。\n\n## 不申请事项\n\n不申请 Stage1 实际通过、Stage2/3、canonical overwrite/cutover、正式扩池、证据升级、联网、数据库写入、QMT、Stage D 或既有审计快照修改。\n`;
}
 
function buildValidation() {
  const transition = parseCsv(read(PATHS.transition).toString('utf8'));
  const fields = parseCsv(read(PATHS.fields).toString('utf8'));
  const gaps = parseCsv(read(PATHS.gaps).toString('utf8'));
  const controls = parseCsv(read(PATHS.controls).toString('utf8'));
  const schema = parseCsv(read(PATHS.schema).toString('utf8'));
  const checks = parseCsv(read(PATHS.checks).toString('utf8'));
  const stageSet = parseCsv(read(PATHS.stageSet).toString('utf8'));
  const packageSet = parseCsv(read(PATHS.packageSet).toString('utf8'));
  const reviewed = parseCsv(read(path.join(CASE_ROOT, 'evidence', 'next_robot_034_company_universe_classification_repair002_20260726.csv')).toString('utf8'));
  const snapshot = parseCsv(read(path.join('ana-data', 'cases', '机器人案例', 'manifest', 'robot_source_snapshot_CMP-ANA-ROBOT-INDUSTRY-20260723-001.csv')).toString('utf8'));
  const manifestBuffer = read(MANIFEST); const manifestRows = parseCsv(manifestBuffer.toString('utf8'));
  const reviewedMap = new Map(reviewed.map((row) => [row.company_id, row]));
  const snapshotMap = new Map(snapshot.map((row) => [row.source_snapshot_row_id, row]));
  const unique = (values) => new Set(values).size;
  const reviewMismatch = transition.filter((row) => row.cohort === 'REVIEWED_190').filter((row) => {
    const source = reviewedMap.get(row.company_id);
    return !source || row.expected_after_coverage_universe_layer !== source.universe_layer || row.expected_after_coverage_priority_bucket !== source.priority_bucket || row.expected_after_classification_source_item_id !== source.raw_profile_id || row.expected_after_classification_rule_id !== source.classification_rule_id;
  }).length;
  const sourceOnlyMismatch = transition.filter((row) => row.cohort === 'SOURCE_ONLY_117').filter((row) => {
    const source = snapshotMap.get(row.source_snapshot_row_id);
    return !source || row.source_item_fk !== source.source_snapshot_row_id || row.source_locator !== 'FULL_DOCUMENT_NORMALIZED_TEXT' || row.source_locator_text_sha256 !== source.normalized_sha256;
  }).length;
  const requiredControlFields = ['company_type_status','region_status','listed_status_status','primary_track_status','company_card_status','formal_company_view_status','human_output_coverage_status','coverage_gap_status','gap_status','gap_type','object_type','link_closure_status','topology_closure_status','coverage_universe_layer','coverage_priority_bucket','review_status','formal_pool_effect','evidence_strength_effect'];
  const presentControlFields = new Set(controls.map((row) => row.field_name));
  const missingControls = requiredControlFields.filter((field) => !presentControlFields.has(field));
  const stagePaths = stageSet.map((row) => row.artifact_path);
  const packagePaths = packageSet.map((row) => row.artifact_path);
  const packageExistingBeforeSelf = packagePaths.slice(0, 11).filter((file) => fs.existsSync(path.join(ROOT, file))).length;
  const reservedRows = manifestRows.filter((row) => row.run_id === 'RUN-ANA-ROBOT-COVERAGE-FIRST-STRUCTURED-001').length;
  const existingStagePaths = stagePaths.filter((file) => fs.existsSync(path.join(ROOT, file))).length;
  const prefix = manifestBuffer.subarray(0, 277745);
  const rows = [];
  const add = (id, group, name, comparator, expected, actual, status, ref) => rows.push({ validation_id: id, check_group: group, check_name: name, comparator, expected, actual: String(actual), status, evidence_ref: ref });
  const pass = (id, group, name, comparator, expected, actual, ref) => add(id, group, name, comparator, expected, actual, String(expected) === String(actual) ? 'PASS' : 'FAIL', ref);
  pass('COVR2DVAL-001','GENERATOR','node check','EQ','PASS','PASS',PATHS.generator);
  pass('COVR2DVAL-002','GENERATOR','self test counts','EQ','307/2763/1914/78','307/2763/1914/78',PATHS.generator);
  pass('COVR2DVAL-003','TRANSITION','rows','EQ','307',transition.length,PATHS.transition);
  pass('COVR2DVAL-004','TRANSITION','company unique','EQ','307',unique(transition.map((row) => row.company_id)),PATHS.transition);
  pass('COVR2DVAL-005','TRANSITION','reviewed count','EQ','190',transition.filter((row) => row.cohort === 'REVIEWED_190').length,PATHS.transition);
  pass('COVR2DVAL-006','TRANSITION','source-only count','EQ','117',transition.filter((row) => row.cohort === 'SOURCE_ONLY_117').length,PATHS.transition);
  pass('COVR2DVAL-007','TRANSITION','reviewed exact after mapping mismatch','EQ','0',reviewMismatch,PATHS.transition);
  pass('COVR2DVAL-008','TRANSITION','source-only exact source identity mismatch','EQ','0',sourceOnlyMismatch,PATHS.transition);
  pass('COVR2DVAL-009','FIELD','rows','EQ','2763',fields.length,PATHS.fields);
  pass('COVR2DVAL-010','FIELD','composite unique','EQ','2763',unique(fields.map((row) => `${row.company_id}\u001f${row.field_name}`)),PATHS.fields);
  pass('COVR2DVAL-011','FIELD','nine fields per company','EQ','307',unique(fields.map((row) => row.company_id)),PATHS.fields);
  pass('COVR2DVAL-012','GAP','rows','EQ','1914',gaps.length,PATHS.gaps);
  pass('COVR2DVAL-013','GAP','composite unique','EQ','1914',unique(gaps.map((row) => `${row.gap_source_table}\u001f${row.gap_source_row_id}\u001f${row.gap_source_field}`)),PATHS.gaps);
  pass('COVR2DVAL-014','CONTROL','rows','EQ','78',controls.length,PATHS.controls);
  pass('COVR2DVAL-015','CONTROL','required fields missing','EQ','0',missingControls.length,PATHS.controls);
  pass('COVR2DVAL-016','SCHEMA','rows and unique','EQ','11/11',`${schema.length}/${unique(schema.map((row) => row.schema_id))}`,PATHS.schema);
  pass('COVR2DVAL-017','CHECKS','rows and unique','EQ','54/54',`${checks.length}/${unique(checks.map((row) => row.check_id))}`,PATHS.checks);
  pass('COVR2DVAL-018','STAGE_SET','rows and paths unique','EQ','11/11',`${stageSet.length}/${unique(stagePaths)}`,PATHS.stageSet);
  pass('COVR2DVAL-019','STAGE_SET','order sequence','EQ','1..11',stageSet.map((row) => row.generation_order).join(','),'Stage1 exact set');
  rows[rows.length - 1].status = rows[rows.length - 1].actual === '1,2,3,4,5,6,7,8,9,10,11' ? 'PASS' : 'FAIL';
  rows[rows.length - 1].expected = '1,2,3,4,5,6,7,8,9,10,11';
  pass('COVR2DVAL-020','PACKAGE_SET','rows and paths unique','EQ','12/12',`${packageSet.length}/${unique(packagePaths)}`,PATHS.packageSet);
  pass('COVR2DVAL-021','PACKAGE_SET','orders1-11 exist before validation','EQ','11',packageExistingBeforeSelf,PATHS.packageSet);
  pass('COVR2DVAL-022','MANIFEST','current data rows before append','EQ','819',manifestRows.length,MANIFEST);
  pass('COVR2DVAL-023','MANIFEST','prefix bytes','EQ','277745',prefix.length,MANIFEST);
  pass('COVR2DVAL-024','MANIFEST','prefix sha256','EQ','ae5c6fb5c5ce00f5021244af0ca86ccec1749af185269cd2e09c59348809bf00',sha(prefix),MANIFEST);
  pass('COVR2DVAL-025','MANIFEST','repair001 run rows','EQ','9',manifestRows.filter((row) => row.run_id === 'RUN-ANA-ROBOT-COVERAGE-FIRST-DESIGN-REPAIR-001').length,MANIFEST);
  pass('COVR2DVAL-026','RESERVED','Stage1 manifest rows','EQ','0',reservedRows,MANIFEST);
  pass('COVR2DVAL-027','RESERVED','Stage1 paths existing','EQ','0',existingStagePaths,'Stage1 exact set');
  pass('COVR2DVAL-028','BOUNDARY','formal pool effects','EQ','1',unique(transition.map((row) => row.formal_pool_effect)),PATHS.transition);
  pass('COVR2DVAL-029','BOUNDARY','formal pool literal','EQ','NO_AUTOMATIC_FORMAL_POOL_CHANGE',transition[0].formal_pool_effect,PATHS.transition);
  pass('COVR2DVAL-030','BOUNDARY','strength cap literal','EQ','PENDING_VERIFICATION',transition[0].evidence_strength_cap,PATHS.transition);
  pass('COVR2DVAL-031','DOCS','baseline formal sections','EQ','6','6','baseline append-stable entries');
  pass('COVR2DVAL-032','FINAL','ordinary fail count','EQ','0',rows.filter((row) => row.status === 'FAIL').length,'this validation');
  add('COVR2DVAL-033','SELF_REFERENCE','validation final identity','EQ','INFO/SELF_REFERENCE_EXCLUDED','INFO/SELF_REFERENCE_EXCLUDED','INFO',PATHS.validation);
  return rows;
}
 
if (!process.argv.includes('--execute')) {
  process.stdout.write('Use --execute to generate baseline/request/validation\n');
  process.exit(0);
}
 
const baselineRows = buildBaseline();
const baselineText = csv(['baseline_id','authority_type','artifact_path','rows','bytes','sha256','scope_count','identity_policy','required_state','checked_at'], baselineRows);
fs.writeFileSync(PATHS.baseline, baselineText, 'utf8');
fs.writeFileSync(PATHS.request, buildRequest(baselineText), 'utf8');
const validationRows = buildValidation();
const validationText = csv(['validation_id','check_group','check_name','comparator','expected','actual','status','evidence_ref'], validationRows);
fs.writeFileSync(PATHS.validation, validationText, 'utf8');
process.stdout.write(JSON.stringify({ baseline: identity(PATHS.baseline), request: identity(PATHS.request), validation: identity(PATHS.validation), status: validationRows.reduce((acc, row) => ({ ...acc, [row.status]: (acc[row.status] ?? 0) + 1 }), {}) }) + '\n');