@stalfh233/omc-cli 0.6.7 → 0.6.11
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/README.md +2 -1
- package/dist/approval-flow.js +5 -3
- package/dist/bizservice-config.js +26 -10
- package/dist/call.js +47 -0
- package/dist/capability-overview.js +47 -24
- package/dist/cli-contract.js +73 -26
- package/dist/cli-output.js +41 -2
- package/dist/cli-stdout.js +74 -0
- package/dist/cli.js +135 -52
- package/dist/command-transport.js +8 -1
- package/dist/config-store.js +17 -2
- package/dist/discovery.js +62 -3
- package/dist/field-families.js +35 -6
- package/dist/goal-routes.js +118 -11
- package/dist/human-summary.js +11 -0
- package/dist/lanes/app-contract.js +12 -5
- package/dist/lanes/app.js +221 -134
- package/dist/lanes/apply-changes.js +2 -1
- package/dist/lanes/b2-registration-contract.js +3 -3
- package/dist/lanes/b2-registration.js +890 -693
- package/dist/lanes/businessrule.js +75 -51
- package/dist/lanes/custom-page.js +166 -115
- package/dist/lanes/datarule-contract.js +2 -2
- package/dist/lanes/datarule.js +238 -154
- package/dist/lanes/dictionary.js +253 -110
- package/dist/lanes/environment.js +57 -9
- package/dist/lanes/field.js +160 -107
- package/dist/lanes/folder-contract.js +7 -3
- package/dist/lanes/folder.js +249 -118
- package/dist/lanes/form-contract.js +1 -1
- package/dist/lanes/form.js +188 -64
- package/dist/lanes/list-view.js +405 -369
- package/dist/lanes/meta.js +31 -6
- package/dist/lanes/model-contract.js +1 -1
- package/dist/lanes/model.js +610 -512
- package/dist/lanes/online-js-contract.js +1 -1
- package/dist/lanes/online-js.js +160 -146
- package/dist/lanes/organization.js +905 -640
- package/dist/lanes/programming.js +274 -136
- package/dist/lanes/report-contract.js +2 -2
- package/dist/lanes/report.js +354 -198
- package/dist/lanes/rule-lifecycle.js +294 -140
- package/dist/lanes/runtime-data-contract.js +4 -4
- package/dist/lanes/runtime-data.js +869 -745
- package/dist/lanes/workflow-contract.js +3 -3
- package/dist/lanes/workflow.js +732 -605
- package/dist/model-scaffold.js +26 -5
- package/dist/path-projection.js +196 -14
- package/dist/plan.js +25 -0
- package/dist/programming-workflow.js +55 -3
- package/dist/read-shapes.js +146 -0
- package/dist/read.js +40 -9
- package/dist/recipes/businessrule.js +74 -24
- package/dist/recipes/dictionary.js +7 -4
- package/dist/recipes/form.js +46 -0
- package/dist/recipes/runtime-data.js +3 -2
- package/dist/refusal-guidance.js +29 -5
- package/dist/rule-support.js +14 -1
- package/dist/rule-when.js +2 -2
- package/dist/tool-types.js +9 -3
- package/dist/version-family-gaps.js +101 -0
- package/dist/write.js +2 -1
- package/package.json +6 -4
- package/skills/omc-glossary.md +13 -0
- package/skills/omc-refusals.md +13 -5
- package/skills/omc-tool-selection.md +2 -1
- 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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
296
|
-
|
|
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(
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
|
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"]);
|