@stalfh233/omc-cli 0.4.31 → 0.4.33

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.
@@ -54,8 +54,8 @@ const childTable = Object.freeze({
54
54
  name: "明细",
55
55
  propertyType: "CHILD_TABLE",
56
56
  subFields: [
57
- { code: "item_name", name: "名称", propertyType: "SHORT_TEXT" },
58
- { code: "item_qty", name: "数量", propertyType: "NUMERICAL", options: { scale: 1 } },
57
+ { code: "itemName", name: "名称", propertyType: "SHORT_TEXT" },
58
+ { code: "itemQty", name: "数量", propertyType: "NUMERICAL", options: { scale: 1 } },
59
59
  ],
60
60
  },
61
61
  preconditions: ["目标模型已存在;父字段 code 和子列 codes 尚未存在;父模型字段发布后再配置表单"],
@@ -65,7 +65,7 @@ const childTable = Object.freeze({
65
65
  ],
66
66
  priorToolReferences: [],
67
67
  dryRun: { supported: true, command: `omc call field.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 验证 subFields 并跳过环境写入" },
68
- expectedReadback: { readback: { field: { code: "items", propertyType: "CHILD_TABLE", subSchema: { properties: ["item_name", "item_qty"] } } }, next: ["field.publish", "form.draft", "form.publish"] },
68
+ expectedReadback: { readback: { field: { code: "items", propertyType: "CHILD_TABLE", subSchema: { properties: ["itemName", "itemQty"] } } }, next: ["field.publish", "form.draft", "form.publish"] },
69
69
  refusalRecovery: [
70
70
  { code: "capability-out-of-coverage", action: "补齐 subFields,并移除不支持的嵌套/高级子列族" },
71
71
  { code: "child-table-update-unsupported", action: "子列用 schemaCode=items 的 field.create/update;不要向父字段 update 传 options/choices/subFields" },
@@ -294,9 +294,9 @@ const workflowTemplateUpdate = Object.freeze({
294
294
  args: {
295
295
  environment,
296
296
  schemaCode: model,
297
- code: "apply_workflow",
297
+ code: "applyWorkflow",
298
298
  template: {
299
- workflowCode: "apply_workflow",
299
+ workflowCode: "applyWorkflow",
300
300
  activities: [
301
301
  { activityCode: "Activity1", activityType: "START", activityName: "开始", i18nMessage: "开始" },
302
302
  { activityCode: "Activity2", activityType: "PARTICIPANT", activityName: "填写申请单", i18nMessage: "填写申请单", participant: "{Originator}" },
@@ -317,7 +317,7 @@ const workflowTemplateUpdate = Object.freeze({
317
317
  "**平台 workflow.create 会自动生成含「填写申请单」发起节点的草稿(START → 填写申请单 → 审批 → END);workflow.update 是 merge,不删除既有节点——要严格三节点需在设计器删该节点,或如实接受该发起节点**",
318
318
  "**读回 participant 为空是平台模板更新面的已知残留,不要反复 update 试图修正(实测 6 次 update 仍为空);记录该差异并留待设计器核对,一次读回确认即可**",
319
319
  ],
320
- priorToolReferences: [{ tool: "workflow.get_draft", args: { environment, workflowCode: "apply_workflow" }, purpose: "取草稿基线作为 merge 基线(默认面即含 activityCode/name/edges;不要 --full)" }],
320
+ priorToolReferences: [{ tool: "workflow.get_draft", args: { environment, workflowCode: "applyWorkflow" }, purpose: "取草稿基线作为 merge 基线(默认面即含 activityCode/name/edges;不要 --full)" }],
321
321
  dryRun: { supported: true, command: `omc call workflow.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验模板形状,不写入" },
322
322
  expectedReadback: { readback: { draftUpdated: true, activities: "<节点数=你数组长度>", edges: "<链路>" }, next: ["用默认 workflow.get_draft 复核 activities/edges(不要 --full)", "workflow.publish", "workflow.bindRule"] },
323
323
  refusalRecovery: [
@@ -336,9 +336,9 @@ const workflowConditionalBranch = Object.freeze({
336
336
  args: {
337
337
  environment,
338
338
  schemaCode: model,
339
- code: "apply_workflow",
339
+ code: "applyWorkflow",
340
340
  template: {
341
- workflowCode: "apply_workflow",
341
+ workflowCode: "applyWorkflow",
342
342
  activities: [
343
343
  { activityCode: "A1", activityType: "START", activityName: "开始", i18nMessage: "开始" },
344
344
  { activityCode: "A2", activityType: "PARTICIPANT", activityName: "填写申请单", i18nMessage: "填写申请单", participant: "{Originator}" },
@@ -361,7 +361,7 @@ const workflowConditionalBranch = Object.freeze({
361
361
  "分支可只写 activities + routes:工具按数组顺序补全到 END 的连线(`con` 兜底线仍需你显式给,除非它就是主链)。",
362
362
  ],
363
363
  priorToolReferences: [
364
- { tool: "workflow.get_draft", args: { environment, workflowCode: "apply_workflow" }, purpose: "取草稿基线(apply_workflow 换成真实 workflowCode;默认面即含 activityCode/name/edges,不要 --full)" },
364
+ { tool: "workflow.get_draft", args: { environment, workflowCode: "applyWorkflow" }, purpose: "取草稿基线(apply_workflow 换成真实 workflowCode;默认面即含 activityCode/name/edges,不要 --full)" },
365
365
  { tool: "org.role.list", args: { environment }, purpose: "取真实参与人/角色 code,避免自由文本名解析不到人" },
366
366
  ],
367
367
  dryRun: { supported: true, command: `omc call workflow.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验 activities/routes/条件叶,不写入" },
@@ -512,12 +512,12 @@ const configureModelRecipe = Object.freeze({
512
512
  args: {
513
513
  environment,
514
514
  name: "采购申请",
515
- code: "purchase_apply",
515
+ code: "purchaseApply",
516
516
  applicationCode: "<applicationCode>",
517
517
  fields: [
518
- { code: "apply_no", name: "申请编号", propertyType: "SHORT_TEXT" },
519
- { code: "apply_amount", name: "申请金额", propertyType: "NUMERICAL", options: { scale: 2 } },
520
- { code: "apply_note", name: "申请说明", propertyType: "LONG_TEXT" },
518
+ { code: "applyNo", name: "申请编号", propertyType: "SHORT_TEXT" },
519
+ { code: "applyAmount", name: "申请金额", propertyType: "NUMERICAL", options: { scale: 2 } },
520
+ { code: "applyNote", name: "申请说明", propertyType: "LONG_TEXT" },
521
521
  ],
522
522
  publishForm: true,
523
523
  },
@@ -528,7 +528,7 @@ const configureModelRecipe = Object.freeze({
528
528
  ],
529
529
  priorToolReferences: [{ tool: "discover_environment", args: { environment }, purpose: "取 applicationCode 与已有模型" }],
530
530
  dryRun: { supported: true, command: `omc call configure_model --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 走 schema/语义/治理预检,不创建" },
531
- expectedReadback: { readback: { modelPresent: true, publishedFields: ["apply_no", "apply_amount", "apply_note"] }, next: ["field.update(表单层条件)", "listview.configure(默认视图已自动存在,直接配置,勿 create)"] },
531
+ expectedReadback: { readback: { modelPresent: true, publishedFields: ["applyNo", "applyAmount", "applyNote"] }, next: ["field.update(表单层条件)", "listview.configure(默认视图已自动存在,直接配置,勿 create)"] },
532
532
  refusalRecovery: [{ code: "capability-out-of-coverage", action: "标量族之外改用低层 field.create;NUMERICAL 补 options.scale" }, ...commonRecovery],
533
533
  requiresEnvironmentDerivedIds: true,
534
534
  prerequisiteData: { source: "discover_environment", fields: ["applicationCode"] },
@@ -552,7 +552,7 @@ const numericTwoDecimals = Object.freeze({
552
552
  title: "数值字段(金额,两位小数 / 整数)— scale 必填",
553
553
  status: "offline-valid",
554
554
  tool: "field.create",
555
- args: { environment, schemaCode: model, code: "apply_amount", name: "申请金额", propertyType: "NUMERICAL", options: { scale: 3 } },
555
+ args: { environment, schemaCode: model, code: "applyAmount", name: "申请金额", propertyType: "NUMERICAL", options: { scale: 3 } },
556
556
  preconditions: ["模型已存在"],
557
557
  conditionalRequirements: ["NUMERICAL 必须显式 options.scale:scale = 小数位数(0=整数, 1=1位, 2=2位 … 8=8位);缺 scale 门户显示裸 decimal 且工具会拒绝。"],
558
558
  priorToolReferences: [{ tool: "discover_environment", args: { environment }, purpose: "确认目标模型已存在" }],
@@ -677,7 +677,7 @@ const dataruleRequired = Object.freeze({
677
677
  args: {
678
678
  environment,
679
679
  schemaCode: model,
680
- propertyCode: "leave_reason",
680
+ propertyCode: "leaveReason",
681
681
  dataRuleType: 4,
682
682
  options: { requiredFormula: '{leave_amount} 大于 {"valueType":"custom","value":5000}' },
683
683
  name: "金额超过阈值说明必填",
@@ -748,7 +748,7 @@ const formConditionalRequired = Object.freeze({
748
748
  args: {
749
749
  environment,
750
750
  schemaCode: model,
751
- code: "leave_reason",
751
+ code: "leaveReason",
752
752
  name: "请假事由",
753
753
  options: { requiredFormula: '{leave_amount} 大于 {"valueType":"custom","value":5000}' },
754
754
  },
@@ -756,7 +756,7 @@ const formConditionalRequired = Object.freeze({
756
756
  conditionalRequirements: ["条件串:`{字段} 操作符 [值]`(操作符两侧留空格)。操作符用**中文**(真实设计器产出):等于/不等于/大于/小于/大于等于/小于等于/包含/不包含/包含全部/包含任意一个/等于任意一个/为空/不为空;也接受符号 == != > >= <= Contains IsEmpty。为空/不为空**不带值**(`{reason} 不为空`),其余带 JSON 值 {\"valueType\":...,\"value\":...}。多条件用 ` && `、多组用 ` || `。**不是** eq/gt/isnull——会让门户渲染 SyntaxError"],
757
757
  priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认条件字段与目标字段都已发布" }],
758
758
  dryRun: { supported: true, command: `omc call field.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验字段 options 形状,不写入" },
759
- expectedReadback: { readback: { field: { code: "leave_reason", options: { requiredFormula: "<protocol>" } } }, next: ["(一调)field.update cascadeRepublish:true", "(分步)field.publish → form.draft(rebuild) → form.publish", "(只刷新表单)form.publish rebuild:true"] },
759
+ expectedReadback: { readback: { field: { code: "leaveReason", options: { requiredFormula: "<protocol>" } } }, next: ["(一调)field.update cascadeRepublish:true", "(分步)field.publish → form.draft(rebuild) → form.publish", "(只刷新表单)form.publish rebuild:true"] },
760
760
  refusalRecovery: [
761
761
  { code: "capability-out-of-coverage", action: "条件串用中文或符号操作符(等于/大于/不为空 或 == />/IsEmpty);恒必填用字面量 \"true\"" },
762
762
  ...commonRecovery,
@@ -768,12 +768,12 @@ const formAlwaysReadonly = Object.freeze({
768
768
  title: "表单层恒只读字段(字段 options.readonlyFormula:true)",
769
769
  status: "offline-valid",
770
770
  tool: "field.update",
771
- args: { environment, schemaCode: model, code: "approval_level", name: "审批级别", options: { readonlyFormula: true } },
771
+ args: { environment, schemaCode: model, code: "approvalLevel", name: "审批级别", options: { readonlyFormula: true } },
772
772
  preconditions: ["目标字段已发布"],
773
773
  conditionalRequirements: ["恒只读用布尔 readonlyFormula:true;条件只读用 readonlyCondition 条件串"],
774
774
  priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认字段已发布" }],
775
775
  dryRun: { supported: true, command: `omc call field.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验 readonlyFormula 是布尔", },
776
- expectedReadback: { readback: { field: { code: "approval_level", options: { readonlyFormula: true } } }, next: ["(一调)field.update cascadeRepublish:true", "(分步)field.publish → form.draft(rebuild) → form.publish", "(只刷新表单)form.publish rebuild:true"] },
776
+ expectedReadback: { readback: { field: { code: "approvalLevel", options: { readonlyFormula: true } } }, next: ["(一调)field.update cascadeRepublish:true", "(分步)field.publish → form.draft(rebuild) → form.publish", "(只刷新表单)form.publish rebuild:true"] },
777
777
  refusalRecovery: [{ code: "capability-out-of-coverage", action: "readonlyFormula 必须是布尔 true;条件只读才用 readonlyCondition" }, ...commonRecovery],
778
778
  requiresEnvironmentDerivedIds: false,
779
779
  });
@@ -785,7 +785,7 @@ const formConditionalReadonly = Object.freeze({
785
785
  args: {
786
786
  environment,
787
787
  schemaCode: model,
788
- code: "approval_level",
788
+ code: "approvalLevel",
789
789
  name: "审批级别",
790
790
  options: { readonlyCondition: '{status} 等于 {"valueType":"specifyOption","value":"已通过"}' },
791
791
  },
@@ -793,7 +793,7 @@ const formConditionalReadonly = Object.freeze({
793
793
  conditionalRequirements: ["条件串协议同必填;恒只读改用 readonlyFormula:true"],
794
794
  priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认字段已发布" }],
795
795
  dryRun: { supported: true, command: `omc call field.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验条件串协议" },
796
- expectedReadback: { readback: { field: { code: "approval_level", options: { readonlyCondition: "<protocol>" } } }, next: ["(一调)field.update cascadeRepublish:true", "(分步)field.publish → form.draft(rebuild) → form.publish", "(只刷新表单)form.publish rebuild:true"] },
796
+ expectedReadback: { readback: { field: { code: "approvalLevel", options: { readonlyCondition: "<protocol>" } } }, next: ["(一调)field.update cascadeRepublish:true", "(分步)field.publish → form.draft(rebuild) → form.publish", "(只刷新表单)form.publish rebuild:true"] },
797
797
  refusalRecovery: [{ code: "capability-out-of-coverage", action: "使用中文或符号操作符与三段条件串" }, ...commonRecovery],
798
798
  requiresEnvironmentDerivedIds: false,
799
799
  });
@@ -1010,7 +1010,7 @@ const listViewConfigure = Object.freeze({
1010
1010
  title: "配置模型的默认列表视图(列/排序;默认视图已自动存在)",
1011
1011
  status: "requires-prior-readback",
1012
1012
  tool: "listview.configure",
1013
- args: { environment, schemaCode: model, fieldCodes: ["apply_no", "apply_amount", "apply_note"] },
1013
+ args: { environment, schemaCode: model, fieldCodes: ["applyNo", "applyAmount", "applyNote"] },
1014
1014
  preconditions: ["字段已 field.publish(视图与表单脚手架都依赖已发布字段)", "默认视图在 model.create 时由平台自动生成,code = schemaCode"],
1015
1015
  conditionalRequirements: [
1016
1016
  "**省略 `code` 即配置默认视图(code = schemaCode);不要先 listview.create——那会多出一个自定义视图。** 只有确实需要第二个视图时才 listview.create。",
@@ -1023,7 +1023,7 @@ const listViewConfigure = Object.freeze({
1023
1023
  { tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认字段已发布" },
1024
1024
  ],
1025
1025
  dryRun: { supported: true, command: `omc call listview.configure --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 预检 fieldCodes/形状,不写" },
1026
- expectedReadback: { readback: { code: "<schemaCode>", added: ["apply_no", "apply_amount"], columns: "…" }, next: ["data.list 验证读面"] },
1026
+ expectedReadback: { readback: { code: "<schemaCode>", added: ["applyNo", "applyAmount"], columns: "…" }, next: ["data.list 验证读面"] },
1027
1027
  refusalRecovery: [
1028
1028
  { code: "listview-columns-unknown-field", action: "字段未发布或名字写错;先 field.create + field.publish 或改引用" },
1029
1029
  { code: "listview-not-found", action: "默认视图应已存在;用 listview.list 确认 code,必要时 listview.create" },
@@ -1050,6 +1050,27 @@ const dataListFilter = Object.freeze({
1050
1050
  refusalRecovery: [{ code: "invalid-filter-DSL", action: "按错误里的 minimalValidFilter/acceptedOps 修正 op/field/value" }],
1051
1051
  requiresEnvironmentDerivedIds: false,
1052
1052
  });
1053
+ const dataDeleteSweep = Object.freeze({
1054
+ id: "sweep-all-readback",
1055
+ title: "清空模型数据并权威验证(list 取 ids → delete → 复验 total:0)",
1056
+ status: "offline-valid",
1057
+ tool: "data.delete",
1058
+ args: { environment, schemaCode: model, bizObjectIds: ["<data.list 逐条 id>"] },
1059
+ preconditions: [
1060
+ "先 data.list 取全量 ids(page 0 起、size 够大或翻页);**submit 后平台会同时保留 DRAFT 与 COMPLETED 两类行(r30 实证 5 次提交 = 10 行),清理对象按 data.list 全量**",
1061
+ ],
1062
+ conditionalRequirements: [
1063
+ "参数名是 `bizObjectIds`(数组)——不是 ids/bizObjectId(单条删除另有其参)",
1064
+ "id 已被删时幂等 no-op,重复清理安全",
1065
+ ],
1066
+ priorToolReferences: [
1067
+ { tool: "data.list", args: { environment, schemaCode: model }, purpose: "取全量 bizObjectIds(含 DRAFT 行)" },
1068
+ ],
1069
+ dryRun: { supported: true, command: `omc call data.delete --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 出删除计划不落库" },
1070
+ expectedReadback: { readback: { deleted: "N", modelResidue: { total: 0 } }, next: ["data.list 复验 total:0 / items:[](权威清空判据)"] },
1071
+ refusalRecovery: [{ code: "wire-business", action: "模型须已创建且有已发布字段;运行面 50000 时先 model.inspect modelPresent 判存在" }],
1072
+ requiresEnvironmentDerivedIds: false,
1073
+ });
1053
1074
  const dictionaryKeyValue = Object.freeze({
1054
1075
  id: "dictionary-key-value",
1055
1076
  title: "建一个键值字典并用其 id 作字段下拉来源",
@@ -1057,7 +1078,7 @@ const dictionaryKeyValue = Object.freeze({
1057
1078
  tool: "dictionary.save",
1058
1079
  args: {
1059
1080
  environment,
1060
- code: "visit_type",
1081
+ code: "visitType",
1061
1082
  name: "回访方式",
1062
1083
  dictionaryType: 0,
1063
1084
  records: [
@@ -1074,10 +1095,10 @@ const dictionaryKeyValue = Object.freeze({
1074
1095
  ],
1075
1096
  priorToolReferences: [
1076
1097
  { tool: "dictionary.names", args: { environment }, purpose: "查 code/name 冲突并读回既有 id(字典多时结果会溢成 artifact;只需 code/name,用 `artifact get <id> --keys` 或 --jq,别整份取)" },
1077
- { tool: "dictionary.get", args: { environment, code: "visit_type" }, purpose: "按 code 精确查存在性(小结果,优于 dictionary.names 全量)" },
1098
+ { tool: "dictionary.get", args: { environment, code: "visitType" }, purpose: "按 code 精确查存在性(小结果,优于 dictionary.names 全量)" },
1078
1099
  ],
1079
1100
  dryRun: { supported: true, command: `omc call dictionary.save --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 预检 code 冲突/形状,不写" },
1080
- expectedReadback: { performed: { errcode: 0 }, readback: { present: true, dictionary: { id: "<dictionary.id>", code: "visit_type", records: ["phone", "visit", "online"] } }, next: ["field.create --example dictionary-backed-single-select(用回读的 id)"] },
1101
+ expectedReadback: { performed: { errcode: 0 }, readback: { present: true, dictionary: { id: "<dictionary.id>", code: "visitType", records: ["phone", "visit", "online"] } }, next: ["field.create --example dictionary-backed-single-select(用回读的 id)"] },
1081
1102
  refusalRecovery: [
1082
1103
  { code: "dictionary-create-duplicate", action: "code 已存在:从 dictionary.names 取 id 后改传 id 做更新" },
1083
1104
  { code: "wire-business", action: "平台 `errcode -1 系统中已经存在相同名称的字典` = **字典名称全局唯一**(code 唯一还不够)——换一个 name(真机 2026-09-25:新 code + 旧 name 仍被拒)" },
@@ -1103,6 +1124,7 @@ export const EXECUTION_METADATA = Object.freeze({
1103
1124
  "method.update": Object.freeze({ recipes: [bizserviceSelfCallCorp], relatedTools: ["method.get", "method.create", "service.get"] }),
1104
1125
  "listview.configure": Object.freeze({ recipes: [listViewConfigure], relatedTools: ["listview.list", "listview.get", "field.list", "field.publish"] }),
1105
1126
  "data.list": Object.freeze({ recipes: [dataListFilter], relatedTools: ["data.load", "data.delete", "listview.configure", "field.list"] }),
1127
+ "data.delete": Object.freeze({ recipes: [dataDeleteSweep], relatedTools: ["data.list", "data.load", "data.submit"] }),
1106
1128
  });
1107
1129
  export function executionMetadataFor(tool) {
1108
1130
  return EXECUTION_METADATA[tool.name];
@@ -46,7 +46,7 @@ function intentGuidance() {
46
46
  why: "整包需求:用一份声明式意图文件描述 models/fields/form/listview/rules/workflow/onlinejs/services,由编译器合成平台原生结构并按依赖顺序执行;比逐能力拼装更省上下文、更不易漂移。",
47
47
  starter: `intentVersion: 1
48
48
  models:
49
- - code: <模型code> # 如 omc_todo_task
49
+ - code: <模型code> # 如 omcTodoTask(小写下划线也合规;字段码必须 lowerCamelCase ≤25 字符——产品实施规范)
50
50
  name: <业务名>
51
51
  fields:
52
52
  - {code: title, name: 标题, type: SHORT_TEXT}
@@ -56,7 +56,6 @@ models:
56
56
  listview: {fieldCodes: [title, level]}
57
57
  rules:
58
58
  - code: Create # 平台预置触发行(name 省略)
59
- name: 新增规则 # 仅自定义规则需要 name;预置行可省
60
59
  enabled: true
61
60
  actions:
62
61
  - when: [{field: amount, gt: 10}]
@@ -128,7 +128,10 @@ function phraseOf(tool, args, outcome) {
128
128
  case "form.draft":
129
129
  return `保存表单 ${stringArg(args, "sheetCode") ?? "?"}(模型 ${schemaCode ?? "?"})的草稿布局`;
130
130
  case "form.publish":
131
- return `发布表单 ${stringArg(args, "sheetCode") ?? "?"}(模型 ${schemaCode ?? "?"})`;
131
+ // r26 finding: sheetCode is optional (resolved from schemaCode for the
132
+ // default form) — the summary showed "?" when the agent omitted it.
133
+ const shownSheet = stringArg(args, "sheetCode") ?? (schemaCode !== undefined ? String(schemaCode) : "?");
134
+ return `发布表单 ${shownSheet}(模型 ${schemaCode ?? "?"}${args.rebuild === true ? ",重建后提拔" : ""})`;
132
135
  case "configure_model": {
133
136
  const fields = args.fields;
134
137
  const count = Array.isArray(fields) ? fields.length : stringArg(args, "fieldCode") ? 1 : 0;
@@ -82,7 +82,7 @@ export function intentCapabilityBlock() {
82
82
  "a rule declares actions (compact UPDATE) or nodeGraph, never both; non-UPDATE node types use nodeGraph",
83
83
  "escape hatch: uncovered capabilities stay available through omc call or a raw nodeGraph",
84
84
  ],
85
- example: 'intentVersion: 1\nmodels:\n - code: eval_m1\n name: 评测建模\n fields:\n - code: f_title\n name: 标题\n type: SHORT_TEXT\n - code: items\n name: 明细\n type: CHILD_TABLE\n subFields:\n - code: item_name\n name: 项目\n type: SHORT_TEXT\n form:\n publish: true\n',
85
+ example: 'intentVersion: 1\nmodels:\n - code: evalM1\n name: 评测建模\n fields:\n - code: fTitle\n name: 标题\n type: SHORT_TEXT\n - code: items\n name: 明细\n type: CHILD_TABLE\n subFields:\n - code: itemName\n name: 项目\n type: SHORT_TEXT\n form:\n publish: true\n',
86
86
  };
87
87
  }
88
88
  export function intentConfigRoot() {
@@ -29,14 +29,15 @@ export const RUNNABLE_EXAMPLES = Object.freeze([
29
29
  yaml: [
30
30
  "intentVersion: 1",
31
31
  "models:",
32
- " - code: ex_order",
32
+ " - code: exOrder",
33
33
  " name: 订单",
34
34
  " fields:",
35
- " - { code: order_no, name: 订单号, type: SHORT_TEXT }",
35
+ " - { code: orderNo, name: 订单号, type: SHORT_TEXT }",
36
36
  " - { code: amount, name: 金额, type: NUMERICAL, options: { scale: 2 } }",
37
37
  " - { code: level, name: 等级, type: RADIO, choices: [A, B, C] } # choices 只用于 RADIO/CHECKBOX/DROPDOWN_BOX/DROPDOWN_MULTI_BOX",
38
+ " # 字段 code 避开平台保留字(id/name/owner/creater/createdTime/modifiedTime/version/…)——撞保留字 offline 校验即拒(code_reserved);name 是字段中文名,不是 code(r31 实证 code:name 被拒后改 itemName 成功)",
38
39
  " form: { publish: true }",
39
- " listview: { fieldCodes: [order_no, amount, level] }",
40
+ " listview: { fieldCodes: [orderNo, amount, level] }",
40
41
  "",
41
42
  ].join("\n"),
42
43
  },
@@ -46,19 +47,19 @@ export const RUNNABLE_EXAMPLES = Object.freeze([
46
47
  yaml: [
47
48
  "intentVersion: 1",
48
49
  "models:",
49
- " - code: ex_supplier",
50
+ " - code: exSupplier",
50
51
  " name: 供应商",
51
52
  " fields:",
52
- " - { code: sup_name, name: 名称, type: SHORT_TEXT }",
53
+ " - { code: supName, name: 名称, type: SHORT_TEXT }",
53
54
  " form: { publish: true }",
54
- " - code: ex_contract",
55
+ " - code: exContract",
55
56
  " name: 合同",
56
57
  " fields:",
57
58
  " - code: supplier",
58
59
  " name: 供应商",
59
60
  " type: WORK_SHEET",
60
- " relativeCode: ex_supplier # 目标模型 code",
61
- " options: { displayField: sup_name } # 显示字段",
61
+ " relativeCode: exSupplier # 目标模型 code",
62
+ " options: { displayField: supName } # 显示字段",
62
63
  " form: { publish: true }",
63
64
  "",
64
65
  ].join("\n"),
@@ -69,12 +70,12 @@ export const RUNNABLE_EXAMPLES = Object.freeze([
69
70
  yaml: [
70
71
  "intentVersion: 1",
71
72
  "models:",
72
- " - code: ex_order",
73
+ " - code: exOrder",
73
74
  " name: 订单",
74
75
  " fields:",
75
76
  " - { code: amount, name: 金额, type: NUMERICAL, options: { scale: 2 } }",
76
77
  " - { code: status, name: 状态, type: SHORT_TEXT }",
77
- " - { code: supplier, name: 供应商, type: WORK_SHEET, relativeCode: ex_supplier }",
78
+ " - { code: supplier, name: 供应商, type: WORK_SHEET, relativeCode: exSupplier }",
78
79
  " rules:",
79
80
  " - code: Create",
80
81
  " name: 新增分级",
@@ -82,17 +83,17 @@ export const RUNNABLE_EXAMPLES = Object.freeze([
82
83
  " actions:",
83
84
  " # 同一规则内多个 action:按数组顺序编译为串行节点",
84
85
  " - type: UPDATE",
85
- " target: ex_order",
86
+ " target: exOrder",
86
87
  " when: [{ field: amount, gte: 100000 }] # 执行门:满足才执行(gte/lte 合法)",
87
88
  " set: [{ field: status, value: 大额 }] # 省略 filter -> 只改当前记录",
88
89
  " - type: UPDATE",
89
- " target: ex_order",
90
+ " target: exOrder",
90
91
  " when: [{ field: amount, lt: 100000 }]",
91
92
  " set: [{ field: status, value: 小额 }]",
92
93
  " - type: UPDATE",
93
- " target: ex_supplier # 跨模型回写",
94
+ " target: exSupplier # 跨模型回写",
94
95
  " filter: [{ field: id, from: supplier }] # 定位目标行:目标 id = 当前记录的 supplier",
95
- " set: [{ field: total_amount, from: amount }]",
96
+ " set: [{ field: totalAmount, from: amount }]",
96
97
  "",
97
98
  ].join("\n"),
98
99
  },
@@ -171,7 +171,7 @@ export function registerModelLane(tools) {
171
171
  const codes = args.schemaCodes && args.schemaCodes.length > 0 ? args.schemaCodes : args.schemaCode ? [args.schemaCode] : [];
172
172
  if (codes.length === 0)
173
173
  throw new ToolInputError({ status: "error", blocker: "tool-input-invalid", tool: "model.inspect", message: "需要 schemaCode(单个)或 schemaCodes(数组,批量)" });
174
- const face = async (name, toolArgs) => {
174
+ const face = async (name, toolArgs, errcodes) => {
175
175
  const tool = siblings.find((entry) => entry.name === name);
176
176
  if (!tool)
177
177
  return undefined;
@@ -184,17 +184,24 @@ export function registerModelLane(tools) {
184
184
  const result = (await withoutReadbackCache(ctx, async () => (await tool.handler(ctx, parsed, tool))));
185
185
  return result;
186
186
  }
187
- catch {
187
+ catch (error) {
188
+ // r30: record the wire errcode so inspectOne can turn the field
189
+ // face's 300004 into a `modelPresent:false` verdict instead of an
190
+ // all-empty aggregate that reads as "registered, no fields".
191
+ const errcode = error.errcode;
192
+ if (errcodes && typeof errcode === "number")
193
+ errcodes.set(name, errcode);
188
194
  return undefined; // a single face being unreachable must not sink the whole inspect
189
195
  }
190
196
  };
191
197
  const inspectOne = async (schemaCode) => {
198
+ const faceErrcodes = new Map();
192
199
  // F5: the six read faces are INDEPENDENT wire reads; fan them out in
193
200
  // parallel (bounded) instead of paying six sequential round-trips. Only
194
201
  // workflow.get_published stays chained (it needs workflowCode from the
195
202
  // draft face). Output ordering is unchanged (results land by index).
196
203
  const [fieldFace, formFace, listviewFace, ruleFace, dataruleFace, draftFace] = await mapBounded([
197
- () => face("field.list", { schemaCode, published: true, ...(all ? { all: true } : {}) }),
204
+ () => face("field.list", { schemaCode, published: true, ...(all ? { all: true } : {}) }, faceErrcodes),
198
205
  () => face("form.get", { schemaCode }),
199
206
  () => face("listview.get", { schemaCode }),
200
207
  () => face("rule.list", { schemaCode }),
@@ -245,15 +252,29 @@ export function registerModelLane(tools) {
245
252
  activities: Array.isArray(summary?.activities) ? summary.activities.map((activity) => ({ code: activity.code, type: activity.type, name: activity.name, participant: activity.participant })) : [],
246
253
  edges: summary?.edges,
247
254
  };
255
+ // r30 verdict: field face ok ⇒ model exists. The wire refusal for a
256
+ // missing model is a RETURNED payload (status:"refused", errcode:300004
257
+ // 数据模型不存在), not a throw — read both signals (returned refusal +
258
+ // thrown WireError) before deciding; unreachable face ⇒ undefined.
259
+ const fieldRefused = fieldFace !== undefined && fieldFace.status === "refused";
260
+ const fieldRefusalErrcode = fieldRefused ? Number(fieldFace.errcode) : undefined;
261
+ const absent = fieldRefusalErrcode === 300004 || faceErrcodes.get("field.list") === 300004;
262
+ const modelPresent = absent ? false : fieldFace !== undefined && !fieldRefused ? true : undefined;
248
263
  return {
249
264
  schemaCode,
265
+ ...(modelPresent !== undefined ? { modelPresent } : {}),
266
+ ...(modelPresent === false
267
+ ? {
268
+ existenceHint: "模型不存在(field.list 300004)——先 model.create / configure_model 再挂字段;勿把空聚合读成『已存在但无字段』(r30 实证该误读导致 field.create 300004 失败往返)",
269
+ }
270
+ : {}),
250
271
  fields,
251
272
  form,
252
273
  listview,
253
274
  rules,
254
275
  datarules,
255
276
  workflow,
256
- hint: "model.inspect 聚合设计时六面(fields/form/listview/rules/datarules/workflow,摘要)。字段 options 细节用 field.list --code;规则图用 rule.get schemaCode+ruleCode;运行数据用 data.list(不在此面)。",
277
+ hint: "model.inspect 聚合设计时六面(fields/form/listview/rules/datarules/workflow,摘要)。modelPresent 是存在性判据(field 面 300004=false);字段 options 细节用 field.list --code;规则图用 rule.get schemaCode+ruleCode;运行数据用 data.list(不在此面)。",
257
278
  };
258
279
  };
259
280
  const one = await inspectOne(codes[0]);
@@ -434,6 +434,26 @@ export function registerRuntimeDataLane(tools) {
434
434
  if (error instanceof WireError && error.kind === "business" && error.errcode === RUNTIME_DATA_WIRE.emptyModelErrcode) {
435
435
  return { schemaCode: args.schemaCode, total: 0, totalPages: 0, page: args.page ?? 0, size: args.size ?? 10, items: [] };
436
436
  }
437
+ // r30: a MISSING model answers this face with a generic 50000
438
+ // 服务异常 — indistinguishable from a real outage. One field-face
439
+ // probe disambiguates: its 300004 (数据模型不存在) turns the
440
+ // refusal into an existence verdict so the caller creates instead
441
+ // of retrying; any other outcome rethrows the original 50000.
442
+ if (error instanceof WireError && error.kind === "business" && error.errcode === 50000) {
443
+ try {
444
+ await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: "true" } });
445
+ }
446
+ catch (probeError) {
447
+ if (probeError instanceof WireError && probeError.kind === "business" && probeError.errcode === 300004) {
448
+ return {
449
+ status: "refused",
450
+ blocker: "wire-business",
451
+ errcode: 300004,
452
+ message: `数据模型不存在(${args.schemaCode})——运行面读取前模型必须已创建并发布字段;先 model.create / configure_model(存在性判据用 model.inspect 的 modelPresent)`,
453
+ };
454
+ }
455
+ }
456
+ }
437
457
  throw error;
438
458
  }
439
459
  const page = extractBizObjectPage(result.data);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stalfh233/omc-cli",
3
- "version": "0.4.31",
3
+ "version": "0.4.33",
4
4
  "description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "commander": "14.0.3",
54
- "omc-core": "0.4.31",
54
+ "omc-core": "0.4.33",
55
55
  "proper-lockfile": "4.1.2",
56
56
  "write-file-atomic": "6.0.0",
57
57
  "zod": "^3.23.8",
@@ -55,13 +55,13 @@ node "$CLI" help omc-five-piece-flow
55
55
  ```json
56
56
  {
57
57
  "schemaCode": "expense",
58
- "code": "expense_items",
58
+ "code": "expenseItems",
59
59
  "name": "费用明细",
60
60
  "propertyType": "CHILD_TABLE",
61
61
  "subFields": [
62
- { "code": "item_name", "name": "费用项目", "propertyType": "SHORT_TEXT" },
63
- { "code": "item_amount", "name": "金额", "propertyType": "NUMERICAL" },
64
- { "code": "item_date", "name": "发生日期", "propertyType": "DATE" }
62
+ { "code": "itemName", "name": "费用项目", "propertyType": "SHORT_TEXT" },
63
+ { "code": "itemAmount", "name": "金额", "propertyType": "NUMERICAL" },
64
+ { "code": "itemDate", "name": "发生日期", "propertyType": "DATE" }
65
65
  ]
66
66
  }
67
67
  ```
@@ -70,9 +70,9 @@ node "$CLI" help omc-five-piece-flow
70
70
 
71
71
  ```bash
72
72
  # --env 在单环境项目(.omc-env 在位)可省;显式写出仅为多环境演示
73
- omc call field.create --env local --args '{"schemaCode":"expense_items","code":"item_note","name":"明细备注","propertyType":"LONG_TEXT"}'
74
- omc call field.update --env local --args '{"schemaCode":"expense_items","code":"item_note","name":"备注"}'
75
- omc call field.delete --env local --args '{"schemaCode":"expense_items","code":"item_note"}'
73
+ omc call field.create --env local --args '{"schemaCode":"expenseItems","code":"itemNote","name":"明细备注","propertyType":"LONG_TEXT"}'
74
+ omc call field.update --env local --args '{"schemaCode":"expenseItems","code":"itemNote","name":"备注"}'
75
+ omc call field.delete --env local --args '{"schemaCode":"expenseItems","code":"itemNote"}'
76
76
  omc call field.publish --env local --args '{"schemaCode":"expense"}'
77
77
  ```
78
78
 
@@ -85,14 +85,14 @@ models:
85
85
  - code: expense
86
86
  name: 费用报销单
87
87
  fields:
88
- - code: expense_items
88
+ - code: expenseItems
89
89
  name: 费用明细
90
90
  type: CHILD_TABLE
91
91
  subFields:
92
- - code: item_name
92
+ - code: itemName
93
93
  name: 费用项目
94
94
  type: SHORT_TEXT
95
- - code: item_amount
95
+ - code: itemAmount
96
96
  name: 金额
97
97
  type: NUMERICAL
98
98
  ```
@@ -23,7 +23,7 @@ be treated as a second source. Read this, then follow the topics it names.
23
23
  3. 环境名以 `omc doctor` 的「环境=」或 `.omc-env` 为准;工具不确定先用默认 `describe`/`--example`,只有单个工具缺形状才用 `--full`;`describe` 带 `exampleHint`/`recipeHint` 时优先 `--example`。
24
24
  4. 只有工具不在 `next` 命中能力内、或要看 purpose/枚举时,才补一次 `commands --json --compact --lane <lane>`。非显然形状(规则图、流程模板、表单布局、RESTFUL)才单独 `describe --example`。
25
25
  5. 键名按对象不同:模型 `schemaCode`、默认表单 `sheetCode`(可省略,默认 == 模型 code)、流程 `workflowCode`/`code`——报错会提示同义键;字段族名认不出会给「最接近的族」。
26
- 6. **写操作串行**:同一环境不要并发写;`write-lock-held` 是瞬态,等几秒重试一次即可。
26
+ 6. **写操作串行**:同一环境不要并发写(含多个 `omc call` 并行进程——环境写锁互斥,r31 实证并行 field.create 2/3 被拒);多个写合并为一条 `omc script` 逐行跑(进程内自动串行);`write-lock-held` 是瞬态,等几秒重试一次即可。
27
27
  7. **单个对象优先高阶编排工具**:模型+字段用 `configure_model`(一次含全部字段),表单用 `configure_form`,列表用 `listview.configure`(**默认视图已存在,省略 code 即配置它,不要 `listview.create`**)。原子工具留给单点修改。
28
28
  8. 拿到 `detailArtifactId` 先 `artifact get <id> --summary` 看结构,再 `--keys/--jq` 取所需;`field.list`/`workflow.*` 默认面已给清单与节点摘要;`workflow.get_draft` 默认面已含 `edges`,改流程只需传 `activities`+`routes`。
29
29
  9. 每个 section 读一次即可;同一 topic 不要反复读。