@tiangong-lca/cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +368 -0
  2. package/bin/tiangong.d.ts +1 -0
  3. package/bin/tiangong.js +32 -0
  4. package/dist/scripts/assert-full-coverage.js +77 -0
  5. package/dist/scripts/assert-full-coverage.js.map +1 -0
  6. package/dist/src/cli.js +2740 -0
  7. package/dist/src/cli.js.map +1 -0
  8. package/dist/src/lib/artifacts.js +86 -0
  9. package/dist/src/lib/artifacts.js.map +1 -0
  10. package/dist/src/lib/dotenv.js +36 -0
  11. package/dist/src/lib/dotenv.js.map +1 -0
  12. package/dist/src/lib/env.js +139 -0
  13. package/dist/src/lib/env.js.map +1 -0
  14. package/dist/src/lib/errors.js +38 -0
  15. package/dist/src/lib/errors.js.map +1 -0
  16. package/dist/src/lib/flow-build-alias-map.js +299 -0
  17. package/dist/src/lib/flow-build-alias-map.js.map +1 -0
  18. package/dist/src/lib/flow-get.js +81 -0
  19. package/dist/src/lib/flow-get.js.map +1 -0
  20. package/dist/src/lib/flow-governance.js +164 -0
  21. package/dist/src/lib/flow-governance.js.map +1 -0
  22. package/dist/src/lib/flow-list.js +187 -0
  23. package/dist/src/lib/flow-list.js.map +1 -0
  24. package/dist/src/lib/flow-publish-reviewed-data.js +808 -0
  25. package/dist/src/lib/flow-publish-reviewed-data.js.map +1 -0
  26. package/dist/src/lib/flow-publish-version.js +541 -0
  27. package/dist/src/lib/flow-publish-version.js.map +1 -0
  28. package/dist/src/lib/flow-read.js +310 -0
  29. package/dist/src/lib/flow-read.js.map +1 -0
  30. package/dist/src/lib/flow-regen-product.js +1396 -0
  31. package/dist/src/lib/flow-regen-product.js.map +1 -0
  32. package/dist/src/lib/flow-remediate.js +821 -0
  33. package/dist/src/lib/flow-remediate.js.map +1 -0
  34. package/dist/src/lib/http.js +45 -0
  35. package/dist/src/lib/http.js.map +1 -0
  36. package/dist/src/lib/io.js +30 -0
  37. package/dist/src/lib/io.js.map +1 -0
  38. package/dist/src/lib/kb-search.js +83 -0
  39. package/dist/src/lib/kb-search.js.map +1 -0
  40. package/dist/src/lib/lifecyclemodel-auto-build.js +1328 -0
  41. package/dist/src/lib/lifecyclemodel-auto-build.js.map +1 -0
  42. package/dist/src/lib/lifecyclemodel-orchestrate.js +1556 -0
  43. package/dist/src/lib/lifecyclemodel-orchestrate.js.map +1 -0
  44. package/dist/src/lib/lifecyclemodel-publish-build.js +296 -0
  45. package/dist/src/lib/lifecyclemodel-publish-build.js.map +1 -0
  46. package/dist/src/lib/lifecyclemodel-publish-resulting-process.js +101 -0
  47. package/dist/src/lib/lifecyclemodel-publish-resulting-process.js.map +1 -0
  48. package/dist/src/lib/lifecyclemodel-resulting-process.js +1152 -0
  49. package/dist/src/lib/lifecyclemodel-resulting-process.js.map +1 -0
  50. package/dist/src/lib/lifecyclemodel-validate-build.js +238 -0
  51. package/dist/src/lib/lifecyclemodel-validate-build.js.map +1 -0
  52. package/dist/src/lib/llm.js +383 -0
  53. package/dist/src/lib/llm.js.map +1 -0
  54. package/dist/src/lib/process-auto-build.js +717 -0
  55. package/dist/src/lib/process-auto-build.js.map +1 -0
  56. package/dist/src/lib/process-batch-build.js +409 -0
  57. package/dist/src/lib/process-batch-build.js.map +1 -0
  58. package/dist/src/lib/process-get.js +66 -0
  59. package/dist/src/lib/process-get.js.map +1 -0
  60. package/dist/src/lib/process-publish-build.js +451 -0
  61. package/dist/src/lib/process-publish-build.js.map +1 -0
  62. package/dist/src/lib/process-resume-build.js +364 -0
  63. package/dist/src/lib/process-resume-build.js.map +1 -0
  64. package/dist/src/lib/publish.js +562 -0
  65. package/dist/src/lib/publish.js.map +1 -0
  66. package/dist/src/lib/remote.js +88 -0
  67. package/dist/src/lib/remote.js.map +1 -0
  68. package/dist/src/lib/review-flow.js +1110 -0
  69. package/dist/src/lib/review-flow.js.map +1 -0
  70. package/dist/src/lib/review-lifecyclemodel.js +727 -0
  71. package/dist/src/lib/review-lifecyclemodel.js.map +1 -0
  72. package/dist/src/lib/review-process.js +702 -0
  73. package/dist/src/lib/review-process.js.map +1 -0
  74. package/dist/src/lib/run.js +106 -0
  75. package/dist/src/lib/run.js.map +1 -0
  76. package/dist/src/lib/state-lock.js +150 -0
  77. package/dist/src/lib/state-lock.js.map +1 -0
  78. package/dist/src/lib/supabase-client.js +269 -0
  79. package/dist/src/lib/supabase-client.js.map +1 -0
  80. package/dist/src/lib/supabase-json-ordered-write.js +186 -0
  81. package/dist/src/lib/supabase-json-ordered-write.js.map +1 -0
  82. package/dist/src/lib/supabase-rest.js +138 -0
  83. package/dist/src/lib/supabase-rest.js.map +1 -0
  84. package/dist/src/lib/supabase-session.js +424 -0
  85. package/dist/src/lib/supabase-session.js.map +1 -0
  86. package/dist/src/lib/tidas-sdk-package-validator.js +493 -0
  87. package/dist/src/lib/tidas-sdk-package-validator.js.map +1 -0
  88. package/dist/src/lib/unstructured.js +203 -0
  89. package/dist/src/lib/unstructured.js.map +1 -0
  90. package/dist/src/lib/user-api-key.js +75 -0
  91. package/dist/src/lib/user-api-key.js.map +1 -0
  92. package/dist/src/lib/validation.js +191 -0
  93. package/dist/src/lib/validation.js.map +1 -0
  94. package/dist/src/main.js +38 -0
  95. package/dist/src/main.js.map +1 -0
  96. package/package.json +81 -0
@@ -0,0 +1,821 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import path from 'node:path';
4
+ import { writeJsonArtifact, writeJsonLinesArtifact, writeTextArtifact } from './artifacts.js';
5
+ import { CliError } from './errors.js';
6
+ import { coerceText, deepGet, isRecord, loadRowsFromFile, normalizeText, } from './flow-governance.js';
7
+ const CONTACT_UUID = 'f4b4c314-8c4c-4c83-968f-5b3c7724f6a8';
8
+ const CONTACT_VERSION = '01.00.000';
9
+ const COMPLIANCE_SOURCE_UUID = 'd92a1a12-2545-49e2-a585-55c259997756';
10
+ const COMPLIANCE_SOURCE_VERSION = '20.20.002';
11
+ const ILCD_FORMAT_SOURCE_UUID = 'a97a0155-0234-4b87-b4ce-a45da52f2a40';
12
+ const ILCD_FORMAT_SOURCE_VERSION = '03.00.003';
13
+ const DEFAULT_MASS_FLOW_PROPERTY_UUID = '93a60a56-a3c8-11da-a746-0800200b9a66';
14
+ const DEFAULT_MASS_FLOW_PROPERTY_VERSION = '03.00.003';
15
+ const DEFAULT_MASS_FLOW_PROPERTY_NAME = 'Mass';
16
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
17
+ const VERSION_QUERY_RE = /([?&]version=)(\d+\.\d+\.\d+)/u;
18
+ const LOCAL_URI_VERSION_RE = /_(\d+\.\d+\.\d+)\.xml$/u;
19
+ const LEGACY_OUTPUT_PREFIX = 'flows_tidas_sdk_plus_classification';
20
+ const GENERIC_MASS_HINTS = new Set([
21
+ 'flow property for kg',
22
+ 'flow property for kilogram',
23
+ 'flow property for kilograms',
24
+ 'kg',
25
+ 'kilogram',
26
+ 'kilograms',
27
+ 'mass',
28
+ 'mass flow',
29
+ 'mass flow kg',
30
+ ]);
31
+ function build_sdk_candidates() {
32
+ return ['@tiangong-lca/tidas-sdk/core'];
33
+ }
34
+ function resolve_sdk_module_from_candidates(requireFn, candidates) {
35
+ const details = [];
36
+ for (const candidate of candidates) {
37
+ try {
38
+ const loaded = requireFn(candidate);
39
+ if (typeof loaded.createFlow === 'function') {
40
+ return {
41
+ ...loaded,
42
+ location: candidate,
43
+ };
44
+ }
45
+ details.push(`Candidate missing createFlow export: ${candidate}`);
46
+ }
47
+ catch (error) {
48
+ details.push(`Failed to load ${candidate}: ${String(error)}`);
49
+ }
50
+ }
51
+ throw new CliError('Unable to resolve the local tidas-sdk flow factory.', {
52
+ code: 'FLOW_REMEDIATE_SDK_NOT_FOUND',
53
+ exitCode: 2,
54
+ details,
55
+ });
56
+ }
57
+ function resolve_local_sdk_module() {
58
+ return resolve_sdk_module_from_candidates(createRequire(import.meta.url), build_sdk_candidates());
59
+ }
60
+ function assert_input_file(inputFile) {
61
+ if (!inputFile) {
62
+ throw new CliError('Missing required --input-file value.', {
63
+ code: 'FLOW_REMEDIATE_INPUT_REQUIRED',
64
+ exitCode: 2,
65
+ });
66
+ }
67
+ const resolved = path.resolve(inputFile);
68
+ if (!existsSync(resolved)) {
69
+ throw new CliError(`Flow remediation input file not found: ${resolved}`, {
70
+ code: 'FLOW_REMEDIATE_INPUT_NOT_FOUND',
71
+ exitCode: 2,
72
+ });
73
+ }
74
+ return resolved;
75
+ }
76
+ function assert_out_dir(outDir) {
77
+ if (!outDir) {
78
+ throw new CliError('Missing required --out-dir value.', {
79
+ code: 'FLOW_REMEDIATE_OUT_DIR_REQUIRED',
80
+ exitCode: 2,
81
+ });
82
+ }
83
+ return path.resolve(outDir);
84
+ }
85
+ function build_output_files(outDir) {
86
+ return {
87
+ allRemediated: path.join(outDir, `${LEGACY_OUTPUT_PREFIX}_remediated_all.jsonl`),
88
+ readyForMcp: path.join(outDir, `${LEGACY_OUTPUT_PREFIX}_remediated_ready_for_mcp.jsonl`),
89
+ residualManualQueue: path.join(outDir, `${LEGACY_OUTPUT_PREFIX}_residual_manual_queue.jsonl`),
90
+ audit: path.join(outDir, `${LEGACY_OUTPUT_PREFIX}_remediation_audit.jsonl`),
91
+ report: path.join(outDir, `${LEGACY_OUTPUT_PREFIX}_remediation_report.json`),
92
+ prompt: path.join(outDir, `${LEGACY_OUTPUT_PREFIX}_residual_manual_queue_prompt.md`),
93
+ };
94
+ }
95
+ function build_local_dataset_uri(datasetKind, uuidValue, version) {
96
+ if (!uuidValue) {
97
+ return '';
98
+ }
99
+ const folderMap = {
100
+ process: 'processes',
101
+ 'process data set': 'processes',
102
+ flow: 'flows',
103
+ 'flow data set': 'flows',
104
+ source: 'sources',
105
+ 'source data set': 'sources',
106
+ contact: 'contacts',
107
+ 'contact data set': 'contacts',
108
+ 'flow property data set': 'flowproperties',
109
+ };
110
+ const kind = datasetKind.trim().toLowerCase();
111
+ const folder = folderMap[kind] ?? 'datasets';
112
+ const versionText = version.trim() || '01.01.000';
113
+ return `../${folder}/${uuidValue}_${versionText}.xml`;
114
+ }
115
+ function build_dataset_format_reference() {
116
+ return {
117
+ '@refObjectId': ILCD_FORMAT_SOURCE_UUID,
118
+ '@type': 'source data set',
119
+ '@uri': build_local_dataset_uri('source', ILCD_FORMAT_SOURCE_UUID, ILCD_FORMAT_SOURCE_VERSION),
120
+ '@version': ILCD_FORMAT_SOURCE_VERSION,
121
+ 'common:shortDescription': [{ '@xml:lang': 'en', '#text': 'ILCD format' }],
122
+ };
123
+ }
124
+ function normalize_multilang_entries(value) {
125
+ if (typeof value === 'string') {
126
+ const text = value.trim();
127
+ return text ? [{ '@xml:lang': 'en', '#text': text }] : null;
128
+ }
129
+ if (Array.isArray(value)) {
130
+ const entries = value.flatMap((item) => normalize_multilang_entries(item) ?? []);
131
+ return entries.length ? entries : null;
132
+ }
133
+ if (!isRecord(value)) {
134
+ return null;
135
+ }
136
+ const text = coerceText(value['#text']);
137
+ if (!text) {
138
+ return null;
139
+ }
140
+ return [
141
+ {
142
+ '@xml:lang': coerceText(value['@xml:lang']) || 'en',
143
+ '#text': text,
144
+ },
145
+ ];
146
+ }
147
+ function first_text(value, fallback = '') {
148
+ const normalized = normalize_multilang_entries(value);
149
+ return normalized?.[0]?.['#text'] ? String(normalized[0]['#text']) : fallback;
150
+ }
151
+ function normalize_multilang_field(container, key, fixes) {
152
+ if (!(key in container)) {
153
+ return;
154
+ }
155
+ const normalized = normalize_multilang_entries(container[key]);
156
+ if (!normalized) {
157
+ delete container[key];
158
+ fixes.push(`remove_empty_multilang:${key}`);
159
+ return;
160
+ }
161
+ if (JSON.stringify(container[key]) !== JSON.stringify(normalized)) {
162
+ container[key] = normalized;
163
+ fixes.push(`normalize_multilang:${key}`);
164
+ }
165
+ }
166
+ function normalize_name_block(dataSetInfo, fixes) {
167
+ const nameBlock = isRecord(dataSetInfo.name) ? dataSetInfo.name : null;
168
+ if (!nameBlock) {
169
+ return;
170
+ }
171
+ for (const key of [
172
+ 'baseName',
173
+ 'flowProperties',
174
+ 'mixAndLocationTypes',
175
+ 'treatmentStandardsRoutes',
176
+ ]) {
177
+ if (!(key in nameBlock)) {
178
+ continue;
179
+ }
180
+ const normalized = normalize_multilang_entries(nameBlock[key]);
181
+ if (!normalized) {
182
+ delete nameBlock[key];
183
+ fixes.push(`remove_empty_name_field:${key}`);
184
+ continue;
185
+ }
186
+ if (JSON.stringify(nameBlock[key]) !== JSON.stringify(normalized)) {
187
+ nameBlock[key] = normalized;
188
+ fixes.push(`normalize_name_field:${key}`);
189
+ }
190
+ }
191
+ }
192
+ function bump_ilcd_version(version) {
193
+ const parts = version.split('.');
194
+ const numbers = [0, 0, 0].map((_, index) => {
195
+ const raw = parts[index];
196
+ const parsed = raw ? Number.parseInt(raw, 10) : 0;
197
+ return Number.isInteger(parsed) ? parsed : 0;
198
+ });
199
+ numbers[2] += 1;
200
+ return `${numbers[0].toString().padStart(2, '0')}.${numbers[1].toString().padStart(2, '0')}.${numbers[2]
201
+ .toString()
202
+ .padStart(3, '0')}`;
203
+ }
204
+ function version_from_uri(uri) {
205
+ const text = uri.trim();
206
+ if (!text) {
207
+ return '';
208
+ }
209
+ const localMatch = text.match(LOCAL_URI_VERSION_RE);
210
+ if (localMatch) {
211
+ return localMatch[1];
212
+ }
213
+ const queryMatch = text.match(VERSION_QUERY_RE);
214
+ return queryMatch?.[2] ?? '';
215
+ }
216
+ function normalize_reference_block(ref, defaultType, shortDescriptionFallback) {
217
+ if (!isRecord(ref)) {
218
+ return null;
219
+ }
220
+ const refObjectId = coerceText(ref['@refObjectId']).toLowerCase();
221
+ if (!refObjectId) {
222
+ return null;
223
+ }
224
+ const refType = coerceText(ref['@type']) || defaultType;
225
+ const version = coerceText(ref['@version']) ||
226
+ version_from_uri(coerceText(ref['@uri'])) ||
227
+ (refObjectId === ILCD_FORMAT_SOURCE_UUID
228
+ ? ILCD_FORMAT_SOURCE_VERSION
229
+ : refObjectId === COMPLIANCE_SOURCE_UUID
230
+ ? COMPLIANCE_SOURCE_VERSION
231
+ : '');
232
+ const shortDescription = normalize_multilang_entries(ref['common:shortDescription']) ??
233
+ (shortDescriptionFallback ? [{ '@xml:lang': 'en', '#text': shortDescriptionFallback }] : null);
234
+ const result = {
235
+ '@type': refType,
236
+ '@refObjectId': refObjectId,
237
+ '@uri': version
238
+ ? build_local_dataset_uri(refType, refObjectId, version)
239
+ : coerceText(ref['@uri']),
240
+ };
241
+ if (version) {
242
+ result['@version'] = version;
243
+ }
244
+ if (shortDescription) {
245
+ result['common:shortDescription'] = shortDescription;
246
+ }
247
+ return result;
248
+ }
249
+ function canonical_contact_reference() {
250
+ return {
251
+ '@type': 'contact data set',
252
+ '@refObjectId': CONTACT_UUID,
253
+ '@uri': build_local_dataset_uri('contact data set', CONTACT_UUID, CONTACT_VERSION),
254
+ '@version': CONTACT_VERSION,
255
+ 'common:shortDescription': [
256
+ { '@xml:lang': 'en', '#text': 'Tiangong LCA Data Working Group' },
257
+ { '@xml:lang': 'zh', '#text': '天工LCA数据团队' },
258
+ ],
259
+ };
260
+ }
261
+ function canonical_compliance_block() {
262
+ return {
263
+ 'common:referenceToComplianceSystem': {
264
+ '@refObjectId': COMPLIANCE_SOURCE_UUID,
265
+ '@type': 'source data set',
266
+ '@uri': build_local_dataset_uri('source data set', COMPLIANCE_SOURCE_UUID, COMPLIANCE_SOURCE_VERSION),
267
+ '@version': COMPLIANCE_SOURCE_VERSION,
268
+ 'common:shortDescription': [
269
+ { '@xml:lang': 'en', '#text': 'ILCD Data Network - Entry-level' },
270
+ ],
271
+ },
272
+ 'common:approvalOfOverallCompliance': 'Fully compliant',
273
+ };
274
+ }
275
+ function normalize_hint_text(value) {
276
+ return normalizeText(first_text(value));
277
+ }
278
+ function infer_flow_property_descriptor_from_hint(value) {
279
+ const normalized = normalize_hint_text(value);
280
+ if (!normalized) {
281
+ return null;
282
+ }
283
+ if (GENERIC_MASS_HINTS.has(normalized) ||
284
+ (normalized.startsWith('flow property for ') && /\b(kg|kilogram|kilograms)\b/u.test(normalized))) {
285
+ return {
286
+ uuid: DEFAULT_MASS_FLOW_PROPERTY_UUID,
287
+ name: DEFAULT_MASS_FLOW_PROPERTY_NAME,
288
+ version: DEFAULT_MASS_FLOW_PROPERTY_VERSION,
289
+ };
290
+ }
291
+ return null;
292
+ }
293
+ function build_flow_property_item(descriptor, internalId, meanValue) {
294
+ return {
295
+ '@dataSetInternalID': internalId,
296
+ meanValue,
297
+ referenceToFlowPropertyDataSet: {
298
+ '@type': 'flow property data set',
299
+ '@refObjectId': descriptor.uuid,
300
+ '@uri': build_local_dataset_uri('flow property data set', descriptor.uuid, descriptor.version),
301
+ '@version': descriptor.version,
302
+ 'common:shortDescription': [{ '@xml:lang': 'en', '#text': descriptor.name }],
303
+ },
304
+ };
305
+ }
306
+ function normalize_flow_properties(dataset, fixes) {
307
+ const flowPropertiesBlock = isRecord(dataset.flowProperties) ? dataset.flowProperties : {};
308
+ dataset.flowProperties = flowPropertiesBlock;
309
+ const rawItems = flowPropertiesBlock.flowProperty;
310
+ const items = Array.isArray(rawItems) ? rawItems : rawItems ? [rawItems] : [];
311
+ const normalizedItems = [];
312
+ const unresolved = [];
313
+ let repairedUnknownUuid = false;
314
+ let filledMissingUuid = false;
315
+ let usedDefaultMassFallback = false;
316
+ for (let index = 0; index < items.length; index += 1) {
317
+ const item = items[index];
318
+ if (!isRecord(item)) {
319
+ unresolved.push({
320
+ validator: 'tidas_sdk',
321
+ path: 'flowDataSet.flowProperties.flowProperty',
322
+ message: 'flowProperty item is not an object',
323
+ code: 'unsupported_item_type',
324
+ });
325
+ continue;
326
+ }
327
+ const reference = isRecord(item.referenceToFlowPropertyDataSet)
328
+ ? item.referenceToFlowPropertyDataSet
329
+ : null;
330
+ const hint = reference?.['common:shortDescription'] ?? reference;
331
+ const internalId = coerceText(item['@dataSetInternalID']) || String(index);
332
+ const meanValue = coerceText(item.meanValue) || '1.0';
333
+ const refUuid = coerceText(reference?.['@refObjectId']).toLowerCase();
334
+ const refVersion = coerceText(reference?.['@version']) || version_from_uri(coerceText(reference?.['@uri']));
335
+ let descriptor;
336
+ if (refUuid && UUID_RE.test(refUuid)) {
337
+ descriptor = {
338
+ uuid: refUuid,
339
+ name: first_text(hint, DEFAULT_MASS_FLOW_PROPERTY_NAME),
340
+ version: refVersion ||
341
+ (refUuid === DEFAULT_MASS_FLOW_PROPERTY_UUID
342
+ ? DEFAULT_MASS_FLOW_PROPERTY_VERSION
343
+ : '01.01.000'),
344
+ };
345
+ }
346
+ else {
347
+ descriptor = infer_flow_property_descriptor_from_hint(hint);
348
+ if (descriptor) {
349
+ if (refUuid) {
350
+ repairedUnknownUuid = true;
351
+ }
352
+ else {
353
+ filledMissingUuid = true;
354
+ }
355
+ }
356
+ }
357
+ if (!descriptor) {
358
+ unresolved.push({
359
+ validator: 'tidas_sdk',
360
+ path: 'flowDataSet.flowProperties.flowProperty.referenceToFlowPropertyDataSet.@refObjectId',
361
+ message: refUuid
362
+ ? `Unknown flow property UUID: ${refUuid}`
363
+ : 'Unable to infer flow property UUID from the reference block',
364
+ code: 'unknown_flow_property_uuid',
365
+ });
366
+ continue;
367
+ }
368
+ normalizedItems.push(build_flow_property_item(descriptor, internalId, meanValue));
369
+ }
370
+ if (!normalizedItems.length) {
371
+ if (!items.length) {
372
+ normalizedItems.push(build_flow_property_item({
373
+ uuid: DEFAULT_MASS_FLOW_PROPERTY_UUID,
374
+ name: DEFAULT_MASS_FLOW_PROPERTY_NAME,
375
+ version: DEFAULT_MASS_FLOW_PROPERTY_VERSION,
376
+ }, '0', '1.0'));
377
+ usedDefaultMassFallback = true;
378
+ }
379
+ else {
380
+ delete flowPropertiesBlock.flowProperty;
381
+ return { items: [], unresolved };
382
+ }
383
+ }
384
+ flowPropertiesBlock.flowProperty =
385
+ normalizedItems.length === 1 ? normalizedItems[0] : normalizedItems;
386
+ fixes.push('normalize_flow_properties');
387
+ if (repairedUnknownUuid) {
388
+ fixes.push('repair_unknown_flow_property_uuid_from_short_description');
389
+ }
390
+ if (filledMissingUuid) {
391
+ fixes.push('fill_missing_flow_property_uuid_from_short_description');
392
+ }
393
+ if (usedDefaultMassFallback) {
394
+ fixes.push('set_default_mass_flow_property');
395
+ }
396
+ return { items: normalizedItems, unresolved };
397
+ }
398
+ function normalize_quantitative_reference(dataset, normalizedFlowProperties, fixes, unresolved) {
399
+ const flowInformation = isRecord(dataset.flowInformation) ? dataset.flowInformation : {};
400
+ dataset.flowInformation = flowInformation;
401
+ const quantitativeReference = isRecord(flowInformation.quantitativeReference)
402
+ ? flowInformation.quantitativeReference
403
+ : {};
404
+ flowInformation.quantitativeReference = quantitativeReference;
405
+ if (!normalizedFlowProperties.length) {
406
+ unresolved.push({
407
+ validator: 'tidas_sdk',
408
+ path: 'flowDataSet.flowInformation.quantitativeReference.referenceToReferenceFlowProperty',
409
+ message: 'Cannot infer referenceToReferenceFlowProperty without any valid flowProperty entries',
410
+ code: 'missing_flow_properties',
411
+ });
412
+ return;
413
+ }
414
+ const allowedIds = new Set(normalizedFlowProperties
415
+ .map((item) => coerceText(item['@dataSetInternalID']))
416
+ .filter((value) => value.length > 0));
417
+ const current = coerceText(quantitativeReference.referenceToReferenceFlowProperty);
418
+ if (!allowedIds.has(current)) {
419
+ quantitativeReference.referenceToReferenceFlowProperty = coerceText(normalizedFlowProperties[0]?.['@dataSetInternalID']);
420
+ fixes.push('set_reference_to_reference_flow_property');
421
+ }
422
+ }
423
+ function normalize_technology_multilang_fields(dataset, fixes) {
424
+ const technology = deepGet(dataset, ['flowInformation', 'technology']);
425
+ if (!isRecord(technology)) {
426
+ return;
427
+ }
428
+ normalize_multilang_field(technology, 'technologicalApplicability', fixes);
429
+ }
430
+ function normalize_technical_specification(dataset, fixes) {
431
+ const technology = deepGet(dataset, ['flowInformation', 'technology']);
432
+ if (!isRecord(technology) || !('referenceToTechnicalSpecification' in technology)) {
433
+ return;
434
+ }
435
+ const normalized = normalize_reference_block(technology.referenceToTechnicalSpecification, 'source data set', first_text(technology.referenceToTechnicalSpecification));
436
+ if (!normalized) {
437
+ delete technology.referenceToTechnicalSpecification;
438
+ fixes.push('remove_invalid_reference_to_technical_specification');
439
+ return;
440
+ }
441
+ if (JSON.stringify(technology.referenceToTechnicalSpecification) !== JSON.stringify(normalized)) {
442
+ technology.referenceToTechnicalSpecification = normalized;
443
+ fixes.push('normalize_reference_to_technical_specification');
444
+ }
445
+ }
446
+ function update_permanent_dataset_uri(uriValue, targetVersion) {
447
+ const uri = coerceText(uriValue);
448
+ if (!uri) {
449
+ return uriValue;
450
+ }
451
+ if (VERSION_QUERY_RE.test(uri)) {
452
+ return uri.replace(VERSION_QUERY_RE, `$1${targetVersion}`);
453
+ }
454
+ return uriValue;
455
+ }
456
+ function parse_validation_error(error) {
457
+ if (isRecord(error) && Array.isArray(error.issues)) {
458
+ const issues = error.issues.filter(isRecord).map((issue) => {
459
+ const pathValue = Array.isArray(issue.path)
460
+ ? issue.path.map((part) => String(part)).join('.')
461
+ : '<exception>';
462
+ return {
463
+ validator: 'tidas_sdk',
464
+ path: pathValue || '<exception>',
465
+ message: coerceText(issue.message) || String(error),
466
+ code: coerceText(issue.code) || 'validation_error',
467
+ };
468
+ });
469
+ if (issues.length) {
470
+ return issues;
471
+ }
472
+ }
473
+ return [
474
+ {
475
+ validator: 'tidas_sdk',
476
+ path: '<exception>',
477
+ message: String(error),
478
+ code: 'exception',
479
+ },
480
+ ];
481
+ }
482
+ function validate_flow_payload(payload, deps) {
483
+ const sdkModule = (deps.loadSdkModule ?? resolve_local_sdk_module)();
484
+ if (typeof sdkModule.createFlow !== 'function') {
485
+ throw new CliError('Resolved tidas-sdk core module does not expose createFlow.', {
486
+ code: 'FLOW_REMEDIATE_SDK_INVALID',
487
+ exitCode: 2,
488
+ details: sdkModule.location ?? null,
489
+ });
490
+ }
491
+ try {
492
+ const entity = sdkModule.createFlow(payload, {
493
+ mode: 'strict',
494
+ throwOnError: false,
495
+ deepValidation: true,
496
+ });
497
+ if (!entity || typeof entity.validate !== 'function') {
498
+ throw new CliError('Resolved tidas-sdk flow entity does not expose validate().', {
499
+ code: 'FLOW_REMEDIATE_SDK_INVALID',
500
+ exitCode: 2,
501
+ details: sdkModule.location ?? null,
502
+ });
503
+ }
504
+ const result = entity.validate();
505
+ if (isRecord(result) && result.success === true) {
506
+ return { success: true };
507
+ }
508
+ return {
509
+ success: false,
510
+ issues: parse_validation_error(isRecord(result) ? result.error : result),
511
+ };
512
+ }
513
+ catch (error) {
514
+ if (error instanceof CliError) {
515
+ throw error;
516
+ }
517
+ return {
518
+ success: false,
519
+ issues: parse_validation_error(error),
520
+ };
521
+ }
522
+ }
523
+ function remediate_row(row, deps) {
524
+ const working = JSON.parse(JSON.stringify(row));
525
+ const payload = isRecord(working.json_ordered) ? working.json_ordered : null;
526
+ const flowDataSet = isRecord(payload?.flowDataSet) ? payload.flowDataSet : null;
527
+ if (!payload || !flowDataSet) {
528
+ return {
529
+ row: working,
530
+ valid: false,
531
+ appliedFixes: [],
532
+ finalReasons: [
533
+ {
534
+ validator: 'tidas_sdk',
535
+ path: 'json_ordered.flowDataSet',
536
+ message: 'Missing flowDataSet payload',
537
+ code: 'missing_flow_dataset',
538
+ },
539
+ ],
540
+ versionBefore: '',
541
+ versionAfter: '',
542
+ };
543
+ }
544
+ const fixes = [];
545
+ const residualUnresolved = [];
546
+ const flowInformation = isRecord(flowDataSet.flowInformation) ? flowDataSet.flowInformation : {};
547
+ flowDataSet.flowInformation = flowInformation;
548
+ const dataSetInformation = isRecord(flowInformation.dataSetInformation)
549
+ ? flowInformation.dataSetInformation
550
+ : {};
551
+ flowInformation.dataSetInformation = dataSetInformation;
552
+ normalize_name_block(dataSetInformation, fixes);
553
+ for (const key of [
554
+ 'common:synonyms',
555
+ 'common:generalComment',
556
+ 'common:shortDescription',
557
+ 'common:name',
558
+ 'common:shortName',
559
+ ]) {
560
+ normalize_multilang_field(dataSetInformation, key, fixes);
561
+ }
562
+ if (isRecord(dataSetInformation['common:other'])) {
563
+ delete dataSetInformation['common:other'];
564
+ fixes.push('remove_common_other_object');
565
+ }
566
+ if (dataSetInformation.CASNumber === '') {
567
+ delete dataSetInformation.CASNumber;
568
+ fixes.push('remove_empty_cas_number');
569
+ }
570
+ const administrativeInformation = isRecord(flowDataSet.administrativeInformation)
571
+ ? flowDataSet.administrativeInformation
572
+ : {};
573
+ flowDataSet.administrativeInformation = administrativeInformation;
574
+ const dataEntryBy = isRecord(administrativeInformation.dataEntryBy)
575
+ ? administrativeInformation.dataEntryBy
576
+ : {};
577
+ administrativeInformation.dataEntryBy = dataEntryBy;
578
+ const publicationAndOwnership = isRecord(administrativeInformation.publicationAndOwnership)
579
+ ? administrativeInformation.publicationAndOwnership
580
+ : {};
581
+ administrativeInformation.publicationAndOwnership = publicationAndOwnership;
582
+ const formatReference = build_dataset_format_reference();
583
+ if (JSON.stringify(dataEntryBy['common:referenceToDataSetFormat']) !==
584
+ JSON.stringify(formatReference)) {
585
+ dataEntryBy['common:referenceToDataSetFormat'] = formatReference;
586
+ fixes.push('set_reference_to_dataset_format');
587
+ }
588
+ const contactReference = canonical_contact_reference();
589
+ if (JSON.stringify(dataEntryBy['common:referenceToPersonOrEntityEnteringTheData']) !==
590
+ JSON.stringify(contactReference)) {
591
+ dataEntryBy['common:referenceToPersonOrEntityEnteringTheData'] = contactReference;
592
+ fixes.push('set_reference_to_person_or_entity_entering_the_data');
593
+ }
594
+ if (JSON.stringify(publicationAndOwnership['common:referenceToOwnershipOfDataSet']) !==
595
+ JSON.stringify(contactReference)) {
596
+ publicationAndOwnership['common:referenceToOwnershipOfDataSet'] = contactReference;
597
+ fixes.push('set_reference_to_ownership_of_dataset');
598
+ }
599
+ const modellingAndValidation = isRecord(flowDataSet.modellingAndValidation)
600
+ ? flowDataSet.modellingAndValidation
601
+ : {};
602
+ flowDataSet.modellingAndValidation = modellingAndValidation;
603
+ const complianceDeclarations = isRecord(modellingAndValidation.complianceDeclarations)
604
+ ? modellingAndValidation.complianceDeclarations
605
+ : {};
606
+ modellingAndValidation.complianceDeclarations = complianceDeclarations;
607
+ const complianceBlock = canonical_compliance_block();
608
+ if (JSON.stringify(complianceDeclarations.compliance) !== JSON.stringify(complianceBlock)) {
609
+ complianceDeclarations.compliance = complianceBlock;
610
+ fixes.push('set_compliance_block');
611
+ }
612
+ const flowProperties = normalize_flow_properties(flowDataSet, fixes);
613
+ residualUnresolved.push(...flowProperties.unresolved);
614
+ normalize_quantitative_reference(flowDataSet, flowProperties.items, fixes, residualUnresolved);
615
+ normalize_technology_multilang_fields(flowDataSet, fixes);
616
+ normalize_technical_specification(flowDataSet, fixes);
617
+ const flowUuid = coerceText(dataSetInformation['common:UUID']) || coerceText(working.id) || 'unknown-flow';
618
+ if (!coerceText(publicationAndOwnership['common:dataSetVersion'])) {
619
+ publicationAndOwnership['common:dataSetVersion'] = '01.01.000';
620
+ }
621
+ const versionBefore = coerceText(publicationAndOwnership['common:dataSetVersion']);
622
+ let versionAfter = versionBefore;
623
+ const stateCode = Number.parseInt(coerceText(working.state_code) || '0', 10) || 0;
624
+ if (stateCode === 100) {
625
+ versionAfter = bump_ilcd_version(versionBefore);
626
+ if (versionAfter !== versionBefore) {
627
+ publicationAndOwnership['common:dataSetVersion'] = versionAfter;
628
+ fixes.push('bump_dataset_version');
629
+ publicationAndOwnership['common:referenceToPrecedingDataSetVersion'] = {
630
+ '@type': 'flow data set',
631
+ '@refObjectId': flowUuid,
632
+ '@uri': build_local_dataset_uri('flow data set', flowUuid, versionBefore),
633
+ '@version': versionBefore,
634
+ 'common:shortDescription': [
635
+ {
636
+ '@xml:lang': 'en',
637
+ '#text': first_text(deepGet(dataSetInformation, ['name', 'baseName']), flowUuid),
638
+ },
639
+ ],
640
+ };
641
+ fixes.push('set_reference_to_preceding_dataset_version');
642
+ const permanentUri = update_permanent_dataset_uri(publicationAndOwnership['common:permanentDataSetURI'], versionAfter);
643
+ if (permanentUri !== publicationAndOwnership['common:permanentDataSetURI']) {
644
+ publicationAndOwnership['common:permanentDataSetURI'] = permanentUri;
645
+ fixes.push('update_permanent_dataset_uri_version');
646
+ }
647
+ if ('version' in working) {
648
+ working.version = versionAfter;
649
+ }
650
+ }
651
+ }
652
+ else if (isRecord(publicationAndOwnership['common:referenceToPrecedingDataSetVersion']) &&
653
+ !Object.keys(publicationAndOwnership['common:referenceToPrecedingDataSetVersion'])
654
+ .length) {
655
+ delete publicationAndOwnership['common:referenceToPrecedingDataSetVersion'];
656
+ fixes.push('remove_empty_reference_to_preceding_dataset_version');
657
+ }
658
+ const validation = validate_flow_payload(payload, deps);
659
+ const finalReasons = validation.success === true
660
+ ? residualUnresolved
661
+ : [...residualUnresolved, ...validation.issues];
662
+ working.json_ordered = payload;
663
+ working.reason = finalReasons;
664
+ return {
665
+ row: working,
666
+ valid: finalReasons.length === 0,
667
+ appliedFixes: fixes,
668
+ finalReasons,
669
+ versionBefore,
670
+ versionAfter,
671
+ };
672
+ }
673
+ function build_prompt(manualRows, manualFile, promptFile) {
674
+ if (!manualRows.length) {
675
+ return `本轮 deterministic remediation 之后,residual manual queue 为 0。
676
+
677
+ 输入文件:
678
+ \`${manualFile}\`
679
+
680
+ 结果:
681
+ 1. 当前没有需要再交给 OpenClaw 手工修复的 flow。
682
+ 2. 可直接对 ready-for-MCP 文件执行后续批量处理。
683
+ 3. 如果后续换了新的输入批次,再重新生成 residual manual queue prompt。
684
+ `;
685
+ }
686
+ const queueIds = manualRows
687
+ .map((row) => coerceText(row.id))
688
+ .filter((value) => value.length > 0)
689
+ .map((value) => `- \`${value}\``)
690
+ .join('\n');
691
+ const outputFile = path.join(path.dirname(promptFile), `${LEGACY_OUTPUT_PREFIX}_residual_manual_fixed.jsonl`);
692
+ const stillInvalidFile = path.join(path.dirname(promptFile), `${LEGACY_OUTPUT_PREFIX}_residual_manual_still_invalid.jsonl`);
693
+ return `你现在要处理 residual manual queue 中 deterministic remediator 之后仍未通过 tidas-sdk 本地校验的 flow。
694
+
695
+ 输入文件:
696
+ \`${manualFile}\`
697
+
698
+ 输出文件:
699
+ \`${outputFile}\`
700
+
701
+ 执行要求:
702
+ 1. 只修改每条记录里的 json_ordered.flowDataSet,保留外层 envelope key:id、user_id、json_ordered、reason、state_code。
703
+ 2. 保持相同 UUID,不要改 id。
704
+ 3. state_code=0 的 flow 保持当前 common:dataSetVersion 不变。
705
+ 4. state_code=100 的 flow 已经完成 deterministic version bump,并已补 common:referenceToPrecedingDataSetVersion;除非绝对必要,不要再次 bump version。
706
+ 5. 优先补齐合法的 flowProperties.flowProperty 和 flowInformation.quantitativeReference.referenceToReferenceFlowProperty,让数据先通过本地 TIDAS SDK 校验。
707
+ 6. 不要无故重写 classification、命名、comment;如果必须改,改动最小,并在结果里保留必要说明。
708
+ 7. 每条输出仍按一行一个 JSON object 的 JSONL 形式写入。
709
+ 8. 修复成功的行把 reason 置为 [];如果仍无法修复,单独另存并写明残留原因。
710
+
711
+ 当前 residual manual queue 共 ${manualRows.length} 条:
712
+ ${queueIds || '- None'}
713
+
714
+ 建议步骤:
715
+ 1. 逐条读取 reason 和 json_ordered.flowDataSet。
716
+ 2. 先判断缺失的是 flowProperties 本体,还是只有 quantitative reference 丢失。
717
+ 3. 能从现有 classificationInformation、baseName、flow type、name.flowProperties 推断合法 flow property 的,补成最小合法块。
718
+ 4. 每改完一条都本地验证一次 TIDAS SDK。
719
+ 5. 把通过校验的 patched row 写到 \`${outputFile}\`。
720
+
721
+ 如果你还要保留未解决项,另写一个旁路文件:
722
+ \`${stillInvalidFile}\`
723
+ `;
724
+ }
725
+ export async function runFlowRemediate(options, deps = {}) {
726
+ const inputFile = assert_input_file(options.inputFile);
727
+ const outDir = assert_out_dir(options.outDir);
728
+ const rows = loadRowsFromFile(inputFile);
729
+ const files = build_output_files(outDir);
730
+ const now = deps.now ?? (() => new Date());
731
+ const remediatedRows = [];
732
+ const validRows = [];
733
+ const manualRows = [];
734
+ const auditRows = [];
735
+ const appliedFixCounts = {};
736
+ for (const row of rows) {
737
+ const result = remediate_row(row, deps);
738
+ remediatedRows.push(result.row);
739
+ if (result.valid) {
740
+ validRows.push(result.row);
741
+ }
742
+ else {
743
+ manualRows.push(result.row);
744
+ }
745
+ result.appliedFixes.forEach((fix) => {
746
+ appliedFixCounts[fix] = (appliedFixCounts[fix] ?? 0) + 1;
747
+ });
748
+ auditRows.push({
749
+ id: row.id,
750
+ user_id: row.user_id,
751
+ state_code: row.state_code,
752
+ version_before: result.versionBefore,
753
+ version_after: result.versionAfter,
754
+ valid_after_remediation: result.valid,
755
+ applied_fixes: result.appliedFixes,
756
+ original_reason: Array.isArray(row.reason) ? row.reason : [],
757
+ final_reason: result.finalReasons,
758
+ });
759
+ }
760
+ writeJsonLinesArtifact(files.allRemediated, remediatedRows);
761
+ writeJsonLinesArtifact(files.readyForMcp, validRows);
762
+ writeJsonLinesArtifact(files.residualManualQueue, manualRows);
763
+ writeJsonLinesArtifact(files.audit, auditRows);
764
+ writeTextArtifact(files.prompt, build_prompt(manualRows, files.residualManualQueue, files.prompt));
765
+ const report = {
766
+ schema_version: 1,
767
+ generated_at_utc: now().toISOString(),
768
+ status: 'completed_local_flow_remediation',
769
+ input_file: inputFile,
770
+ out_dir: outDir,
771
+ counts: {
772
+ input_rows: rows.length,
773
+ state_code_0_rows: rows.filter((row) => Number.parseInt(coerceText(row.state_code) || '0', 10) === 0).length,
774
+ state_code_100_rows: rows.filter((row) => Number.parseInt(coerceText(row.state_code) || '0', 10) === 100).length,
775
+ remediated_rows: remediatedRows.length,
776
+ ready_for_mcp_rows: validRows.length,
777
+ residual_manual_rows: manualRows.length,
778
+ },
779
+ applied_fix_counts: Object.fromEntries(Object.entries(appliedFixCounts).sort(([left], [right]) => left.localeCompare(right))),
780
+ residual_manual_ids: manualRows
781
+ .map((row) => coerceText(row.id))
782
+ .filter((value) => value.length > 0),
783
+ validation_backend: 'tidas_sdk',
784
+ files: {
785
+ all_remediated: files.allRemediated,
786
+ ready_for_mcp: files.readyForMcp,
787
+ residual_manual_queue: files.residualManualQueue,
788
+ audit: files.audit,
789
+ prompt: files.prompt,
790
+ report: files.report,
791
+ },
792
+ };
793
+ writeJsonArtifact(files.report, report);
794
+ return report;
795
+ }
796
+ export const __testInternals = {
797
+ assert_input_file,
798
+ assert_out_dir,
799
+ build_dataset_format_reference,
800
+ build_flow_property_item,
801
+ build_output_files,
802
+ build_prompt,
803
+ build_local_dataset_uri,
804
+ bump_ilcd_version,
805
+ canonical_compliance_block,
806
+ canonical_contact_reference,
807
+ infer_flow_property_descriptor_from_hint,
808
+ normalize_flow_properties,
809
+ normalize_multilang_entries,
810
+ normalize_multilang_field,
811
+ normalize_name_block,
812
+ normalize_quantitative_reference,
813
+ normalize_reference_block,
814
+ parse_validation_error,
815
+ remediate_row,
816
+ resolve_sdk_module_from_candidates,
817
+ update_permanent_dataset_uri,
818
+ validate_flow_payload,
819
+ version_from_uri,
820
+ };
821
+ //# sourceMappingURL=flow-remediate.js.map