@stalfh233/omc-cli 0.4.42 → 0.4.43
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/dist/lanes/field.js
CHANGED
|
@@ -36,8 +36,8 @@ function fieldRollbackSpec(toolName) {
|
|
|
36
36
|
const row = before;
|
|
37
37
|
const options = row.options !== undefined ? decodeJsonColumn(row.options, JSON_COLUMN_PASSTHROUGH) : undefined;
|
|
38
38
|
if (toolName === "field.delete")
|
|
39
|
-
return { kind: "calls", calls: [{ tool: "field.create", args: { schemaCode, code, name: String(row.name ?? code), propertyType: String(row.propertyType ?? row.type), ...(options !== undefined ? { options } : {}), ...(row.choices !== undefined ? { choices: row.choices } : {}), ...(row.relativeCode
|
|
40
|
-
return { kind: "calls", calls: [{ tool: "field.update", args: { schemaCode, code, ...(row.name !== undefined ? { name: row.name } : {}), ...(row.name_i18n
|
|
39
|
+
return { kind: "calls", calls: [{ tool: "field.create", args: { schemaCode, code, name: String(row.name ?? code), propertyType: String(row.propertyType ?? row.type), ...(options !== undefined ? { options } : {}), ...(row.choices !== undefined ? { choices: row.choices } : {}), ...(row.relativeCode != null ? { relativeCode: row.relativeCode } : {}), ...(row.subFields !== undefined ? { subFields: row.subFields } : {}) } }] };
|
|
40
|
+
return { kind: "calls", calls: [{ tool: "field.update", args: { schemaCode, code, ...(row.name !== undefined ? { name: row.name } : {}), ...(row.name_i18n != null ? { name_i18n: row.name_i18n } : {}), ...(options !== undefined ? { options } : {}), ...(row.choices !== undefined ? { choices: row.choices } : {}), ...(row.relativeCode != null ? { relativeCode: row.relativeCode } : {}), ...(row.subFields !== undefined ? { subFields: row.subFields } : {}), ...(row.propertyEmpty !== undefined ? { propertyEmpty: row.propertyEmpty } : {}) } }] };
|
|
41
41
|
},
|
|
42
42
|
};
|
|
43
43
|
}
|
package/dist/lanes/workflow.js
CHANGED
|
@@ -967,6 +967,23 @@ async function backfillDesignerDefaults(session, schemaCode, body, callerTemplat
|
|
|
967
967
|
}
|
|
968
968
|
}
|
|
969
969
|
}
|
|
970
|
+
/**
|
|
971
|
+
* Rename pairs surfaced in the readback (live r51 2026-10-14): a rename mints
|
|
972
|
+
* `<original>_<n>` and DROPS the original code — re-sending the OLD code then
|
|
973
|
+
* reads as a brand-new node and APPENDS it, silently growing the flow. The
|
|
974
|
+
* readback names the pairs so the caller switches to the new code.
|
|
975
|
+
*/
|
|
976
|
+
function renamedActivityPairs(template, data) {
|
|
977
|
+
const callerCodes = new Set((callerActivities(template) ?? []).map((row) => activityCodeOf(row)).filter((code) => code !== ""));
|
|
978
|
+
const bodyCodes = new Set((Array.isArray(data.activities) ? data.activities : []).map((row) => String(row.activityCode ?? "")));
|
|
979
|
+
const pairs = [];
|
|
980
|
+
for (const code of bodyCodes) {
|
|
981
|
+
const match = /^(.+)_\d+$/.exec(code);
|
|
982
|
+
if (match && callerCodes.has(match[1]) && !bodyCodes.has(match[1]))
|
|
983
|
+
pairs.push({ from: match[1], to: code });
|
|
984
|
+
}
|
|
985
|
+
return pairs;
|
|
986
|
+
}
|
|
970
987
|
/**
|
|
971
988
|
* Live 2026-10-14: a workflow node's propertyPermissions ACCEPTS
|
|
972
989
|
* `required:true` on a CHILD_TABLE (子表) property at the wire level, but the
|
|
@@ -1633,7 +1650,8 @@ export function registerWorkflowLane(tools) {
|
|
|
1633
1650
|
throw new ToolRefusalError({ status: "refused", blocker: "workflow-render-readback-invalid", message: "workflow.update returned no draft" });
|
|
1634
1651
|
validateWorkflowRender(data, "workflow.update readback");
|
|
1635
1652
|
const participantWarnings = participantReferenceWarnings(data);
|
|
1636
|
-
|
|
1653
|
+
const renamedActivities = renamedActivityPairs(template, data);
|
|
1654
|
+
return { performed: { errcode: performed.errcode }, readback: { workflowCode: args.code, activities: Array.isArray(data.activities) ? data.activities.length : 0, templateType: data.templateType ?? null, ...(participantWarnings.length ? { participantWarnings } : {}), ...(renamedActivities.length ? { renamedActivities, renamedHint: "改名会铸新码(上表 from→to)且旧码已失效——后续读写一律用 to;再用旧码 from 会被当作新节点追加" } : {}), workflowRenderCompat: workflowRenderCompat(data) } };
|
|
1637
1655
|
}, (args) => {
|
|
1638
1656
|
// Zero-wire refusal (ticket 77): fires in semanticGate BEFORE the
|
|
1639
1657
|
// governed pipeline — including the rollback before-capture read.
|
|
@@ -8,12 +8,12 @@ const onlinejsHighlightScript = Object.freeze({
|
|
|
8
8
|
environment,
|
|
9
9
|
schemaCode: model,
|
|
10
10
|
sheetCode: "<默认表单 code == 模型 code>",
|
|
11
|
-
|
|
11
|
+
replace: true,
|
|
12
12
|
source: "(function(form){ var amt = form.getControl('amountCode'); var party = form.getControl('targetCode'); function refresh(v){ party.options.style = Number(v) > 100000 ? 'color: red;' : ''; } form.on('onRendered', function(){ refresh(amt.getValue()); amt.valueChange.subscribe(function(change){ refresh(change.value); }); }); })",
|
|
13
13
|
},
|
|
14
14
|
preconditions: [
|
|
15
15
|
"控件编码先 model.inspect/field.list 确认(示例 amountCode/targetCode 换成真实字段 code)",
|
|
16
|
-
"完整 `(function(form){…})`
|
|
16
|
+
"完整 `(function(form){…})` 生命周期外壳必须配 `replace:true`(默认 INCREMENTAL merge 收外壳会被拒并指路 replace——r38 实证)",
|
|
17
17
|
],
|
|
18
18
|
conditionalRequirements: [
|
|
19
19
|
"事件模型:`form.on('onRendered', …)` 初始化 + `<控件>.valueChange.subscribe(function(change){ change.value })` 监听;改样式用 `control.options.style`",
|
|
@@ -23,7 +23,7 @@ const onlinejsHighlightScript = Object.freeze({
|
|
|
23
23
|
priorToolReferences: [{ tool: "form.get", args: { environment, schemaCode: model }, purpose: "确认表单已发布与控件编码" }],
|
|
24
24
|
dryRun: { supported: true, command: `omc call onlinejs.compose --env ${environment} --dry-run --args-file compose.json`, notes: "dry-run 校验外壳/合并模式,不写平台" },
|
|
25
25
|
expectedReadback: { readback: { deliveryState: "DELIVERED", published: true, scriptElementPresent: true, htmlRenderCompat: { draft: { ok: true }, published: { ok: true } } } },
|
|
26
|
-
refusalRecovery: [{ code: "
|
|
26
|
+
refusalRecovery: [{ code: "onlinejs-source-invalid", action: "完整外壳改 `replace:true`(六元素模板保留,仅整体替换 customScript)" }],
|
|
27
27
|
requiresEnvironmentDerivedIds: false,
|
|
28
28
|
});
|
|
29
29
|
export const ONLINEJS_METADATA = Object.freeze({
|
package/dist/rollback-spec.js
CHANGED
|
@@ -59,6 +59,28 @@ export function deepDiff(before, current, path = "", limit = 40, acc = []) {
|
|
|
59
59
|
walk(before, current, path);
|
|
60
60
|
return { changes: acc, total: acc.length };
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Drop null/undefined values from inverse-call args, recursively (live r55
|
|
64
|
+
* 2026-10-14): the wire rows carry explicit nulls (`relativeCode:null`) and
|
|
65
|
+
* `!== undefined` guards let them through — the target tool's input schema then
|
|
66
|
+
* rejects the replay as input-invalid (exit 10) and the restore stops. A null
|
|
67
|
+
* in the before row means "no value", and for every replayable config tool an
|
|
68
|
+
* ABSENT key is the same meaning with a schema the tool accepts.
|
|
69
|
+
*/
|
|
70
|
+
export function pruneNullish(value) {
|
|
71
|
+
if (Array.isArray(value))
|
|
72
|
+
return value.map(pruneNullish);
|
|
73
|
+
if (value !== null && typeof value === "object") {
|
|
74
|
+
const out = {};
|
|
75
|
+
for (const [key, item] of Object.entries(value)) {
|
|
76
|
+
if (item === null || item === undefined)
|
|
77
|
+
continue;
|
|
78
|
+
out[key] = pruneNullish(item);
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
62
84
|
/**
|
|
63
85
|
* Canonical projection for restore verification: a replayed restore will
|
|
64
86
|
* NEVER be byte-equal to the before snapshot — the platform stamps
|
package/dist/rollback.js
CHANGED
|
@@ -5,13 +5,20 @@ import { SessionManager } from "./session-manager.js";
|
|
|
5
5
|
import { makeToolContext, buildToolRegistry } from "./tools.js";
|
|
6
6
|
import { runToolCall } from "./call.js";
|
|
7
7
|
import { readJournal, resolveJournalEntry, laterWritesOnTarget } from "./journal.js";
|
|
8
|
-
import { canonicalForVerify, deepDiff } from "./rollback-spec.js";
|
|
8
|
+
import { canonicalForVerify, deepDiff, pruneNullish } from "./rollback-spec.js";
|
|
9
9
|
import { sha256 } from "./support.js";
|
|
10
10
|
import { emitTransportResult } from "./command-transport.js";
|
|
11
11
|
/** Pure runtime assembly: an injected context (tests) or the default one. */
|
|
12
12
|
function runtime(context) {
|
|
13
13
|
return context ?? makeToolContext({ sessions: new SessionManager(new ConfigStore(defaultPaths())) });
|
|
14
14
|
}
|
|
15
|
+
/** Nullish-prune every call arg set in an inverse plan (see pruneNullish). */
|
|
16
|
+
function pruneInversePlan(plan) {
|
|
17
|
+
const pruneCalls = (calls) => calls.map((call) => ({ tool: call.tool, args: pruneNullish(call.args) }));
|
|
18
|
+
if (plan.kind === "calls")
|
|
19
|
+
return { ...plan, calls: pruneCalls(plan.calls) };
|
|
20
|
+
return plan.partial ? { ...plan, partial: pruneCalls(plan.partial) } : plan;
|
|
21
|
+
}
|
|
15
22
|
function resolvedEnvironment(environment) {
|
|
16
23
|
return new ConfigStore(defaultPaths()).resolveEnvironment(environment).name;
|
|
17
24
|
}
|
|
@@ -94,17 +101,31 @@ async function buildPlan(ref, environmentOption, context) {
|
|
|
94
101
|
currentFingerprint,
|
|
95
102
|
diff: deepDiff(before, current),
|
|
96
103
|
laterWrites,
|
|
97
|
-
|
|
104
|
+
// Every inverse call's args are nullish-pruned centrally: wire-row nulls
|
|
105
|
+
// must never reach a tool's input schema (r55 field.update relativeCode:null).
|
|
106
|
+
inverse: pruneInversePlan(spec.inverse({ before, args: artifactArgs, readback: (artifact.readback ?? {}) })),
|
|
98
107
|
},
|
|
99
108
|
};
|
|
100
109
|
}
|
|
101
110
|
export async function planRollback(ref, options, context) {
|
|
102
111
|
const loaded = await buildPlan(ref, options.environment, context);
|
|
103
112
|
const { entry, plan } = loaded;
|
|
113
|
+
// Preview values are for READING — full before templates run to tens of KB
|
|
114
|
+
// and used to blow the envelope budget (live r51: plan had to be fetched
|
|
115
|
+
// back from an artifact). Truncate previews; `apply` executes the full
|
|
116
|
+
// parameters regardless.
|
|
117
|
+
const preview = (value) => {
|
|
118
|
+
if (value === undefined || value === null)
|
|
119
|
+
return value;
|
|
120
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
121
|
+
return text !== undefined && text.length > 200 ? `${text.slice(0, 200)}…(省略 ${text.length - 200}B,apply 用完整参数)` : value;
|
|
122
|
+
};
|
|
123
|
+
const script = plan.inverse.kind === "calls" ? plan.inverse.calls.map((call) => JSON.stringify({ tool: call.tool, args: { ...call.args, environment: entry.environment } })) : [];
|
|
124
|
+
const scriptOmitted = script.join("").length > 6000;
|
|
104
125
|
return {
|
|
105
126
|
status: "ok",
|
|
106
127
|
rollbackOf: { seq: entry.seq, tool: entry.tool, target: entry.target, ts: entry.ts },
|
|
107
|
-
diff: { changedPaths: plan.diff.changes, total: plan.diff.total },
|
|
128
|
+
diff: { changedPaths: plan.diff.changes.map((change) => ({ path: change.path, before: preview(change.before), current: preview(change.current) })), total: plan.diff.total },
|
|
108
129
|
...(plan.laterWrites.length > 0
|
|
109
130
|
? { warning: `该目标在此写之后还有 ${plan.laterWrites.length} 次写(seq ${plan.laterWrites.map((w) => w.seq).join(", ")});本次回滚会用 before 参数覆盖这些后续变更`, laterWrites: plan.laterWrites.map((w) => ({ seq: w.seq, tool: w.tool, ts: w.ts })) }
|
|
110
131
|
: {}),
|
|
@@ -113,7 +134,9 @@ export async function planRollback(ref, options, context) {
|
|
|
113
134
|
kind: "calls",
|
|
114
135
|
count: plan.inverse.calls.length,
|
|
115
136
|
...(plan.inverse.notes ? { notes: plan.inverse.notes } : {}),
|
|
116
|
-
|
|
137
|
+
...(scriptOmitted
|
|
138
|
+
? { scriptOmitted: true, scriptHint: "恢复调用参数较大(含完整 before 模板)已省略——直接 omc rollback apply <seq> 执行即用完整参数" }
|
|
139
|
+
: { script }),
|
|
117
140
|
}
|
|
118
141
|
: { kind: "irreversible", reason: plan.inverse.reason, ...(plan.inverse.partial ? { partial: plan.inverse.partial } : {}) },
|
|
119
142
|
hint: "预览不写入;确认后 omc rollback apply <seq>(恢复走完整写闸门,回滚本身也会记 journal,可再回滚)",
|
|
@@ -137,7 +160,16 @@ export async function applyRollback(ref, options, context) {
|
|
|
137
160
|
context: ctx,
|
|
138
161
|
print: () => undefined,
|
|
139
162
|
});
|
|
140
|
-
results.push({
|
|
163
|
+
results.push({
|
|
164
|
+
tool: call.tool,
|
|
165
|
+
exitCode,
|
|
166
|
+
status: result.status,
|
|
167
|
+
...(result.deliveryState ? { deliveryState: result.deliveryState } : {}),
|
|
168
|
+
...(result.summary ? { summary: result.summary } : {}),
|
|
169
|
+
// r55: exit-code-only results hid the reason a restore call failed
|
|
170
|
+
// (input-invalid showed as a bare "status=error"). Surface the shape.
|
|
171
|
+
...(exitCode === 0 ? {} : { blocker: result.blocker ?? null, message: result.message ?? null, ...(result.issues !== undefined ? { issues: result.issues } : {}), ...(result.diagnostic !== undefined ? { diagnostic: result.diagnostic } : {}) }),
|
|
172
|
+
});
|
|
141
173
|
if (exitCode !== 0)
|
|
142
174
|
return { status: "stopped", rollbackOf: { seq: entry.seq, tool: entry.tool }, step: index + 1, results, message: `恢复调用 ${call.tool} 未成功(status=${result.status});已执行的调用已各自记 journal,可对已完成步骤再 plan` };
|
|
143
175
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stalfh233/omc-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.43",
|
|
4
4
|
"description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@stalfh233/omc-fake-platform": "0.4.
|
|
53
|
+
"@stalfh233/omc-fake-platform": "0.4.43",
|
|
54
54
|
"commander": "14.0.3",
|
|
55
|
-
"omc-core": "0.4.
|
|
55
|
+
"omc-core": "0.4.43",
|
|
56
56
|
"proper-lockfile": "4.1.2",
|
|
57
57
|
"write-file-atomic": "6.0.0",
|
|
58
58
|
"zod": "^3.23.8",
|
|
@@ -42,6 +42,7 @@ node "$CLI" help omc-five-piece-flow
|
|
|
42
42
|
|
|
43
43
|
## 模型与字段
|
|
44
44
|
|
|
45
|
+
- **code 长度平台硬限**:模型 code ≤26 字符、字段 code ≤28(`business-code-invalid` 出门校验;长业务名先起短 code,长名放 name)。
|
|
45
46
|
- 模型层字段默认可空;必填、只读、可见性在表单和流程节点配置。
|
|
46
47
|
- `propertyEmpty` 默认 `false`,不要把业务必填硬编码在数据模型。
|
|
47
48
|
- 关联单选优先读目标模型已发布字段,明确显示字段;无映射不要写空数组字符串。
|