@stalfh233/omc-cli 0.6.6 → 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 (68) 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 +90 -17
  7. package/dist/cli-output.js +41 -2
  8. package/dist/cli-stdout.js +74 -0
  9. package/dist/cli.js +162 -54
  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-start-here.md +2 -2
  67. package/skills/omc-tool-selection.md +2 -1
  68. package/skills/omc-write-hazards.md +5 -2
@@ -109,16 +109,87 @@ async function listRules(session, schemaCode) {
109
109
  async function ruleOf(session, schemaCode, ruleCode) {
110
110
  return (await listRules(session, schemaCode)).find((row) => String(row.code ?? row.ruleCode ?? "") === ruleCode);
111
111
  }
112
+ /** Platform-side compiler/processor noise (lombok classloader, javac internals). */
113
+ const PLATFORM_COMPILE_ERROR_RE = /Can't initialize javac processor|NoClassDefFoundError|LombokProcessor|AnnotationProcessor|JavacProcessingEnvironment|com\.sun\.tools\.javac/i;
114
+ function errorTextOf(entry) {
115
+ if (typeof entry === "string")
116
+ return entry;
117
+ const record = asRecord(entry);
118
+ if (!record)
119
+ return String(entry ?? "");
120
+ for (const key of ["errorMessage", "message", "msg", "error"]) {
121
+ if (typeof record[key] === "string")
122
+ return record[key];
123
+ }
124
+ return JSON.stringify(record);
125
+ }
126
+ function classifyCompileError(entry) {
127
+ const record = asRecord(entry);
128
+ const message = errorTextOf(entry);
129
+ const lineRaw = record?.line ?? record?.lineNumber;
130
+ const line = typeof lineRaw === "number" ? lineRaw : typeof lineRaw === "string" && /^\d+$/.test(lineRaw) ? Number(lineRaw) : null;
131
+ const fileName = typeof record?.fileName === "string" ? record.fileName : typeof record?.file === "string" ? record.file : undefined;
132
+ const origin = PLATFORM_COMPILE_ERROR_RE.test(message) ? "platform" : "user";
133
+ return { line, message, origin, ...(fileName ? { fileName } : {}) };
134
+ }
135
+ /**
136
+ * r28j F9/F10: compile failures used to arrive as `{count,sha256,omitted:true}`
137
+ * via the generic diet, forcing a second `artifact get`. Small error lists are
138
+ * cheap to inline; platform lombok/javac faults must stay visible even when
139
+ * `success:true` (or when they share the list with user symbol errors).
140
+ */
141
+ export function presentCompileErrors(raw) {
142
+ if (raw == null)
143
+ return { errors: null, platformErrors: [], userErrors: [] };
144
+ const list = Array.isArray(raw) ? raw : [raw];
145
+ const normalized = list.map(classifyCompileError);
146
+ // Platform stacks are long — keep message first line only in the inline face
147
+ // so a 4-error compile stays under the 2KB diet gate (r28j F9).
148
+ const slim = normalized.map((entry) => ({
149
+ ...entry,
150
+ message: entry.message.split("\n", 1)[0] ?? entry.message,
151
+ }));
152
+ const platformErrors = slim.filter((entry) => entry.origin === "platform");
153
+ const userErrors = slim.filter((entry) => entry.origin === "user");
154
+ const inlineBytes = Buffer.byteLength(JSON.stringify(slim), "utf8");
155
+ if (slim.length <= 8 && inlineBytes <= 1800) {
156
+ return { errors: slim, platformErrors, userErrors };
157
+ }
158
+ const digest = sha256(JSON.stringify(normalized));
159
+ return {
160
+ // Still return classified first-line summaries so callers see origin without
161
+ // a second round-trip; full stacks stay in the artifact.
162
+ errors: null,
163
+ platformErrors,
164
+ userErrors,
165
+ errorsOmitted: {
166
+ count: normalized.length,
167
+ sha256: digest,
168
+ fetch: "omc artifact get <detailArtifactId> --jq errors(或本次 call 加 --jq errors)",
169
+ },
170
+ };
171
+ }
112
172
  async function compileSource(session, code, content) {
113
173
  const response = await wireCall(session, { path: ENDPOINTS.programmingCompile, method: "POST", body: { code, content } });
114
174
  const result = asRecord(response.data) ?? {};
115
- const success = result.success === true;
175
+ const rawErrors = result.errors ?? result.error ?? null;
176
+ const presented = presentCompileErrors(rawErrors);
177
+ // r28j F10: platform lombok/javac processor faults are an environment defect.
178
+ // If the wire still says success:true while returning processor noise, do not
179
+ // trust the green light — force false and mark degraded. When success:false
180
+ // already, keep it false and surface origin=platform alongside user errors.
181
+ const wireSuccess = result.success === true;
182
+ const platformFault = presented.platformErrors.length > 0;
183
+ const success = wireSuccess && !platformFault;
116
184
  return {
117
185
  success,
118
- errors: result.errors ?? result.error ?? null,
186
+ errors: rawErrors,
187
+ presented,
119
188
  diagnostics: {
120
189
  ...(typeof result.message === "string" ? { message: result.message } : {}),
121
190
  ...(Array.isArray(result.errorMessages) ? { errorMessages: result.errorMessages } : {}),
191
+ ...(platformFault ? { platformCompilerDegraded: true, platformCompilerNote: "编译结果含平台内部错误(lombok/javac processor);success 不可单独作为“用户源码正确”的证据" } : {}),
192
+ ...(wireSuccess && platformFault ? { wireSuccessIgnored: true } : {}),
122
193
  },
123
194
  };
124
195
  }
@@ -253,7 +324,15 @@ export function registerProgrammingLane(tools) {
253
324
  content,
254
325
  contentSha256: content === null ? null : sha256(content),
255
326
  contentBytes: content === null ? null : Buffer.byteLength(content, "utf8"),
256
- ...(read.provisioned ? {} : { hint: "未落库草稿:content 是平台按规则类型生成的模板骨架(version:null)。改完后 programming.save 落库,programming.publish 生效。" }),
327
+ // r28j F7: --search "bizObject" 被写危害文档淹没;API 骨架入口钉在这里。
328
+ apiHint: 'BizObject 读写骨架:omc help omc-backend-online-coding --section "5. 典型 handler 骨架"',
329
+ ...(read.provisioned
330
+ ? {
331
+ // Draft face always reports published:false after publish too —
332
+ // do not let callers treat this boolean as the publish verdict (F11).
333
+ publishedNote: "本面 published 来自 version=0 草稿行;发布是否生效请看 programming.publish 的 readback.runtimeEffective / developmentMode+enabled,不要看本字段。",
334
+ }
335
+ : { hint: "未落库草稿:content 是平台按规则类型生成的模板骨架(version:null)。改完后 programming.save 落库,programming.publish 生效。" }),
257
336
  };
258
337
  }, {
259
338
  target: `programming.get:${args.schemaCode}/${args.businessRuleCode}`,
@@ -287,149 +366,208 @@ export function registerProgrammingLane(tools) {
287
366
  if (content === undefined || content.trim() === "")
288
367
  return { status: "refused", blocker: "programming-content-required", message: "没有可编译的源码:传 content,或先 programming.save 落库草稿。" };
289
368
  const result = await compileSource(session, code, content);
369
+ const presented = result.presented;
290
370
  return {
291
371
  schemaCode: args.schemaCode,
292
372
  businessRuleCode: args.businessRuleCode,
293
373
  code,
294
374
  success: result.success,
295
- errors: result.errors,
296
- ...(result.success ? {} : { hint: "编译失败时规则无法运行(运行时每次执行都重新编译)。按 errors 修正后再 publish。" }),
375
+ // r28j F9: inline classified errors (first line) instead of
376
+ // `{count,sha256,omitted:true}` that forced a second artifact get.
377
+ errors: presented.errorsOmitted
378
+ ? { count: presented.errorsOmitted.count, sha256: presented.errorsOmitted.sha256, omitted: true, fetch: presented.errorsOmitted.fetch }
379
+ : presented.errors,
380
+ platformErrors: presented.platformErrors,
381
+ userErrors: presented.userErrors,
382
+ ...(result.diagnostics.platformCompilerDegraded ? { platformCompilerDegraded: true, platformCompilerNote: result.diagnostics.platformCompilerNote } : {}),
383
+ ...(result.success
384
+ ? {}
385
+ : {
386
+ hint: presented.userErrors.length > 0
387
+ ? "编译失败时规则无法运行(运行时每次执行都重新编译)。先按 userErrors 修正源码;platformErrors 是环境/lombok 故障,不是你的符号错。"
388
+ : "编译失败时规则无法运行(运行时每次执行都重新编译)。本次只有 platformErrors(平台 javac/lombok 处理器故障)——不是用户源码符号错;修好环境后再 compile。",
389
+ }),
297
390
  };
391
+ }, {
392
+ // Keep the classified error face inline even when stacks would trip the
393
+ // generic diet (F9). Full stacks remain in detailArtifactId when omitted.
394
+ compact: (result) => result,
298
395
  }),
299
396
  }));
300
- writeTool(tools, "programming.save", LANE, TOOLS["programming.save"].scope, TOOLS["programming.save"].description, {
301
- schemaCode: z.string().min(1),
302
- businessRuleCode: z.string().min(1),
303
- content: z.string().min(1),
304
- code: z.string().min(1).optional(),
305
- businessRuleName: z.string().optional(),
306
- appCode: z.string().optional(),
307
- }, async (session, args) => {
308
- const code = args.code ?? businessRuleSourceCode(args.schemaCode, args.businessRuleCode);
309
- const response = await wireCall(session, {
310
- path: ENDPOINTS.programmingSaveSource,
311
- method: "POST",
312
- body: {
313
- code,
314
- codeType: CODE_TYPE_BUSINESS_RULE,
315
- schemaCode: args.schemaCode,
316
- businessRuleCode: args.businessRuleCode,
317
- ...(args.businessRuleName !== undefined ? { businessRuleName: args.businessRuleName } : {}),
318
- ...(args.appCode !== undefined ? { appCode: args.appCode } : {}),
319
- className: code,
320
- content: args.content,
321
- },
322
- });
323
- const saved = asRecord(response.data);
324
- const readback = await readSource(session, { schemaCode: args.schemaCode, businessRuleCode: args.businessRuleCode, code });
325
- const readbackContent = typeof readback.model?.content === "string" ? readback.model.content : null;
326
- return {
327
- performed: { errcode: 0, code, version: saved?.version ?? null, contentSha256: sha256(args.content), contentBytes: Buffer.byteLength(args.content, "utf8") },
328
- readback: {
329
- present: readback.present,
330
- provisioned: readback.provisioned,
331
- version: readback.model?.version ?? null,
332
- contentMatches: readbackContent === args.content,
333
- contentSha256: readbackContent === null ? null : sha256(readbackContent),
334
- published: readback.model?.published === true,
335
- note: "草稿保存不改变运行时——生效需 programming.publish。",
336
- },
337
- };
338
- }, (args) => {
339
- const code = args.code ?? businessRuleSourceCode(args.schemaCode, args.businessRuleCode);
340
- assertTemplateShape(args.content, code);
341
- }, async (_session, args) => {
342
- const code = args.code ?? businessRuleSourceCode(args.schemaCode, args.businessRuleCode);
343
- return {
344
- code,
345
- contentSha256: sha256(args.content),
346
- contentBytes: Buffer.byteLength(args.content, "utf8"),
347
- note: "dry-run 已执行模板校验(包路径/类名);真写会覆盖远端草稿 content(平台无乐观锁)。",
348
- };
349
- }, sourceRollback());
350
- writeTool(tools, "programming.publish", LANE, TOOLS["programming.publish"].scope, TOOLS["programming.publish"].description, {
351
- schemaCode: z.string().min(1),
352
- businessRuleCode: z.string().min(1),
353
- content: z.string().optional().describe("要发布的源码;省略时用远端草稿(未落库则用模板骨架,会因模板未实现而编译通过但语义为空——通常应传 content)"),
354
- code: z.string().min(1).optional(),
355
- businessRuleName: z.string().optional(),
356
- appCode: z.string().optional(),
357
- }, async (session, args) => {
358
- const code = args.code ?? businessRuleSourceCode(args.schemaCode, args.businessRuleCode);
359
- const rule = await ruleOf(session, args.schemaCode, args.businessRuleCode);
360
- if (!rule) {
361
- throw new ToolRefusalError({
362
- status: "refused",
363
- blocker: "programming-rule-absent",
364
- message: `业务规则 ${args.schemaCode}/${args.businessRuleCode} 不存在:publish 必须携带 businessRule 整模型(平台据此把规则切 CODING+enable+发布)。先 rule.create/rule.list 确认规则存在。`,
365
- });
366
- }
367
- let content = args.content;
368
- if (content === undefined) {
369
- const read = await readSource(session, { schemaCode: args.schemaCode, businessRuleCode: args.businessRuleCode });
370
- content = typeof read.model?.content === "string" ? read.model.content : undefined;
371
- }
372
- if (content === undefined || content.trim() === "")
373
- throw new ToolRefusalError({
374
- status: "refused",
375
- blocker: "programming-content-required",
376
- message: "没有可发布的源码:传 content,或先 programming.save 落库草稿。",
377
- });
378
- assertTemplateShape(content, code);
379
- // Explicit compile gate: the server compiles again inside publish, but a
380
- // failed compile must surface its diagnostics BEFORE any write is admitted.
381
- const compiled = await compileSource(session, code, content);
382
- if (!compiled.success) {
383
- throw new ToolRefusalError({
384
- status: "refused",
385
- blocker: "programming-compile-failed",
386
- message: "编译失败,拒绝发布(运行时每次执行都重新编译——坏源码会让该规则的每次业务执行失败)。按 errors 修正后重试。",
387
- errors: compiled.errors,
388
- diagnostics: compiled.diagnostics,
397
+ writeTool({
398
+ tools,
399
+ name: "programming.save",
400
+ lane: LANE,
401
+ scope: TOOLS["programming.save"].scope,
402
+ description: TOOLS["programming.save"].description,
403
+ inputSchema: {
404
+ schemaCode: z.string().min(1),
405
+ businessRuleCode: z.string().min(1),
406
+ content: z.string().min(1),
407
+ code: z.string().min(1).optional(),
408
+ businessRuleName: z.string().optional(),
409
+ appCode: z.string().optional(),
410
+ },
411
+ execute: async (session, args) => {
412
+ const code = args.code ?? businessRuleSourceCode(args.schemaCode, args.businessRuleCode);
413
+ const response = await wireCall(session, {
414
+ path: ENDPOINTS.programmingSaveSource,
415
+ method: "POST",
416
+ body: {
417
+ code,
418
+ codeType: CODE_TYPE_BUSINESS_RULE,
419
+ schemaCode: args.schemaCode,
420
+ businessRuleCode: args.businessRuleCode,
421
+ ...(args.businessRuleName !== undefined ? { businessRuleName: args.businessRuleName } : {}),
422
+ ...(args.appCode !== undefined ? { appCode: args.appCode } : {}),
423
+ className: code,
424
+ content: args.content,
425
+ },
389
426
  });
390
- }
391
- const response = await wireCall(session, {
392
- path: ENDPOINTS.programmingPublish,
393
- method: "POST",
394
- body: {
395
- code,
396
- codeType: CODE_TYPE_BUSINESS_RULE,
397
- schemaCode: args.schemaCode,
398
- businessRuleCode: args.businessRuleCode,
399
- ...(args.businessRuleName !== undefined ? { businessRuleName: args.businessRuleName } : {}),
400
- ...(args.appCode !== undefined ? { appCode: args.appCode } : {}),
401
- className: code,
402
- content,
403
- businessRule: rule,
404
- },
405
- });
406
- // Readback: draft content must equal what we published, and the rule must
407
- // now be in coding mode + enabled (the platform's publishBusinessRule
408
- // side effect). published content itself has no wire read endpoint.
409
- const readbackSource = await readSource(session, { schemaCode: args.schemaCode, businessRuleCode: args.businessRuleCode, code });
410
- const readbackRule = await ruleOf(session, args.schemaCode, args.businessRuleCode);
411
- const readbackContent = typeof readbackSource.model?.content === "string" ? readbackSource.model.content : null;
412
- return {
413
- performed: { errcode: 0, code, contentSha256: sha256(content), contentBytes: Buffer.byteLength(content, "utf8"), compiled: true },
414
- readback: {
415
- present: readbackSource.present,
416
- contentMatches: readbackContent === content,
417
- developmentMode: readbackRule?.developmentMode ?? readbackRule?.development_mode ?? null,
418
- enabled: readbackRule?.enabled ?? null,
419
- publishStatus: readbackRule?.publishStatus ?? null,
420
- publishedFlag: readbackSource.model?.published === true,
421
- note: "运行时执行的是 published 最新版;published 内容无 wire 读取端点,回滚靠本地 git + 重新 publish。",
422
- },
423
- };
424
- }, (args) => {
425
- const code = args.code ?? businessRuleSourceCode(args.schemaCode, args.businessRuleCode);
426
- if (args.content !== undefined)
427
+ const saved = asRecord(response.data);
428
+ const readback = await readSource(session, { schemaCode: args.schemaCode, businessRuleCode: args.businessRuleCode, code });
429
+ const readbackContent = typeof readback.model?.content === "string" ? readback.model.content : null;
430
+ return {
431
+ performed: { errcode: 0, code, version: saved?.version ?? null, contentSha256: sha256(args.content), contentBytes: Buffer.byteLength(args.content, "utf8") },
432
+ readback: {
433
+ present: readback.present,
434
+ provisioned: readback.provisioned,
435
+ version: readback.model?.version ?? null,
436
+ contentMatches: readbackContent === args.content,
437
+ contentSha256: readbackContent === null ? null : sha256(readbackContent),
438
+ published: readback.model?.published === true,
439
+ note: "草稿保存不改变运行时——生效需 programming.publish。",
440
+ },
441
+ };
442
+ },
443
+ preflight: (args) => {
444
+ const code = args.code ?? businessRuleSourceCode(args.schemaCode, args.businessRuleCode);
427
445
  assertTemplateShape(args.content, code);
428
- }, async (_session, args) => ({
429
- code: args.code ?? businessRuleSourceCode(args.schemaCode, args.businessRuleCode),
430
- contentSha256: args.content === undefined ? null : sha256(args.content),
431
- note: "dry-run 已执行模板校验;真写会 compile→publish(插新 published 版本行并把规则切 CODING+enable)。",
432
- }));
446
+ },
447
+ dryRunPayload: async (_session, args) => {
448
+ const code = args.code ?? businessRuleSourceCode(args.schemaCode, args.businessRuleCode);
449
+ return {
450
+ code,
451
+ contentSha256: sha256(args.content),
452
+ contentBytes: Buffer.byteLength(args.content, "utf8"),
453
+ note: "dry-run 已执行模板校验(包路径/类名);真写会覆盖远端草稿 content(平台无乐观锁)。",
454
+ };
455
+ },
456
+ rollback: sourceRollback(),
457
+ });
458
+ writeTool({
459
+ tools,
460
+ name: "programming.publish",
461
+ lane: LANE,
462
+ scope: TOOLS["programming.publish"].scope,
463
+ description: TOOLS["programming.publish"].description,
464
+ inputSchema: {
465
+ schemaCode: z.string().min(1),
466
+ businessRuleCode: z.string().min(1),
467
+ content: z.string().optional().describe("要发布的源码;省略时用远端草稿(未落库则用模板骨架,会因模板未实现而编译通过但语义为空——通常应传 content)"),
468
+ code: z.string().min(1).optional(),
469
+ businessRuleName: z.string().optional(),
470
+ appCode: z.string().optional(),
471
+ },
472
+ execute: async (session, args) => {
473
+ const code = args.code ?? businessRuleSourceCode(args.schemaCode, args.businessRuleCode);
474
+ const rule = await ruleOf(session, args.schemaCode, args.businessRuleCode);
475
+ if (!rule) {
476
+ throw new ToolRefusalError({
477
+ status: "refused",
478
+ blocker: "programming-rule-absent",
479
+ message: `业务规则 ${args.schemaCode}/${args.businessRuleCode} 不存在:publish 必须携带 businessRule 整模型(平台据此把规则切 CODING+enable+发布)。先 rule.create/rule.list 确认规则存在。`,
480
+ });
481
+ }
482
+ let content = args.content;
483
+ if (content === undefined) {
484
+ const read = await readSource(session, { schemaCode: args.schemaCode, businessRuleCode: args.businessRuleCode });
485
+ content = typeof read.model?.content === "string" ? read.model.content : undefined;
486
+ }
487
+ if (content === undefined || content.trim() === "")
488
+ throw new ToolRefusalError({
489
+ status: "refused",
490
+ blocker: "programming-content-required",
491
+ message: "没有可发布的源码:传 content,或先 programming.save 落库草稿。",
492
+ });
493
+ assertTemplateShape(content, code);
494
+ // Explicit compile gate: the server compiles again inside publish, but a
495
+ // failed compile must surface its diagnostics BEFORE any write is admitted.
496
+ const compiled = await compileSource(session, code, content);
497
+ if (!compiled.success) {
498
+ throw new ToolRefusalError({
499
+ status: "refused",
500
+ blocker: "programming-compile-failed",
501
+ message: "编译失败,拒绝发布(运行时每次执行都重新编译——坏源码会让该规则的每次业务执行失败)。按 userErrors 修正后重试;platformErrors 是环境故障。",
502
+ errors: compiled.presented.errors ?? compiled.errors,
503
+ platformErrors: compiled.presented.platformErrors,
504
+ userErrors: compiled.presented.userErrors,
505
+ diagnostics: compiled.diagnostics,
506
+ });
507
+ }
508
+ const response = await wireCall(session, {
509
+ path: ENDPOINTS.programmingPublish,
510
+ method: "POST",
511
+ body: {
512
+ code,
513
+ codeType: CODE_TYPE_BUSINESS_RULE,
514
+ schemaCode: args.schemaCode,
515
+ businessRuleCode: args.businessRuleCode,
516
+ ...(args.businessRuleName !== undefined ? { businessRuleName: args.businessRuleName } : {}),
517
+ ...(args.appCode !== undefined ? { appCode: args.appCode } : {}),
518
+ className: code,
519
+ content,
520
+ businessRule: rule,
521
+ },
522
+ });
523
+ // Readback: draft content must equal what we published, and the rule must
524
+ // now be in coding mode + enabled (the platform's publishBusinessRule
525
+ // side effect). published content itself has no wire read endpoint.
526
+ const readbackSource = await readSource(session, { schemaCode: args.schemaCode, businessRuleCode: args.businessRuleCode, code });
527
+ const readbackRule = await ruleOf(session, args.schemaCode, args.businessRuleCode);
528
+ const readbackContent = typeof readbackSource.model?.content === "string" ? readbackSource.model.content : null;
529
+ const developmentMode = readbackRule?.developmentMode ?? readbackRule?.development_mode ?? null;
530
+ const enabled = readbackRule?.enabled ?? null;
531
+ const codingMode = developmentMode === 1 || developmentMode === "CODING" || developmentMode === "1";
532
+ // r28j F11 live (1.1.25): after a successful publish the rule list shows
533
+ // developmentMode=1 + enabled=true (publishBusinessRule side effect), but
534
+ // publishStatus is absent/null on the list projection and get_source.published
535
+ // stays false because that face always reads the version=0 draft row.
536
+ // Authoritative signal = coding mode + enabled (+ compiled gate above).
537
+ const runtimeEffective = codingMode && enabled === true ? "published" : "draft";
538
+ const publishStatusRaw = readbackRule?.publishStatus;
539
+ return {
540
+ performed: { errcode: 0, code, contentSha256: sha256(content), contentBytes: Buffer.byteLength(content, "utf8"), compiled: true },
541
+ readback: {
542
+ present: readbackSource.present,
543
+ contentMatches: readbackContent === content,
544
+ developmentMode,
545
+ enabled,
546
+ authoritative: "developmentMode+enabled",
547
+ runtimeEffective,
548
+ ...(publishStatusRaw === undefined
549
+ ? {}
550
+ : publishStatusRaw === null
551
+ ? { publishStatus: null, publishStatusNote: "本版本平台 list/get 投影的 publishStatus 恒为 null,不能用来判断是否已发布;请用 authoritative=developmentMode+enabled(CODING/1 + enabled:true = 发布副作用已发生)" }
552
+ : { publishStatus: publishStatusRaw }),
553
+ // Draft-row flag from get_source — NOT proof of publish. Omit when false
554
+ // so it cannot contradict runtimeEffective:"published".
555
+ ...(readbackSource.model?.published === true ? { publishedFlag: true } : { publishedFlagNote: "get_source.published 读的是 version=0 草稿行,发布后仍常为 false;不是发布成败判据(published 内容无 wire 读端点)" }),
556
+ note: "运行时执行的是 published 最新版;published 内容无 wire 读取端点,回滚靠本地 git + 重新 publish。判发布是否生效:authoritative=developmentMode+enabled(1/CODING + true)。",
557
+ },
558
+ };
559
+ },
560
+ preflight: (args) => {
561
+ const code = args.code ?? businessRuleSourceCode(args.schemaCode, args.businessRuleCode);
562
+ if (args.content !== undefined)
563
+ assertTemplateShape(args.content, code);
564
+ },
565
+ dryRunPayload: async (_session, args) => ({
566
+ code: args.code ?? businessRuleSourceCode(args.schemaCode, args.businessRuleCode),
567
+ contentSha256: args.content === undefined ? null : sha256(args.content),
568
+ note: "dry-run 已执行模板校验;真写会 compile→publish(插新 published 版本行并把规则切 CODING+enable)。",
569
+ }),
570
+ });
433
571
  }
434
572
  export { PROGRAMMING_WIRE };
435
573
  //# sourceMappingURL=programming.js.map
@@ -97,7 +97,7 @@ export const REPORT_LANE_CONTRACT = Object.freeze({
97
97
  }),
98
98
  "report.create": Object.freeze({
99
99
  scope: REPORT_SCOPE,
100
- description: "新建报表页:平台 report/create(code,name,appCode,parentId 必填;parentId 取所属分组 folder id)再 BI report/v2/reportpage 初始化 dashboard(global 用默认主题)。readback 见 report.get。",
100
+ description: "新建报表页:平台 report/create(code,name,appCode,parentId 必填;parentId 必须是 folder.tree 里 type=Folder 的 id,不能挂应用根/报表节点)再 BI report/v2/reportpage 初始化 dashboard(global 用默认主题)。平台/读面可能静默改挂或 folderList 投影与入参不一致——create 会按 folder.tree 读回实际 parentId/path,不一致时标 parentMismatch:true;BI 半成功(WRITE_UNVERIFIED)时带 verifyCommand。也可用 report.tree / folder.tree 核对落点。",
101
101
  }),
102
102
  "report.update": Object.freeze({
103
103
  scope: REPORT_SCOPE,
@@ -134,7 +134,7 @@ export function assertReportContract() {
134
134
  assertDescriptionCarries(REPORT_LANE_CONTRACT, "report.tree", ["queryReportChartTree", "queryReportChartTreeByApp"]);
135
135
  assertDescriptionCarries(REPORT_LANE_CONTRACT, "report.get", ["report/get", "report/v2/config", "present"]);
136
136
  assertDescriptionCarries(REPORT_LANE_CONTRACT, "report.chartdata", ["chartdata", "report.get", "limit"]);
137
- assertDescriptionCarries(REPORT_LANE_CONTRACT, "report.create", ["report/create", "parentId"]);
137
+ assertDescriptionCarries(REPORT_LANE_CONTRACT, "report.create", ["report/create", "parentId", "folder.tree", "parentMismatch"]);
138
138
  assertDescriptionCarries(REPORT_LANE_CONTRACT, "report.update", ["report/update"]);
139
139
  assertDescriptionCarries(REPORT_LANE_CONTRACT, "report.delete", ["removeReport", "logical_delete"]);
140
140
  assertDescriptionCarries(REPORT_LANE_CONTRACT, "report.configure", ["saveGlobal", "addChart", "updateChart", "removeCharts"]);