@stalfh233/omc-cli 0.6.7 → 0.6.10

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 (67) hide show
  1. package/README.md +2 -1
  2. package/dist/approval-flow.js +5 -3
  3. package/dist/bizservice-config.js +26 -10
  4. package/dist/call.js +47 -0
  5. package/dist/capability-overview.js +47 -24
  6. package/dist/cli-contract.js +73 -26
  7. package/dist/cli-output.js +41 -2
  8. package/dist/cli-stdout.js +74 -0
  9. package/dist/cli.js +135 -52
  10. package/dist/command-transport.js +8 -1
  11. package/dist/config-store.js +17 -2
  12. package/dist/discovery.js +62 -3
  13. package/dist/field-families.js +35 -6
  14. package/dist/goal-routes.js +118 -11
  15. package/dist/human-summary.js +11 -0
  16. package/dist/lanes/app-contract.js +12 -5
  17. package/dist/lanes/app.js +221 -134
  18. package/dist/lanes/apply-changes.js +2 -1
  19. package/dist/lanes/b2-registration-contract.js +3 -3
  20. package/dist/lanes/b2-registration.js +890 -693
  21. package/dist/lanes/businessrule.js +75 -51
  22. package/dist/lanes/custom-page.js +166 -115
  23. package/dist/lanes/datarule-contract.js +2 -2
  24. package/dist/lanes/datarule.js +238 -154
  25. package/dist/lanes/dictionary.js +253 -110
  26. package/dist/lanes/environment.js +57 -9
  27. package/dist/lanes/field.js +160 -107
  28. package/dist/lanes/folder-contract.js +7 -3
  29. package/dist/lanes/folder.js +249 -118
  30. package/dist/lanes/form-contract.js +1 -1
  31. package/dist/lanes/form.js +188 -64
  32. package/dist/lanes/list-view.js +405 -369
  33. package/dist/lanes/meta.js +31 -6
  34. package/dist/lanes/model-contract.js +1 -1
  35. package/dist/lanes/model.js +610 -512
  36. package/dist/lanes/online-js-contract.js +1 -1
  37. package/dist/lanes/online-js.js +160 -146
  38. package/dist/lanes/organization.js +905 -640
  39. package/dist/lanes/programming.js +274 -136
  40. package/dist/lanes/report-contract.js +2 -2
  41. package/dist/lanes/report.js +354 -198
  42. package/dist/lanes/rule-lifecycle.js +294 -140
  43. package/dist/lanes/runtime-data-contract.js +4 -4
  44. package/dist/lanes/runtime-data.js +869 -745
  45. package/dist/lanes/workflow-contract.js +3 -3
  46. package/dist/lanes/workflow.js +732 -605
  47. package/dist/model-scaffold.js +26 -5
  48. package/dist/path-projection.js +196 -14
  49. package/dist/plan.js +25 -0
  50. package/dist/programming-workflow.js +55 -3
  51. package/dist/read-shapes.js +146 -0
  52. package/dist/read.js +40 -9
  53. package/dist/recipes/businessrule.js +74 -24
  54. package/dist/recipes/dictionary.js +7 -4
  55. package/dist/recipes/form.js +46 -0
  56. package/dist/recipes/runtime-data.js +3 -2
  57. package/dist/refusal-guidance.js +29 -5
  58. package/dist/rule-support.js +14 -1
  59. package/dist/rule-when.js +2 -2
  60. package/dist/tool-types.js +9 -3
  61. package/dist/version-family-gaps.js +101 -0
  62. package/dist/write.js +2 -1
  63. package/package.json +6 -4
  64. package/skills/omc-glossary.md +13 -0
  65. package/skills/omc-refusals.md +13 -5
  66. package/skills/omc-tool-selection.md +2 -1
  67. package/skills/omc-write-hazards.md +5 -2
@@ -13,6 +13,7 @@ import { runtimeWriteWithAppMapping } from "../runtime-support.js";
13
13
  import { DATARULE_LANE_CONTRACT, DATARULE_TYPE_OPTION_KEYS } from "./datarule-contract.js";
14
14
  import { normalizeAggregationFormula } from "../field-families.js";
15
15
  import { CONDITION_PROTOCOL_EXAMPLES, dataRuleOptionsShapeError } from "./datarule-protocol.js";
16
+ import { readModelRecord } from "../model-record.js";
16
17
  const DATARULE_LANE = DATARULE_LANE_CONTRACT.lane;
17
18
  const DATARULE_TOOLS = DATARULE_LANE_CONTRACT.tools;
18
19
  const dataRuleTypeArg = z
@@ -20,7 +21,13 @@ const dataRuleTypeArg = z
20
21
  .int()
21
22
  .min(1)
22
23
  .max(5)
23
- .describe("field-level rule type: 1 校验 / 2 计算拼接 / 3 显示 / 4 必填 / 5 只读 (8 EXTENSION is out of scope)");
24
+ .describe("field-level rule type → options 键映射:" +
25
+ "1 校验 → options.conditions/prompt/regexp/maxLength…(配合 checkType 1/2/3/4/5);" +
26
+ "2 计算拼接 → options.shortTextStitch|expression(须为含 formula 的 JSON 对象字符串);" +
27
+ "3 显示 → options.displayFormula;" +
28
+ "4 必填 → options.requiredFormula(无条件必填用字面量 \"true\";条件式如 \"{amount} 大于 {\\\"valueType\\\":\\\"custom\\\",\\\"value\\\":0}\");" +
29
+ "5 只读 → options.readonlyCondition。" +
30
+ "8 EXTENSION 不在范围。表单运行时条件优先 field.update(见 datarule.create summary)。");
24
31
  const dataRuleOptionsArg = z
25
32
  .record(z.unknown())
26
33
  .describe("rule options object, sent to the wire as a JSON string; type4 requires requiredFormula, type5 readonlyCondition, type3 displayFormula, type2 shortTextStitch|expression, type1 a check key keyed by checkType");
@@ -741,166 +748,243 @@ export function registerDataRuleLane(tools) {
741
748
  capability: capabilityArg,
742
749
  },
743
750
  handler: async (ctx, args, self) => governedRead(ctx, self, args, async (session) => {
751
+ // r28g F32:与 rule.list 同口径——模型不存在时 ok + modelPresent:false,
752
+ // 不把平台 601003 原样 refused(三种读面反应不一致)。
753
+ const modelRecord = await readModelRecord(session, args.schemaCode);
754
+ if (modelRecord === null) {
755
+ return {
756
+ schemaCode: args.schemaCode,
757
+ dataRuleType: args.dataRuleType ?? null,
758
+ modelPresent: false,
759
+ rules: [],
760
+ rulesCount: 0,
761
+ hint: "模型不存在(bizmodels/get_all 空)——空规则不是「已清干净的证据」也不是错误;判定模型面用 model.inspect",
762
+ };
763
+ }
744
764
  const rows = await listDataRules(session, args.schemaCode, args.dataRuleType);
745
765
  return {
746
766
  schemaCode: args.schemaCode,
747
767
  dataRuleType: args.dataRuleType ?? null,
768
+ modelPresent: true,
769
+ rulesCount: rows.length,
748
770
  rules: rows.map(decodeDataRule),
749
771
  };
772
+ }, {
773
+ target: `datarule.list:${args.schemaCode}:${args.dataRuleType ?? "*"}`,
774
+ // r28g F12:默认 compact 把 BULKY `rules` 折成 rulesCount + rules:[],
775
+ // 6 条带 options 就触发「数据丢失假象」。自定义 compact 保形为库存摘要
776
+ // (id/type/property/checkType/name + optionsKeys),完整 options 仍在
777
+ // detailArtifactId。
778
+ compact: (result) => {
779
+ const rules = Array.isArray(result.rules) ? result.rules : [];
780
+ const inventory = rules.map((row) => {
781
+ const options = row.options && typeof row.options === "object" && !Array.isArray(row.options)
782
+ ? row.options
783
+ : null;
784
+ return {
785
+ id: row.id ?? null,
786
+ propertyCode: row.propertyCode ?? null,
787
+ propertyCodes: row.propertyCodes ?? [],
788
+ dataRuleType: row.dataRuleType ?? null,
789
+ checkType: row.checkType ?? null,
790
+ name: row.name ?? null,
791
+ ...(options ? { optionsKeys: Object.keys(options) } : {}),
792
+ ...(typeof row.shapeWarning === "string" ? { shapeWarning: row.shapeWarning } : {}),
793
+ };
794
+ });
795
+ return {
796
+ schemaCode: result.schemaCode,
797
+ dataRuleType: result.dataRuleType ?? null,
798
+ modelPresent: result.modelPresent,
799
+ rulesCount: typeof result.rulesCount === "number" ? result.rulesCount : inventory.length,
800
+ rules: inventory,
801
+ rulesOmitted: true,
802
+ rulesOmittedNote: "内联 rules 是库存摘要(无 options 正文);完整行在 detailArtifactId——`omc artifact get <id> --jq 'rules'` 或本次 call 加 `--jq rules` / `--extract rules`。",
803
+ ...(typeof result.hint === "string" ? { hint: result.hint } : {}),
804
+ };
805
+ },
750
806
  }),
751
807
  }));
752
- writeTool(tools, "datarule.create", DATARULE_LANE, DATARULE_TOOLS["datarule.create"].scope, DATARULE_TOOLS["datarule.create"].description, {
753
- schemaCode: z.string().min(1),
754
- propertyCode: propertyCodeArg,
755
- propertyCodes: propertyCodesArg,
756
- dataRuleType: dataRuleTypeArg,
757
- options: dataRuleOptionsArg,
758
- checkType: checkTypeArg,
759
- childRule: z.boolean().optional(),
760
- name: z.string().optional(),
761
- sortKey: z.number().int().optional(),
762
- }, async (session, args) => {
763
- assertDataRuleOptions(args.dataRuleType, args.options, args.checkType);
764
- assertDataRuleCheckType(args.dataRuleType, args.checkType);
765
- assertDataRuleTarget(args);
766
- const codes = dataRuleTargetCodes(args);
767
- const published = await readPublishedFieldCodesOrNull(session, args.schemaCode);
768
- await assertChildFormulaRefs(session, args, published);
769
- if (published !== null) {
770
- const missing = codes.filter((code) => !published.has(code));
771
- if (missing.length > 0)
772
- throw new ToolRefusalError({ status: "refused", blocker: "datarule-property-not-published", schemaCode: args.schemaCode, propertyCodes: codes, missing, message: `字段 ${args.schemaCode}.${missing.join(",")} 不在发布面上;拒绝 datarule.create POST` });
773
- }
774
- const existingRules = await listDataRules(session, args.schemaCode);
775
- // dataRuleType 1 (CHECK_RULE) fans out into 5 checkTypes that COEXIST on one
776
- // field (the platform auto-creates a checkType 4 maxLength row per text
777
- // field), so the clash key there is field + `checkType`, not field + type.
778
- if (existingRules.some((row) => {
779
- if (Number(row.dataRuleType) !== args.dataRuleType)
780
- return false;
781
- if (!storedTargetCodes(row).some((code) => codes.includes(code)))
782
- return false;
783
- return args.dataRuleType === 1 ? (row.checkType ?? null) === (args.checkType ?? null) : true;
784
- }))
785
- throw new ToolRefusalError({ status: "refused", blocker: "datarule-duplicate-property-type", schemaCode: args.schemaCode, propertyCodes: codes, dataRuleType: args.dataRuleType, ...(args.dataRuleType === 1 ? { checkType: args.checkType } : {}), message: `字段 ${args.schemaCode}.${codes.join(",")} 已存在同类型数据规则${args.dataRuleType === 1 ? `(checkType ${args.checkType})` : ""};拒绝重复 POST` });
786
- const createRule = () => wireCall(session, {
787
- path: ENDPOINTS.dataRuleCreate,
788
- method: "POST",
789
- body: dataRuleBody(args, JSON.stringify(withOptionsPropertyCodes(withStitchEditorText(args.options), codes))),
790
- });
791
- // Ticket 78: ONLY type2 CALCULATE has the corroborated unpublished-app
792
- // FALSE dead-cycle 305003 (checkCalculateRulesDeadCycle mis-concatenates
793
- // schemaCode.propertyCode when getAppFunctionByCode is null); types
794
- // 3/4/5 keep the plain wire failure.
795
- const performed = args.dataRuleType === 2
796
- ? await runtimeWriteWithAppMapping(session, args.schemaCode, 305003, "datarule.create 类型2 CALCULATE 报死循环(305003,误判)", createRule)
797
- : await createRule();
798
- const rules = (await listDataRules(session, args.schemaCode, args.dataRuleType)).filter((row) => storedTargetCodes(row).some((code) => codes.includes(code)));
799
- return {
800
- performed: { errcode: performed.errcode },
801
- readback: {
802
- schemaCode: args.schemaCode,
803
- propertyCodes: codes,
804
- dataRuleType: args.dataRuleType,
805
- matchedCount: rules.length,
806
- rules: rules.map(decodeDataRule),
807
- ...(args.dataRuleType >= 3 ? { advisory: DATARULE_FORM_INTERACTION_ADVISORY } : {}),
808
- // r35(2026-10-31 真机):type1 校验规则建好读回一致,但 REST `data.submit`
809
- // 提交违规数据(-50 / 超阈值)errcode 0 放行、原值落库——校验在门户前端
810
- // 提交管线执行,REST 不拦。先告知执行面,省掉「先实测两轮才发现不生效」。
811
- ...(args.dataRuleType === 1
812
- ? { executionWarning: "校验规则在门户前端提交时执行;REST `data.submit` 不拦截违规数据(r35 真机:-50 与超阈值均 errcode 0 放行)。验收拦截效果需走门户,或改用业务规则(BIZ_ACTION/条件)在服务端拦。" }
813
- : {}),
814
- ...(() => { const w = dataRuleFormulaWarnings(args.dataRuleType, args.options); return w.length ? { lintWarnings: w } : {}; })(),
815
- },
816
- };
817
- },
818
- // T-04: the type-2 shape gate is static — refuse BEFORE the write permission gate.
819
- (args) => {
820
- assertDataRuleTarget(args);
821
- assertDataRuleOptions(args.dataRuleType, args.options, args.checkType);
822
- assertDataRuleCheckType(args.dataRuleType, args.checkType);
823
- },
824
- // Dry-run is the cheapest place to see the formula lint (the platform
825
- // validates nothing at create time; the failure surfaces at submit).
826
- async (_session, args) => {
827
- const lintWarnings = dataRuleFormulaWarnings(args.dataRuleType, args.options);
828
- return lintWarnings.length ? { lintWarnings } : {};
829
- }, dataruleRollbackSpec("datarule.create"));
830
- writeTool(tools, "datarule.update", DATARULE_LANE, DATARULE_TOOLS["datarule.update"].scope, DATARULE_TOOLS["datarule.update"].description, {
831
- id: z.string().min(1).optional().describe("数据规则记录 id;省略时可改用业务键 schemaCode + propertyCode(+dataRuleType) 定位"),
832
- schemaCode: z.string().min(1),
833
- propertyCode: propertyCodeArg,
834
- propertyCodes: propertyCodesArg,
835
- dataRuleType: dataRuleTypeArg,
836
- options: dataRuleOptionsArg,
837
- checkType: checkTypeArg,
838
- childRule: z.boolean().optional(),
839
- name: z.string().optional(),
840
- sortKey: z.number().int().optional(),
841
- }, async (session, args) => {
842
- assertDataRuleOptions(args.dataRuleType, args.options, args.checkType);
843
- assertDataRuleCheckType(args.dataRuleType, args.checkType);
844
- const id = await locateDataRuleId(session, args, "update POST");
845
- const before = await listDataRules(session, args.schemaCode);
846
- const current = before.find((row) => row.id === id);
847
- if (!current)
848
- throw new ToolRefusalError({ status: "refused", blocker: "datarule-absent", message: `数据规则 ${id} 不存在于 ${args.schemaCode};拒绝 update POST` });
849
- // The stored childRule governs when the caller omits it (full-row update).
850
- const patchArgs = { ...args, id };
851
- await assertChildFormulaRefs(session, { ...patchArgs, childRule: args.childRule ?? current.childRule === true });
852
- // Keep the existing binding when the caller omits a target (the platform
853
- // update is a full-row overwrite — an empty patch would clear propertyCodes).
854
- const requested = dataRuleTargetCodes(patchArgs);
855
- const codes = requested.length > 0 ? requested : storedTargetCodes(current);
856
- const boundArgs = { ...patchArgs, propertyCode: codes.length === 1 ? codes[0] : undefined, propertyCodes: codes };
857
- if (before.some((row) => {
858
- if (row.id === id)
859
- return false;
860
- if (Number(row.dataRuleType) !== args.dataRuleType)
861
- return false;
862
- if (!storedTargetCodes(row).some((code) => codes.includes(code)))
863
- return false;
864
- return args.dataRuleType === 1 ? (row.checkType ?? null) === (args.checkType ?? null) : true;
865
- }))
866
- throw new ToolRefusalError({ status: "refused", blocker: "datarule-duplicate-property-type", message: `字段 ${args.schemaCode}.${codes.join(",")} 已存在同类型数据规则${args.dataRuleType === 1 ? `(checkType ${args.checkType})` : ""};拒绝 update POST` });
867
- const beforeDecoded = decodeDataRule(current);
868
- const performed = await wireCall(session, {
869
- path: ENDPOINTS.dataRuleUpdate,
870
- method: "POST",
871
- body: fullDataRuleBody(current, boundArgs, JSON.stringify(withOptionsPropertyCodes(withStitchEditorText(args.options), codes))),
872
- });
873
- const record = (await listDataRules(session, args.schemaCode, args.dataRuleType)).find((row) => row.id === id);
874
- if (!record)
875
- throw new WireError("business", `data rule ${id} disappeared after update`, 304001);
876
- const afterDecoded = decodeDataRule(record);
877
- const proof = dataRuleChanges(beforeDecoded, afterDecoded);
878
- return {
879
- performed: { errcode: performed.errcode },
880
- readback: { id, present: true, before: beforeDecoded, after: afterDecoded, changedFields: proof.changedFields, unexpectedChanges: proof.unexpectedChanges, rule: afterDecoded },
881
- };
882
- }, (args) => {
883
- assertDataRuleOptions(args.dataRuleType, args.options, args.checkType);
884
- }, undefined, dataruleRollbackSpec("datarule.update"));
885
- writeTool(tools, "datarule.delete", DATARULE_LANE, DATARULE_TOOLS["datarule.delete"].scope, DATARULE_TOOLS["datarule.delete"].description, {
886
- id: z.string().min(1).optional().describe("数据规则记录 id;省略时可改用业务键 schemaCode + propertyCode(+dataRuleType) 定位"),
887
- schemaCode: z.string().min(1),
888
- propertyCode: propertyCodeArg,
889
- propertyCodes: propertyCodesArg,
890
- dataRuleType: z.number().int().min(1).max(5).optional().describe("业务键定位去歧义:同一字段可能有多条规则(如必填+只读),用它指定要删的类型"),
891
- }, async (session, args) => {
892
- const id = await locateDataRuleId(session, args, "delete POST");
893
- const before = (await listDataRules(session, args.schemaCode)).find((row) => row.id === id);
894
- const performed = await wireCall(session, {
895
- path: ENDPOINTS.dataRuleDelete,
896
- method: "POST",
897
- query: { id },
898
- });
899
- const after = await listDataRules(session, args.schemaCode);
900
- return {
901
- performed: { errcode: performed.errcode },
902
- readback: { id, existedBefore: before !== undefined, absent: !after.some((row) => row.id === id) },
903
- };
904
- }, undefined, undefined, dataruleRollbackSpec("datarule.delete"));
808
+ writeTool({
809
+ tools,
810
+ name: "datarule.create",
811
+ lane: DATARULE_LANE,
812
+ scope: DATARULE_TOOLS["datarule.create"].scope,
813
+ description: DATARULE_TOOLS["datarule.create"].description,
814
+ inputSchema: {
815
+ schemaCode: z.string().min(1),
816
+ propertyCode: propertyCodeArg,
817
+ propertyCodes: propertyCodesArg,
818
+ dataRuleType: dataRuleTypeArg,
819
+ options: dataRuleOptionsArg,
820
+ checkType: checkTypeArg,
821
+ childRule: z.boolean().optional(),
822
+ name: z.string().optional(),
823
+ sortKey: z.number().int().optional(),
824
+ },
825
+ execute: async (session, args) => {
826
+ assertDataRuleOptions(args.dataRuleType, args.options, args.checkType);
827
+ assertDataRuleCheckType(args.dataRuleType, args.checkType);
828
+ assertDataRuleTarget(args);
829
+ const codes = dataRuleTargetCodes(args);
830
+ const published = await readPublishedFieldCodesOrNull(session, args.schemaCode);
831
+ await assertChildFormulaRefs(session, args, published);
832
+ if (published !== null) {
833
+ const missing = codes.filter((code) => !published.has(code));
834
+ if (missing.length > 0)
835
+ throw new ToolRefusalError({ status: "refused", blocker: "datarule-property-not-published", schemaCode: args.schemaCode, propertyCodes: codes, missing, message: `字段 ${args.schemaCode}.${missing.join(",")} 不在发布面上;拒绝 datarule.create POST` });
836
+ }
837
+ const existingRules = await listDataRules(session, args.schemaCode);
838
+ // dataRuleType 1 (CHECK_RULE) fans out into 5 checkTypes that COEXIST on one
839
+ // field (the platform auto-creates a checkType 4 maxLength row per text
840
+ // field), so the clash key there is field + `checkType`, not field + type.
841
+ if (existingRules.some((row) => {
842
+ if (Number(row.dataRuleType) !== args.dataRuleType)
843
+ return false;
844
+ if (!storedTargetCodes(row).some((code) => codes.includes(code)))
845
+ return false;
846
+ return args.dataRuleType === 1 ? (row.checkType ?? null) === (args.checkType ?? null) : true;
847
+ }))
848
+ throw new ToolRefusalError({ status: "refused", blocker: "datarule-duplicate-property-type", schemaCode: args.schemaCode, propertyCodes: codes, dataRuleType: args.dataRuleType, ...(args.dataRuleType === 1 ? { checkType: args.checkType } : {}), message: `字段 ${args.schemaCode}.${codes.join(",")} 已存在同类型数据规则${args.dataRuleType === 1 ? `(checkType ${args.checkType})` : ""};拒绝重复 POST` });
849
+ const createRule = () => wireCall(session, {
850
+ path: ENDPOINTS.dataRuleCreate,
851
+ method: "POST",
852
+ body: dataRuleBody(args, JSON.stringify(withOptionsPropertyCodes(withStitchEditorText(args.options), codes))),
853
+ });
854
+ // Ticket 78: ONLY type2 CALCULATE has the corroborated unpublished-app
855
+ // FALSE dead-cycle 305003 (checkCalculateRulesDeadCycle mis-concatenates
856
+ // schemaCode.propertyCode when getAppFunctionByCode is null); types
857
+ // 3/4/5 keep the plain wire failure.
858
+ const performed = args.dataRuleType === 2
859
+ ? await runtimeWriteWithAppMapping(session, args.schemaCode, 305003, "datarule.create 类型2 CALCULATE 报死循环(305003,误判)", createRule)
860
+ : await createRule();
861
+ const rules = (await listDataRules(session, args.schemaCode, args.dataRuleType)).filter((row) => storedTargetCodes(row).some((code) => codes.includes(code)));
862
+ return {
863
+ performed: { errcode: performed.errcode },
864
+ readback: {
865
+ schemaCode: args.schemaCode,
866
+ propertyCodes: codes,
867
+ dataRuleType: args.dataRuleType,
868
+ matchedCount: rules.length,
869
+ rules: rules.map(decodeDataRule),
870
+ ...(args.dataRuleType >= 3 ? { advisory: DATARULE_FORM_INTERACTION_ADVISORY } : {}),
871
+ // r35(2026-10-31 真机):type1 校验规则建好读回一致,但 REST `data.submit`
872
+ // 提交违规数据(-50 / 超阈值)errcode 0 放行、原值落库——校验在门户前端
873
+ // 提交管线执行,REST 不拦。先告知执行面,省掉「先实测两轮才发现不生效」。
874
+ ...(args.dataRuleType === 1
875
+ ? { executionWarning: "校验规则在门户前端提交时执行;REST `data.submit` 不拦截违规数据(r35 真机:-50 与超阈值均 errcode 0 放行)。验收拦截效果需走门户,或改用业务规则(BIZ_ACTION/条件)在服务端拦。" }
876
+ : {}),
877
+ ...(() => { const w = dataRuleFormulaWarnings(args.dataRuleType, args.options); return w.length ? { lintWarnings: w } : {}; })(),
878
+ },
879
+ };
880
+ },
881
+ preflight: // T-04: the type-2 shape gate is static — refuse BEFORE the write permission gate.
882
+ (args) => {
883
+ assertDataRuleTarget(args);
884
+ assertDataRuleOptions(args.dataRuleType, args.options, args.checkType);
885
+ assertDataRuleCheckType(args.dataRuleType, args.checkType);
886
+ },
887
+ dryRunPayload: // Dry-run is the cheapest place to see the formula lint (the platform
888
+ // validates nothing at create time; the failure surfaces at submit).
889
+ async (_session, args) => {
890
+ const lintWarnings = dataRuleFormulaWarnings(args.dataRuleType, args.options);
891
+ return lintWarnings.length ? { lintWarnings } : {};
892
+ },
893
+ rollback: dataruleRollbackSpec("datarule.create"),
894
+ });
895
+ writeTool({
896
+ tools,
897
+ name: "datarule.update",
898
+ lane: DATARULE_LANE,
899
+ scope: DATARULE_TOOLS["datarule.update"].scope,
900
+ description: DATARULE_TOOLS["datarule.update"].description,
901
+ inputSchema: {
902
+ id: z.string().min(1).optional().describe("数据规则记录 id;省略时可改用业务键 schemaCode + propertyCode(+dataRuleType) 定位"),
903
+ schemaCode: z.string().min(1),
904
+ propertyCode: propertyCodeArg,
905
+ propertyCodes: propertyCodesArg,
906
+ dataRuleType: dataRuleTypeArg,
907
+ options: dataRuleOptionsArg,
908
+ checkType: checkTypeArg,
909
+ childRule: z.boolean().optional(),
910
+ name: z.string().optional(),
911
+ sortKey: z.number().int().optional(),
912
+ },
913
+ execute: async (session, args) => {
914
+ assertDataRuleOptions(args.dataRuleType, args.options, args.checkType);
915
+ assertDataRuleCheckType(args.dataRuleType, args.checkType);
916
+ const id = await locateDataRuleId(session, args, "update POST");
917
+ const before = await listDataRules(session, args.schemaCode);
918
+ const current = before.find((row) => row.id === id);
919
+ if (!current)
920
+ throw new ToolRefusalError({ status: "refused", blocker: "datarule-absent", message: `数据规则 ${id} 不存在于 ${args.schemaCode};拒绝 update POST` });
921
+ // The stored childRule governs when the caller omits it (full-row update).
922
+ const patchArgs = { ...args, id };
923
+ await assertChildFormulaRefs(session, { ...patchArgs, childRule: args.childRule ?? current.childRule === true });
924
+ // Keep the existing binding when the caller omits a target (the platform
925
+ // update is a full-row overwrite — an empty patch would clear propertyCodes).
926
+ const requested = dataRuleTargetCodes(patchArgs);
927
+ const codes = requested.length > 0 ? requested : storedTargetCodes(current);
928
+ const boundArgs = { ...patchArgs, propertyCode: codes.length === 1 ? codes[0] : undefined, propertyCodes: codes };
929
+ if (before.some((row) => {
930
+ if (row.id === id)
931
+ return false;
932
+ if (Number(row.dataRuleType) !== args.dataRuleType)
933
+ return false;
934
+ if (!storedTargetCodes(row).some((code) => codes.includes(code)))
935
+ return false;
936
+ return args.dataRuleType === 1 ? (row.checkType ?? null) === (args.checkType ?? null) : true;
937
+ }))
938
+ throw new ToolRefusalError({ status: "refused", blocker: "datarule-duplicate-property-type", message: `字段 ${args.schemaCode}.${codes.join(",")} 已存在同类型数据规则${args.dataRuleType === 1 ? `(checkType ${args.checkType})` : ""};拒绝 update POST` });
939
+ const beforeDecoded = decodeDataRule(current);
940
+ const performed = await wireCall(session, {
941
+ path: ENDPOINTS.dataRuleUpdate,
942
+ method: "POST",
943
+ body: fullDataRuleBody(current, boundArgs, JSON.stringify(withOptionsPropertyCodes(withStitchEditorText(args.options), codes))),
944
+ });
945
+ const record = (await listDataRules(session, args.schemaCode, args.dataRuleType)).find((row) => row.id === id);
946
+ if (!record)
947
+ throw new WireError("business", `data rule ${id} disappeared after update`, 304001);
948
+ const afterDecoded = decodeDataRule(record);
949
+ const proof = dataRuleChanges(beforeDecoded, afterDecoded);
950
+ return {
951
+ performed: { errcode: performed.errcode },
952
+ readback: { id, present: true, before: beforeDecoded, after: afterDecoded, changedFields: proof.changedFields, unexpectedChanges: proof.unexpectedChanges, rule: afterDecoded },
953
+ };
954
+ },
955
+ preflight: (args) => {
956
+ assertDataRuleOptions(args.dataRuleType, args.options, args.checkType);
957
+ },
958
+ rollback: dataruleRollbackSpec("datarule.update"),
959
+ });
960
+ writeTool({
961
+ tools,
962
+ name: "datarule.delete",
963
+ lane: DATARULE_LANE,
964
+ scope: DATARULE_TOOLS["datarule.delete"].scope,
965
+ description: DATARULE_TOOLS["datarule.delete"].description,
966
+ inputSchema: {
967
+ id: z.string().min(1).optional().describe("数据规则记录 id;省略时可改用业务键 schemaCode + propertyCode(+dataRuleType) 定位"),
968
+ schemaCode: z.string().min(1),
969
+ propertyCode: propertyCodeArg,
970
+ propertyCodes: propertyCodesArg,
971
+ dataRuleType: z.number().int().min(1).max(5).optional().describe("业务键定位去歧义:同一字段可能有多条规则(如必填+只读),用它指定要删的类型"),
972
+ },
973
+ execute: async (session, args) => {
974
+ const id = await locateDataRuleId(session, args, "delete POST");
975
+ const before = (await listDataRules(session, args.schemaCode)).find((row) => row.id === id);
976
+ const performed = await wireCall(session, {
977
+ path: ENDPOINTS.dataRuleDelete,
978
+ method: "POST",
979
+ query: { id },
980
+ });
981
+ const after = await listDataRules(session, args.schemaCode);
982
+ return {
983
+ performed: { errcode: performed.errcode },
984
+ readback: { id, existedBefore: before !== undefined, absent: !after.some((row) => row.id === id) },
985
+ };
986
+ },
987
+ rollback: dataruleRollbackSpec("datarule.delete"),
988
+ });
905
989
  }
906
990
  //# sourceMappingURL=datarule.js.map