@stalfh233/omc-cli 0.6.12 → 0.6.14
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.
- package/coverage/m1-coverage-manifest-v1.json +2 -0
- package/dist/approval-flow.js +84 -3
- package/dist/batch-call.js +32 -2
- package/dist/bench/call-face-eval.js +2 -2
- package/dist/call.js +35 -2
- package/dist/capability-overview.js +21 -7
- package/dist/cli-contract.js +8 -2
- package/dist/cli.js +313 -72
- package/dist/describe-cache.js +8 -4
- package/dist/discovery.js +4 -0
- package/dist/environment-policy.js +20 -0
- package/dist/field-families.js +62 -4
- package/dist/form-support.js +293 -6
- package/dist/goal-routes.js +137 -22
- package/dist/identity.js +63 -11
- package/dist/lanes/app-contract.js +4 -4
- package/dist/lanes/app.js +20 -2
- package/dist/lanes/dictionary-contract.js +16 -5
- package/dist/lanes/dictionary-coverage.js +1 -0
- package/dist/lanes/dictionary.js +200 -8
- package/dist/lanes/environment-contract.js +18 -6
- package/dist/lanes/environment.js +129 -16
- package/dist/lanes/field-contract.js +3 -2
- package/dist/lanes/field.js +785 -63
- package/dist/lanes/form.js +52 -6
- package/dist/lanes/list-view-contract.js +3 -3
- package/dist/lanes/list-view.js +143 -12
- package/dist/lanes/meta.js +36 -10
- package/dist/lanes/model-contract.js +7 -6
- package/dist/lanes/model.js +154 -10
- package/dist/lanes/organization.js +67 -7
- package/dist/lanes/runtime-data-contract.js +4 -2
- package/dist/lanes/runtime-data.js +395 -41
- package/dist/lanes/workflow-contract.js +2 -2
- package/dist/lanes/workflow.js +381 -11
- package/dist/model-scaffold.js +11 -3
- package/dist/path-projection.js +181 -31
- package/dist/read-shapes.js +31 -11
- package/dist/recipes/b2-registration.js +1 -1
- package/dist/recipes/field.js +16 -3
- package/dist/recipes/form.js +4 -3
- package/dist/recipes/runtime-data.js +58 -8
- package/dist/recipes/workflow.js +86 -1
- package/dist/refusal-guidance.js +18 -6
- package/dist/rollback-spec.js +41 -2
- package/dist/rollback.js +49 -11
- package/dist/script.js +72 -12
- package/dist/skills.js +91 -3
- package/dist/tools.js +113 -1
- package/dist/wire.js +13 -1
- package/dist/write-lock.js +92 -4
- package/dist/write.js +129 -16
- package/package.json +3 -3
- package/skills/omc-business-configuration.md +1 -1
- package/skills/omc-capabilities.md +5 -4
- package/skills/omc-playbook.md +41 -0
- package/skills/omc-refusals.md +58 -6
- package/skills/omc-tool-selection.md +2 -0
- package/skills/omc-write-hazards.md +3 -2
package/dist/path-projection.js
CHANGED
|
@@ -304,6 +304,98 @@ function resultLayerNote(result, path) {
|
|
|
304
304
|
resultLayerHint: `「${head}」在 result 顶层(只是不在 artifact 副本里)。取它请不带 --jq 直接读回执,或改取 artifact 内的键(见 available)`,
|
|
305
305
|
};
|
|
306
306
|
}
|
|
307
|
+
/** Envelope keys of the stdout wrapper — never part of the tool result. */
|
|
308
|
+
const ENVELOPE_HEAD_KEYS = new Set(["ok", "status", "result", "error", "requestId"]);
|
|
309
|
+
/**
|
|
310
|
+
* r29f F1:调用方按信封写 `--jq result.applications[].code` 时首段 pathMiss。
|
|
311
|
+
* 首段恰为信封键时剥掉重试一次;成功则标 `autoStripped`,失败保持原 miss。
|
|
312
|
+
*/
|
|
313
|
+
function stripEnvelopeHead(path) {
|
|
314
|
+
const trimmed = path.replace(/^\$\.?/, "").replace(/^[.\/]+/, "");
|
|
315
|
+
const match = /^([A-Za-z_][\w]*)/.exec(trimmed);
|
|
316
|
+
if (match === null)
|
|
317
|
+
return { path };
|
|
318
|
+
const head = match[1];
|
|
319
|
+
if (!ENVELOPE_HEAD_KEYS.has(head))
|
|
320
|
+
return { path };
|
|
321
|
+
const rest = trimmed.slice(head.length).replace(/^[.\/]+/, "");
|
|
322
|
+
return { path: rest, stripped: head };
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* r29f F5:外置后 `--jq` 在 artifact 正文上求值,回执派生键(*Count / *Omitted /
|
|
326
|
+
* detailArtifactId / from)消失。把回执派生键合进视图:artifact 同名键优先,
|
|
327
|
+
* 回执派生键补位;保留 `from:"artifact"`。
|
|
328
|
+
*/
|
|
329
|
+
function isReceiptDerivedKey(key) {
|
|
330
|
+
// `detailFile` 故意**不在此列**:它是本地文件系统路径(进程内才有效),
|
|
331
|
+
// 合进投影视图会让 `--keys` 把本机路径当"结果键"吐出来(2026-11-05 回归:
|
|
332
|
+
// readpath-roundtrip 1.2 的 keys 断言多了 detailFile)。对外只认 artifactId。
|
|
333
|
+
return /(?:Count|Omitted)$/.test(key) || key === "total" || key === "detailArtifactId" || key === "detailSha256" || key === "from" || key === "truncated" || key === "truncatedKeys" || key === "fetch" || key === "tablesHint";
|
|
334
|
+
}
|
|
335
|
+
function mergeReceiptDerivedKeys(artifactSource, result) {
|
|
336
|
+
if (artifactSource === null || typeof artifactSource !== "object" || Array.isArray(artifactSource))
|
|
337
|
+
return artifactSource;
|
|
338
|
+
const view = { ...artifactSource };
|
|
339
|
+
for (const [key, value] of Object.entries(result)) {
|
|
340
|
+
if (!isReceiptDerivedKey(key))
|
|
341
|
+
continue;
|
|
342
|
+
if (Object.prototype.hasOwnProperty.call(view, key))
|
|
343
|
+
continue;
|
|
344
|
+
view[key] = value;
|
|
345
|
+
}
|
|
346
|
+
return view;
|
|
347
|
+
}
|
|
348
|
+
function projectPathOnSource(source, path) {
|
|
349
|
+
const tryOnce = (candidate) => {
|
|
350
|
+
if (candidate.includes("[]"))
|
|
351
|
+
return { projected: projectJsonPathFanout(source, candidate), fanout: true };
|
|
352
|
+
return { projected: projectJsonPath(source, candidate), fanout: false };
|
|
353
|
+
};
|
|
354
|
+
const first = tryOnce(path);
|
|
355
|
+
if (!first.projected.missed)
|
|
356
|
+
return { ...first };
|
|
357
|
+
const stripped = stripEnvelopeHead(path);
|
|
358
|
+
// r30a F7/F8: stripping `status`/`ok` down to an EMPTY path used to return
|
|
359
|
+
// the whole source object (looked like a successful answer). Keep those as
|
|
360
|
+
// pathMiss. r31c F6: `--jq result` is the one intentional alias — callers
|
|
361
|
+
// habitually mean "the whole result package", so return it with a note
|
|
362
|
+
// instead of miss-then-hint.
|
|
363
|
+
if (stripped.stripped === undefined || stripped.path === path)
|
|
364
|
+
return { ...first };
|
|
365
|
+
if (stripped.path.trim() === "") {
|
|
366
|
+
if (stripped.stripped === "result") {
|
|
367
|
+
return {
|
|
368
|
+
projected: { missed: false, value: source },
|
|
369
|
+
fanout: false,
|
|
370
|
+
autoStripped: {
|
|
371
|
+
field: "result",
|
|
372
|
+
reason: "--jq 的路径相对 result;写 result 等于整包",
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
projected: {
|
|
378
|
+
missed: true,
|
|
379
|
+
at: stripped.stripped,
|
|
380
|
+
available: Array.isArray(source) || source === null || typeof source !== "object"
|
|
381
|
+
? `「${stripped.stripped}」是信封顶层键;投影相对 result,剥掉后路径为空——不要用 --jq ${stripped.stripped} 取信封字段`
|
|
382
|
+
: Object.keys(source).slice(0, 12),
|
|
383
|
+
},
|
|
384
|
+
fanout: false,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const second = tryOnce(stripped.path);
|
|
388
|
+
if (!second.projected.missed) {
|
|
389
|
+
return {
|
|
390
|
+
...second,
|
|
391
|
+
autoStripped: {
|
|
392
|
+
field: stripped.stripped,
|
|
393
|
+
reason: `剥掉了信封前缀「${stripped.stripped}.」;投影相对 result,请直接写 --jq ${stripped.path || "<path>"}`,
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
return { ...first };
|
|
398
|
+
}
|
|
307
399
|
export function projectResultExtract(result, paths, keysOnly) {
|
|
308
400
|
const artifactId = typeof result.detailArtifactId === "string" ? result.detailArtifactId : typeof result.artifactId === "string" ? result.artifactId : undefined;
|
|
309
401
|
// Tools hand an in-process artifact as `detailFile` (a local path);
|
|
@@ -328,17 +420,34 @@ export function projectResultExtract(result, paths, keysOnly) {
|
|
|
328
420
|
catch {
|
|
329
421
|
source = text;
|
|
330
422
|
}
|
|
423
|
+
// r29f F5:外置后投影视图 = artifact 正文 ∪ 回执派生键(artifact 优先)。
|
|
424
|
+
source = mergeReceiptDerivedKeys(source, result);
|
|
331
425
|
}
|
|
332
426
|
}
|
|
333
427
|
catch {
|
|
334
428
|
// A pruned/expired artifact must not mask the real tool answer.
|
|
335
429
|
return { ...result, extract: { artifactId: artifactId ?? null, error: "artifact not found", hint: "产物已过期或被清理;去掉 --extract 重新调用该工具即可重新生成" } };
|
|
336
430
|
}
|
|
431
|
+
// r30a F8: when projecting against an artifact that is a write-history record
|
|
432
|
+
// (tool/requestId/performed/…), prefer the in-memory result for keys that
|
|
433
|
+
// exist on the result but would silently resolve to a wrong-shaped value on
|
|
434
|
+
// the artifact (or miss entirely). Never fall back to "parent object".
|
|
435
|
+
const projectionSource = source;
|
|
337
436
|
const out = {
|
|
338
437
|
status: result.status ?? "ok",
|
|
339
438
|
...(artifactId !== undefined ? { artifactId, sha256: artifact?.sha256 } : {}),
|
|
340
439
|
from: readFromArtifact ? "artifact" : "result",
|
|
341
440
|
};
|
|
441
|
+
// 投影只改变取值路径,不改变源结果的完整性事实。尤其是外置 artifact
|
|
442
|
+
// 场景,调用方仍必须能判断 items 是否被折叠,以及源侧 total/itemsCount。
|
|
443
|
+
// 只透传源中实际存在的键;未截断结果不要凭空写 truncated:false。
|
|
444
|
+
const sourceRecord = source && typeof source === "object" && !Array.isArray(source)
|
|
445
|
+
? source
|
|
446
|
+
: undefined;
|
|
447
|
+
for (const key of ["truncated", "truncatedKeys", "itemsCount", "total"]) {
|
|
448
|
+
if (sourceRecord !== undefined && Object.prototype.hasOwnProperty.call(sourceRecord, key))
|
|
449
|
+
out[key] = sourceRecord[key];
|
|
450
|
+
}
|
|
342
451
|
// T6:列表投影里「未配置的列」也是 null 键——工具自己已经写了 projectionHint
|
|
343
452
|
// (#15 A6),但投影层此前把它丢了,于是 `--jq items[].fields.x` 全 null 时
|
|
344
453
|
// 调用方看不到任何线索。把源结果的提示原样透传(有才带,零额外体积)。
|
|
@@ -374,68 +483,109 @@ export function projectResultExtract(result, paths, keysOnly) {
|
|
|
374
483
|
hint: unsupported,
|
|
375
484
|
};
|
|
376
485
|
}
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
486
|
+
// Prefer in-memory result when the path resolves there and would miss
|
|
487
|
+
// on the artifact (never silently substitute a wrong-shaped parent object).
|
|
488
|
+
const onArtifact = projectPathOnSource(projectionSource, path);
|
|
489
|
+
const onResult = readFromArtifact ? projectPathOnSource(result, path) : undefined;
|
|
490
|
+
const preferResult = onResult !== undefined && !onResult.projected.missed && onArtifact.projected.missed ? onResult : undefined;
|
|
491
|
+
const { projected, fanout, autoStripped } = preferResult ?? onArtifact;
|
|
492
|
+
if (fanout) {
|
|
493
|
+
const fan = projected;
|
|
494
|
+
if (fan.missed) {
|
|
495
|
+
const didYouMean = pathMissDidYouMean(fan.at, fan.available);
|
|
384
496
|
return {
|
|
385
497
|
path,
|
|
386
498
|
content: null,
|
|
387
499
|
pathMiss: true,
|
|
388
|
-
missedAt:
|
|
389
|
-
available:
|
|
390
|
-
missedEntries:
|
|
500
|
+
missedAt: fan.at,
|
|
501
|
+
available: fan.available,
|
|
502
|
+
missedEntries: fan.missedEntries,
|
|
391
503
|
...(didYouMean !== undefined ? { didYouMean } : {}),
|
|
392
504
|
...(readFromArtifact ? resultLayerNote(result, path) ?? {} : {}),
|
|
393
|
-
hint: missHintFor(
|
|
505
|
+
hint: missHintFor(fan.at, fan.available),
|
|
394
506
|
};
|
|
395
507
|
}
|
|
396
508
|
return {
|
|
397
509
|
path,
|
|
398
|
-
content:
|
|
399
|
-
mapped:
|
|
400
|
-
...(
|
|
401
|
-
...(
|
|
402
|
-
...(
|
|
510
|
+
content: fan.values,
|
|
511
|
+
mapped: fan.values.length,
|
|
512
|
+
...(autoStripped !== undefined ? { autoStripped } : {}),
|
|
513
|
+
...(preferResult ? { from: "result" } : {}),
|
|
514
|
+
...(fan.nullEntries > 0 ? { nullEntries: fan.nullEntries } : {}),
|
|
515
|
+
...(fan.nullEntries > 0 && fan.nullEntries === fan.values.length ? { hint: ALL_NULL_HINT } : {}),
|
|
516
|
+
...(fan.missedEntries > 0 ? { missedEntries: fan.missedEntries, hint: `有 ${fan.missedEntries} 条记录在该路径下缺失,values 只含命中项` } : {}),
|
|
403
517
|
};
|
|
404
518
|
}
|
|
405
|
-
const
|
|
406
|
-
if (
|
|
407
|
-
|
|
408
|
-
if (projected.unsupportedSyntax === true) {
|
|
519
|
+
const single = projected;
|
|
520
|
+
if (single.missed) {
|
|
521
|
+
if (single.unsupportedSyntax === true) {
|
|
409
522
|
return {
|
|
410
523
|
path,
|
|
411
524
|
content: null,
|
|
412
525
|
pathMiss: false,
|
|
413
526
|
unsupportedSyntax: true,
|
|
414
527
|
unanswered: true,
|
|
415
|
-
available:
|
|
416
|
-
hint: typeof
|
|
528
|
+
available: single.available,
|
|
529
|
+
hint: typeof single.available === "string" ? single.available : UNSUPPORTED_JQ_OBJECT_HINT,
|
|
417
530
|
};
|
|
418
531
|
}
|
|
419
|
-
const didYouMean = pathMissDidYouMean(
|
|
532
|
+
const didYouMean = pathMissDidYouMean(single.at, single.available);
|
|
420
533
|
return {
|
|
421
534
|
path,
|
|
422
535
|
content: null,
|
|
423
536
|
pathMiss: true,
|
|
424
|
-
missedAt:
|
|
425
|
-
available:
|
|
537
|
+
missedAt: single.at,
|
|
538
|
+
available: single.available,
|
|
426
539
|
...(didYouMean !== undefined ? { didYouMean } : {}),
|
|
427
540
|
...(readFromArtifact ? resultLayerNote(result, path) ?? {} : {}),
|
|
428
|
-
hint: missHintFor(
|
|
541
|
+
hint: missHintFor(single.at, single.available),
|
|
429
542
|
};
|
|
430
543
|
}
|
|
431
|
-
return {
|
|
544
|
+
return {
|
|
545
|
+
path,
|
|
546
|
+
content: single.value ?? null,
|
|
547
|
+
...(autoStripped !== undefined ? { autoStripped } : {}),
|
|
548
|
+
...(preferResult ? { from: "result" } : {}),
|
|
549
|
+
};
|
|
432
550
|
});
|
|
433
|
-
|
|
434
|
-
|
|
551
|
+
// r30a F21 / r31c F7: single path keeps top-level `content`. Multi-path used
|
|
552
|
+
// to ALSO dump a path→value `content` map that duplicated `extract[]`
|
|
553
|
+
// (probe: roles[].name + roles[].code returned twice). Keep ONE shape:
|
|
554
|
+
// extract[] for per-path detail, plus `rows` that zip parallel array
|
|
555
|
+
// projections so two columns align without a second round trip.
|
|
556
|
+
const extracts = out.extract;
|
|
557
|
+
if (paths.length === 1) {
|
|
558
|
+
out.content = extracts[0]?.content ?? null;
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
const columns = extracts.map((entry) => ({
|
|
562
|
+
path: entry.path,
|
|
563
|
+
values: entry.pathMiss === true || entry.unsupportedSyntax === true
|
|
564
|
+
? []
|
|
565
|
+
: Array.isArray(entry.content)
|
|
566
|
+
? entry.content
|
|
567
|
+
: entry.content === undefined
|
|
568
|
+
? []
|
|
569
|
+
: [entry.content],
|
|
570
|
+
}));
|
|
571
|
+
const rowCount = Math.max(0, ...columns.map((column) => column.values.length));
|
|
572
|
+
const rows = [];
|
|
573
|
+
for (let index = 0; index < rowCount; index += 1) {
|
|
574
|
+
const row = {};
|
|
575
|
+
for (const column of columns)
|
|
576
|
+
row[column.path] = index < column.values.length ? column.values[index] : null;
|
|
577
|
+
rows.push(row);
|
|
578
|
+
}
|
|
579
|
+
out.rows = rows;
|
|
580
|
+
out.rowCount = rowCount;
|
|
581
|
+
// Deliberately NO top-level `content` on multi-path — that was the duplicate.
|
|
582
|
+
}
|
|
583
|
+
// Keep one machine-readable shape for artifact/call consumers. `content` on
|
|
584
|
+
// the single-path branch remains as a backwards-compatible convenience.
|
|
585
|
+
out.projections = extracts;
|
|
435
586
|
// A projection that missed is not an answer. `status` stays `ok` (adding a
|
|
436
587
|
// new status value would fall through classifyResult to error/exit 15 and
|
|
437
588
|
// change the exit contract), but the envelope now says so out loud.
|
|
438
|
-
const extracts = out.extract;
|
|
439
589
|
if (extracts.some((entry) => entry.pathMiss === true || entry.unsupportedSyntax === true)) {
|
|
440
590
|
out.unanswered = true;
|
|
441
591
|
// 多路径:语法说明给一次,不再逐条复制。不支持语法优先于 pathMiss 语法课。
|
|
@@ -443,7 +593,7 @@ export function projectResultExtract(result, paths, keysOnly) {
|
|
|
443
593
|
out.hint = UNSUPPORTED_JQ_OBJECT_HINT;
|
|
444
594
|
}
|
|
445
595
|
else if (multiPath) {
|
|
446
|
-
out.hint = `${RESULT_RELATIVE_PATH_NOTE}${JQ_PATH_SYNTAX}
|
|
596
|
+
out.hint = `${RESULT_RELATIVE_PATH_NOTE}${JQ_PATH_SYNTAX}多路径时用 extract[](逐条)或 rows[](列对齐);不再重复 content 映射。${JQ_PATH_HELP_POINTER}`;
|
|
447
597
|
}
|
|
448
598
|
}
|
|
449
599
|
}
|
package/dist/read-shapes.js
CHANGED
|
@@ -12,15 +12,16 @@
|
|
|
12
12
|
const READ_SHAPES = {
|
|
13
13
|
// r28k 治理面形状钉:截断≠空表;指纹有用途;doctor/get_status 分流。
|
|
14
14
|
"discover_environment": {
|
|
15
|
-
keys: ["applications:array<{code,name,groupId?,portalHidden?}>", "applicationsCount", "groups", "groupsCount", "foldersAndModels?", "foldersAndModelsCount?", "foldersAndModelsOmitted?", "models?", "modelsCount?", "modelsOmitted?", "tablesHint?", "applicationCode", "applicationResolution", "readiness", "environmentFingerprint", "fingerprintHint?", "detailArtifactId?"],
|
|
15
|
+
keys: ["applications:array<{code,name,groupId?,portalHidden?}>", "applicationsCount", "groups", "groupsCount", "foldersAndModels?", "foldersAndModelsCount?", "foldersAndModelsOmitted?", "models?", "modelsCount?", "modelsOmitted?", "tablesHint?", "applicationCode", "applicationResolution", "readiness", "detectedVersion", "inAnchorFamily", "versionSource", "versionFamily", "anchorVersion", "versionNotice?", "environmentFingerprint", "fingerprintHint?", "detailArtifactId?"],
|
|
16
16
|
notes: [
|
|
17
17
|
"applications/groups 内联;foldersAndModels/models 外置时是 null + *Omitted:true(不是 [])",
|
|
18
18
|
"environmentFingerprint 用途见 fingerprintHint(写前漂移/计划绑定/读缓存键,非审批令牌)",
|
|
19
|
+
"detectedVersion/inAnchorFamily/versionSource 与 doctor/fingerprint 同源",
|
|
19
20
|
],
|
|
20
21
|
},
|
|
21
22
|
"environment.fingerprint": {
|
|
22
|
-
keys: ["environment", "fingerprint", "fingerprintHint"],
|
|
23
|
-
notes: ["fingerprint 随 env/origin/application/enterprise
|
|
23
|
+
keys: ["environment", "fingerprint", "fingerprintHint", "detectedVersion", "inAnchorFamily", "versionSource", "versionFamily", "anchorVersion", "versionNotice?"],
|
|
24
|
+
notes: ["fingerprint 随 env/origin/application/enterprise 变;写前漂移/计划绑定", "版本证据与 discover/doctor 同源"],
|
|
24
25
|
},
|
|
25
26
|
"get_status": {
|
|
26
27
|
keys: ["environment", "session", "coverage", "environmentScope", "unsupportedCapabilities?", "writeLease"],
|
|
@@ -31,8 +32,8 @@ const READ_SHAPES = {
|
|
|
31
32
|
notes: ["unsupportedCapabilities.coverageClaimMeaning:只列已知缺口;未列 ≠ 已验证可用"],
|
|
32
33
|
},
|
|
33
34
|
"coverage.manifest": {
|
|
34
|
-
keys: ["manifest:{version_family,anchor_version,items[],counts?,filter?,hint?}"],
|
|
35
|
-
notes: ["按 lane 索引(同 lane
|
|
35
|
+
keys: ["manifest:{version_family,anchor_version,items[],counts?{total,totalScope,totalBeforeFilter?,byStatus},filter?,hint?}"],
|
|
36
|
+
notes: ["按 lane 索引(同 lane 可多行);capability 参数可传能力描述名或 lane id(如 W1-list-view);答能力能不能用 → omc capability listview"],
|
|
36
37
|
},
|
|
37
38
|
"apply_changes": {
|
|
38
39
|
keys: ["status", "steps?", "failedStep?", "progressFile?", "deliveryState?"],
|
|
@@ -42,7 +43,21 @@ const READ_SHAPES = {
|
|
|
42
43
|
// items are {id, fields:{<code>: value}} — business columns live under fields.
|
|
43
44
|
keys: ["items:array<{id,fields:{<code>:value}}>", "total", "totalPages", "page", "size"],
|
|
44
45
|
// r28i F4: agents try rows[] / items[].sku first; pin the real shape here too.
|
|
45
|
-
notes: [
|
|
46
|
+
notes: [
|
|
47
|
+
"业务列在 items[].fields.<code>,不在 item 顶层,也不是 rows[](--jq items[].sku / rows[].sku 会 miss;正确:items[].fields.sku)",
|
|
48
|
+
"字典字段回记录 code;显示名需 dictionary.records 映射(伴生 *_key 常为 null,勿当显示名)",
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
"dictionary.records": {
|
|
52
|
+
keys: ["records:{totalElements,content[]}", "total", "orderNote", "enabledOnly?", "dictionaryId?", "parentId?"],
|
|
53
|
+
notes: ["enabledOnly/parentId 也走同一分页壳,不再回裸数组"],
|
|
54
|
+
},
|
|
55
|
+
"dictionary.get": {
|
|
56
|
+
keys: ["dictionary:{id,code,name,...}", "recordsCount?"],
|
|
57
|
+
notes: ["头读面不含 records 明细;条数看 recordsCount,明细走 dictionary.records"],
|
|
58
|
+
},
|
|
59
|
+
"dictionary.references": {
|
|
60
|
+
keys: ["dictionary", "references:array<{schemaCode,fieldCode,fieldName,propertyType}>", "count", "truncated", "modelsScanned"],
|
|
46
61
|
},
|
|
47
62
|
"folder.tree": {
|
|
48
63
|
keys: ["applicationCode", "applicationExists", "nodeCount", "folderCount", "modelCount", "nodes[]", "note"],
|
|
@@ -55,12 +70,17 @@ const READ_SHAPES = {
|
|
|
55
70
|
"data.load": {
|
|
56
71
|
keys: ["bizObjectId", "exists", "data:{...}"],
|
|
57
72
|
},
|
|
73
|
+
"app.list": {
|
|
74
|
+
keys: ["applications:array<{code,name,groupId?}>", "applicationsCount", "omitted"],
|
|
75
|
+
notes: ["applicationsCount + omitted 与 discover 同构;omitted:false 表示内联即全集"],
|
|
76
|
+
},
|
|
58
77
|
"model.list": {
|
|
59
78
|
// Compact diet may rewrite models to "code|name" pairs and add modelsCount.
|
|
60
|
-
keys: ["models:array<code
|
|
79
|
+
keys: ["models:array<{code,schemaCode,name}>", "count", "modelsCount", "applicationCode", "applicationResolution", "scopeHint"],
|
|
61
80
|
notes: [
|
|
62
|
-
"下游工具的 schemaCode = 本面 models 的 code
|
|
63
|
-
"
|
|
81
|
+
"下游工具的 schemaCode = 本面 models 的 code",
|
|
82
|
+
"applicationCode 不在 app.list → app-not-found(applicationResolution.status=unresolved)",
|
|
83
|
+
"modelsCount 等派生键外置后也可 --jq(投影层会合进 artifact 视图)",
|
|
64
84
|
],
|
|
65
85
|
},
|
|
66
86
|
"model.inspect": {
|
|
@@ -135,8 +155,8 @@ const READ_SHAPES = {
|
|
|
135
155
|
notes: ["roleId 与 roleCode 二者其一;members[].id 是用户 recId"],
|
|
136
156
|
},
|
|
137
157
|
"org.role.userScope": {
|
|
138
|
-
keys: ["roleId", "userId", "count", "scopes:array<{id,name,type,deptPath}>"],
|
|
139
|
-
notes: ["不是枚举档位:scopes 是部门节点列表;roleId 与 roleCode
|
|
158
|
+
keys: ["roleId", "userId", "count", "scopes:array<{id,deptId,name,type,deptPath,includeChildren,source}>", "actor?", "readback"],
|
|
159
|
+
notes: ["不是枚举档位:scopes 是部门节点列表;roleId 与 roleCode 二者其一;includeChildren 平台不返回(unknown);source:configured;空结果是合法空集"],
|
|
140
160
|
},
|
|
141
161
|
};
|
|
142
162
|
/** Lookup the authored return shape for a tool, if any. */
|
|
@@ -54,7 +54,7 @@ const bizserviceSelfCallCorp = Object.freeze({
|
|
|
54
54
|
environment,
|
|
55
55
|
serviceCode: "<service_code>",
|
|
56
56
|
methodCode: "<method_code>",
|
|
57
|
-
|
|
57
|
+
methodInputParameters: [
|
|
58
58
|
{ code: "X-LowCode-Corpid", name: "corpId头", required: false, codeType: 0, bizCode: "<corpId 常量值>", configJson: { position: "REQUEST_HEADER" } },
|
|
59
59
|
],
|
|
60
60
|
},
|
package/dist/recipes/field.js
CHANGED
|
@@ -148,6 +148,20 @@ const dictionarySelection = Object.freeze({
|
|
|
148
148
|
requiresEnvironmentDerivedIds: true,
|
|
149
149
|
prerequisiteData: { source: "dictionary.names → dictionary.records(enabledOnly:true)", fields: ["dictionary.code", "record.code", "record.id"] },
|
|
150
150
|
});
|
|
151
|
+
const dictionaryMultiSelection = Object.freeze({
|
|
152
|
+
id: "dictionary-backed-multi-select",
|
|
153
|
+
title: "字典多选",
|
|
154
|
+
status: "requires-prior-readback",
|
|
155
|
+
tool: "field.create",
|
|
156
|
+
args: { environment, schemaCode: model, code: "tags", name: "标签", propertyType: "DROPDOWN_MULTI_BOX", options: { optionsSet: { optionsType: "dictionary", dictionary: { checkedDictionary: "<id>", checkedDictionaryCode: "<code>", useDictionariesData: [{ code: "<c>", id: "<id>" }] } } } },
|
|
157
|
+
preconditions: ["模型/字典可用"],
|
|
158
|
+
conditionalRequirements: ["optionsSet同单选;值=分号串"],
|
|
159
|
+
priorToolReferences: [],
|
|
160
|
+
dryRun: { supported: true, command: `omc call field.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "" },
|
|
161
|
+
expectedReadback: { next: ["field.publish"] },
|
|
162
|
+
refusalRecovery: commonRecovery,
|
|
163
|
+
requiresEnvironmentDerivedIds: true,
|
|
164
|
+
});
|
|
151
165
|
const childTable = Object.freeze({
|
|
152
166
|
id: "minimal-child-table",
|
|
153
167
|
title: "创建带两个标量列的子表字段",
|
|
@@ -315,8 +329,7 @@ const externalLookupModel = Object.freeze({
|
|
|
315
329
|
conditionalRequirements: [
|
|
316
330
|
"**数据段首选(平台标准模式,2026-10-29 playwright 门户实证定案)**:映射模型的 GetList/Load 规则挂 BIZ_ACTION 实时调接口(接口方法配 frontEndExtensionEnable:true)——门户关联控件**打开即触发**前端查询链→GetList 规则→BIZ_ACTION 实时调外部接口(引擎日志 bizBusService.invoke + 外部网关日志双侧铁证)。**验证必须走门户填表;data.list 后端查询不反映此链,别用它判死**(r29 曾据此误判不成立)。配置侧三个收尾点:① WORK_SHEET 字段必须带 relativeCode(仅 options.schemaCode 发布表单会 301015 关联业务模型为空);② GetList 规则的 BIZ_ACTION 入参映射要接前端搜索词(实测未配词时 invoke 参数为空 {});③ 出参→列表行的映射配完候选才回显(未配时弹窗 Total 0 但接口已调)。「下拉框形态(输关键字回车查询)」是表单 attributes 层的控件形态设置,默认弹窗形态。备选(不需要实时时):预置 Create/Update 规则落映射表 / TIMING 定时同步。图内「BIZ_ACTION→CREATE 落表→再查」回环在 1.1.25 不可表达,不要走这条岔路。",
|
|
317
331
|
"**表单段是平台原生能力**:关联单选指向映射模型后,门户上输入关键字回车即查映射表、选中即 mappings 带出——前台填表时映射自然出现,无需按钮/脚本。",
|
|
318
|
-
"
|
|
319
|
-
"映射模型表要有数据下拉才有选项——空表=空下拉;数据段先通再配表单段。",
|
|
332
|
+
"下拉框形态在 form.draft 控件层配置;映射表要有数据下拉才有选项。",
|
|
320
333
|
],
|
|
321
334
|
priorToolReferences: [
|
|
322
335
|
{ tool: "model.create", args: { environment }, purpose: "建映射模型(字段与接口返回一一对应)" },
|
|
@@ -333,6 +346,6 @@ const externalLookupModel = Object.freeze({
|
|
|
333
346
|
});
|
|
334
347
|
export const FIELD_METADATA = Object.freeze({
|
|
335
348
|
"field.update": Object.freeze({ recipes: [fieldSerialNumber, formConditionalRequired, formAlwaysReadonly, formConditionalReadonly], relatedTools: ["field.list", "field.publish", "form.draft", "form.publish"] }),
|
|
336
|
-
"field.create": Object.freeze({ recipes: [numericTwoDecimals, dictionarySelection, childTable, businessModelDropdown, formulaAggregationCrossModel, externalLookupModel], relatedTools: ["dictionary.names", "dictionary.records", "discover_environment", "field.list", "field.publish", "form.draft", "configure_form", "form.publish"] }),
|
|
349
|
+
"field.create": Object.freeze({ recipes: [numericTwoDecimals, dictionarySelection, dictionaryMultiSelection, childTable, businessModelDropdown, formulaAggregationCrossModel, externalLookupModel], relatedTools: ["dictionary.names", "dictionary.records", "discover_environment", "field.list", "field.publish", "form.draft", "configure_form", "form.publish"] }),
|
|
337
350
|
});
|
|
338
351
|
//# sourceMappingURL=field.js.map
|
package/dist/recipes/form.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { environment, model, commonRecovery } from "./shared.js";
|
|
2
2
|
const configureFormRecipe = Object.freeze({
|
|
3
3
|
id: "publish-default-form",
|
|
4
|
-
title: "
|
|
4
|
+
title: "把已发布字段绑进默认表单(发布请随后调用 form.publish)",
|
|
5
5
|
status: "offline-valid",
|
|
6
6
|
tool: "configure_form",
|
|
7
|
-
args: { environment, schemaCode: model
|
|
7
|
+
args: { environment, schemaCode: model },
|
|
8
8
|
preconditions: ["字段已 field.publish"],
|
|
9
9
|
conditionalRequirements: ["表单层条件必填/只读请用 field.update 的 options(form-conditional-required / form-always-readonly recipe),不是本编排器"],
|
|
10
10
|
priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认字段已发布" }],
|
|
@@ -36,7 +36,7 @@ const formDraftTwoColumn = Object.freeze({
|
|
|
36
36
|
draftViewJson: '[[\"visitNo\",\"customer\"],[\"remark\"]]',
|
|
37
37
|
draftAttributesJson: JSON.stringify({
|
|
38
38
|
visitNo: { key: "visitNo", type: 1, options: { name: "拜访单号", span: 12 } },
|
|
39
|
-
customer: { key: "customer", type:
|
|
39
|
+
customer: { key: "customer", type: 9, options: { name: "客户", span: 12 } },
|
|
40
40
|
remark: { key: "remark", type: 1, options: { name: "备注", span: 24 } },
|
|
41
41
|
}, null, 0),
|
|
42
42
|
},
|
|
@@ -44,6 +44,7 @@ const formDraftTwoColumn = Object.freeze({
|
|
|
44
44
|
conditionalRequirements: [
|
|
45
45
|
"**列数由行分组决定,不由 span 决定**:一行放几个 code 就是几列;span 只控制控件在行内的 24 栅格宽度,且必须与行分组自洽(两列 span:12+12,整宽 span:24)",
|
|
46
46
|
"draftAttributesJson 的每个控件的 `options` 必须是**对象**,不能是 JSON 字符串(否则运行时写 options.name_i18n 抛错、整页空白)",
|
|
47
|
+
"控件 type 与 field propertyType 数字同源(WORK_SHEET=9)",
|
|
47
48
|
"rows 与 attrs 的 code 集合必须一致,否则 `layout_coverage_mismatch`",
|
|
48
49
|
"改列数就改行分组、重写 attrs 的 span —— 不要只改 span",
|
|
49
50
|
],
|
|
@@ -76,6 +76,7 @@ const dataListFilter = Object.freeze({
|
|
|
76
76
|
conditionalRequirements: [
|
|
77
77
|
"filter 是**一个**子句 `{op, field, value?}`;op ∈ eq/neq/gt/lt/gte/lte/like/notlike/in/notin/between/notbetween/isnull/isnotnull(camelCase 别名 equals/notEquals/contains/isNull/isNotNull 等价——r36 体检发现别名合法但只列规范名会被误判『静默接受坏 op』)。",
|
|
78
78
|
"多条件 `filters:[…]`(AND);分组 `filterGroup:{op:'and'|'or',clauses:[…]}`;系统/外部字段用 rawCondition。",
|
|
79
|
+
"列投影:`fields` / `displayFields` / `columns` 三名同义(与 data.load 的 fields 对齐)。",
|
|
79
80
|
],
|
|
80
81
|
priorToolReferences: [
|
|
81
82
|
{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认要过滤的字段 code 已发布" },
|
|
@@ -85,30 +86,79 @@ const dataListFilter = Object.freeze({
|
|
|
85
86
|
refusalRecovery: [{ code: "invalid-filter-DSL", action: "按错误里的 minimalValidFilter/acceptedOps 修正 op/field/value" }],
|
|
86
87
|
requiresEnvironmentDerivedIds: false,
|
|
87
88
|
});
|
|
89
|
+
const childRowsUpsert = Object.freeze({
|
|
90
|
+
id: "child-rows-upsert",
|
|
91
|
+
title: "子表三合一:改既有行 / 追加 / 显式删除",
|
|
92
|
+
status: "offline-valid",
|
|
93
|
+
tool: "data.update",
|
|
94
|
+
args: {
|
|
95
|
+
environment,
|
|
96
|
+
schemaCode: model,
|
|
97
|
+
bizObjectId: "<data.load 的 id>",
|
|
98
|
+
formData: {
|
|
99
|
+
items: [
|
|
100
|
+
{ id: "<既有行 id>", qty: 2, price: 10, amount: 20 },
|
|
101
|
+
{ sku: "NEW", qty: 1, price: 5, amount: 5, rowStatus: "Added" },
|
|
102
|
+
{ id: "<要删的行 id>", rowStatus: "Deleted" },
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
preconditions: ["data.load 拿子行 id"],
|
|
107
|
+
conditionalRequirements: [
|
|
108
|
+
"既有行带 id(省 id 当新增);追加 rowStatus:Added;删除 {id,rowStatus:Deleted};**少传行不删除**(readback.warnings: child-rows-not-deleted)",
|
|
109
|
+
],
|
|
110
|
+
priorToolReferences: [
|
|
111
|
+
{ tool: "data.load", args: { environment, schemaCode: model, bizObjectId: "<id>" }, purpose: "取子行 id" },
|
|
112
|
+
],
|
|
113
|
+
dryRun: { supported: true, command: `omc call data.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "校验子行形状" },
|
|
114
|
+
expectedReadback: { readback: { authoritative: "子表行按 Added/Modified/Deleted 落地" } },
|
|
115
|
+
refusalRecovery: [
|
|
116
|
+
{ code: "child-rows-missing-id", action: "无 id 的行补 id 或改 rowStatus:Added" },
|
|
117
|
+
],
|
|
118
|
+
notes: "data.save 新建时子行可不带 id(工具 stamp Added);本配方指 update/既有记录",
|
|
119
|
+
requiresEnvironmentDerivedIds: true,
|
|
120
|
+
});
|
|
88
121
|
const dataDeleteSweep = Object.freeze({
|
|
89
122
|
id: "sweep-all-readback",
|
|
90
|
-
title: "清空模型数据并权威验证(list 取 ids → delete →
|
|
123
|
+
title: "清空模型数据并权威验证(list 取 ids → 逐条 delete → 逐条 load 确认 absent)",
|
|
91
124
|
status: "offline-valid",
|
|
92
125
|
tool: "data.delete",
|
|
93
|
-
|
|
126
|
+
// 1.1.x 上 bizObjectIds:[多个] 是假成功(blocker:data-delete-batch-fake-success)。
|
|
127
|
+
// recipe.args 给单条形状;多条用 dryRun.command 的 --args-list 一次跑完。
|
|
128
|
+
args: { environment, schemaCode: model, bizObjectId: "<data.list 一条 id>" },
|
|
94
129
|
preconditions: [
|
|
95
130
|
"先 data.list 取全量 ids(`all:true` 才含 DRAFT;翻页取全),清理对象按 data.list 全量",
|
|
96
131
|
"**行数以 data.list total 为准,别按「N 提交=2N 行」外推**:带流程的 submit 原地推进同一行(r28b 实测 3→3);双行只在平台另起草稿行时出现(r30 的 5→10)",
|
|
97
132
|
],
|
|
98
133
|
conditionalRequirements: [
|
|
99
|
-
"
|
|
134
|
+
"1.1.x 上批量 `bizObjectIds` 是假成功(回执 DELIVERED+absent:true 但行仍在),必须单条 `bizObjectId`;用 `--args-list` 一次跑多条单删,不要抄 `bizObjectIds:[多个]`",
|
|
135
|
+
"删完逐条 `data.load` 确认 `exists:false`(`absent:true` / `modelResidue.total` 递减都不是删除成功的证明);最后再 `data.list` 复验 total:0",
|
|
100
136
|
"id 已被删时幂等 no-op,重复清理安全",
|
|
101
137
|
],
|
|
102
138
|
priorToolReferences: [
|
|
103
|
-
{ tool: "data.list", args: { environment, schemaCode: model }, purpose: "取全量
|
|
139
|
+
{ tool: "data.list", args: { environment, schemaCode: model }, purpose: "取全量 ids(含 DRAFT 行),再拆成逐条 bizObjectId" },
|
|
140
|
+
],
|
|
141
|
+
dryRun: {
|
|
142
|
+
supported: true,
|
|
143
|
+
command: `omc call data.delete --env ${environment} --args-list '[{"schemaCode":"${model}","bizObjectId":"<id1>"},{"schemaCode":"${model}","bizObjectId":"<id2>"}]'`,
|
|
144
|
+
notes: "多条用 --args-list 逐条单删(每条一个 bizObjectId);不要用 bizObjectIds 数组。dry-run 可加 --dry-run 预检不落库",
|
|
145
|
+
},
|
|
146
|
+
expectedReadback: {
|
|
147
|
+
readback: { deleted: true, exists: false },
|
|
148
|
+
next: [
|
|
149
|
+
"对每条 id:omc call data.load --args '{\"schemaCode\":\"…\",\"bizObjectId\":\"<id>\"}' 确认 exists:false",
|
|
150
|
+
"最后 data.list 复验 total:0 / items:[](权威清空判据)",
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
refusalRecovery: [
|
|
154
|
+
{ code: "data-delete-batch-fake-success", action: "改用单条 bizObjectId,或 --args-list 一次跑多条单删;不要再传 bizObjectIds:[多个]" },
|
|
155
|
+
{ code: "wire-business", action: "模型须已创建且有已发布字段;运行面 50000 时先 model.inspect modelPresent 判存在" },
|
|
104
156
|
],
|
|
105
|
-
dryRun: { supported: true, command: `omc call data.delete --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 出删除计划不落库" },
|
|
106
|
-
expectedReadback: { readback: { deleted: "N", modelResidue: { total: 0 } }, next: ["data.list 复验 total:0 / items:[](权威清空判据)"] },
|
|
107
|
-
refusalRecovery: [{ code: "wire-business", action: "模型须已创建且有已发布字段;运行面 50000 时先 model.inspect modelPresent 判存在" }],
|
|
108
157
|
requiresEnvironmentDerivedIds: false,
|
|
109
158
|
});
|
|
110
159
|
export const RUNTIME_DATA_METADATA = Object.freeze({
|
|
111
|
-
"data.save": Object.freeze({ recipes: [workflowSave], relatedTools: ["workflow.get_published", "data.submit", "workitem.list"] }),
|
|
160
|
+
"data.save": Object.freeze({ recipes: [workflowSave], relatedTools: ["workflow.get_published", "data.submit", "workitem.list", "data.update"] }),
|
|
161
|
+
"data.update": Object.freeze({ recipes: [childRowsUpsert], relatedTools: ["data.load", "data.save", "data.submit"] }),
|
|
112
162
|
"data.submit": Object.freeze({ recipes: [plainSubmit, workflowSubmit], relatedTools: ["data.save", "workitem.list", "data.load"] }),
|
|
113
163
|
"data.list": Object.freeze({ recipes: [dataListFilter], relatedTools: ["data.load", "data.delete", "listview.configure", "field.list"] }),
|
|
114
164
|
"data.delete": Object.freeze({ recipes: [dataDeleteSweep], relatedTools: ["data.list", "data.load", "data.submit"] }),
|