@stalfh233/omc-cli 0.4.14 → 0.4.16
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/LICENSE +21 -21
- package/README.md +297 -297
- package/coverage/m1-coverage-manifest-v1.json +1278 -1277
- package/dist/args.js +0 -0
- package/dist/artifacts.js +0 -0
- package/dist/bench/call-face-eval.js +0 -0
- package/dist/bench/context-attribution.js +0 -0
- package/dist/bench/discovery-cost-eval.js +0 -0
- package/dist/bench/driver.js +0 -0
- package/dist/bench/evals.js +0 -0
- package/dist/bench/fake-http-server.js +0 -0
- package/dist/bench/instrument.js +0 -0
- package/dist/bench/intent-face-eval.js +40 -40
- package/dist/bench/run.js +0 -0
- package/dist/bench/scenario.js +0 -0
- package/dist/bench/types.js +0 -0
- package/dist/bi-wire.js +0 -0
- package/dist/bizservice-config.js +38 -13
- package/dist/call.js +0 -0
- package/dist/capability-absences.js +0 -0
- package/dist/capability-overview.js +2 -2
- package/dist/capability-shape.js +0 -0
- package/dist/cli-contract.js +0 -0
- package/dist/cli-output.js +41 -15
- package/dist/cli.js +42 -3
- package/dist/code-rules.js +0 -0
- package/dist/command-transport.js +0 -0
- package/dist/config-store.js +0 -0
- package/dist/context.js +0 -0
- package/dist/contract-consistency.js +0 -0
- package/dist/contract-resources.js +0 -0
- package/dist/coverage-consistency.js +0 -0
- package/dist/coverage-registry.js +0 -0
- package/dist/coverage.js +0 -0
- package/dist/data-list-filter.js +0 -0
- package/dist/discovery.js +0 -0
- package/dist/endpoints.js +2 -0
- package/dist/environment-policy.js +0 -0
- package/dist/execution-metadata.js +4 -4
- package/dist/fake/app.js +0 -0
- package/dist/fake/b2-registration.js +26 -0
- package/dist/fake/businessrule.js +0 -0
- package/dist/fake/datarule.js +0 -0
- package/dist/fake/dictionary.js +0 -0
- package/dist/fake/environment.js +0 -0
- package/dist/fake/field.js +0 -0
- package/dist/fake/form.js +0 -0
- package/dist/fake/index.js +0 -0
- package/dist/fake/list-view.js +0 -0
- package/dist/fake/model.js +0 -0
- package/dist/fake/online-js.js +0 -0
- package/dist/fake/report.js +0 -0
- package/dist/fake/routes.js +0 -0
- package/dist/fake/rule-lifecycle.js +0 -0
- package/dist/fake/runtime-data.js +0 -0
- package/dist/fake/state.js +0 -0
- package/dist/fake/workflow.js +0 -0
- package/dist/field-change.js +0 -0
- package/dist/field-families.js +0 -0
- package/dist/form-layout.js +0 -0
- package/dist/form-support.js +12 -2
- package/dist/goal-routes.js +0 -0
- package/dist/governed-execution.js +0 -0
- package/dist/human-summary.js +0 -0
- package/dist/identity.js +0 -0
- package/dist/intent/baseline.js +0 -0
- package/dist/intent/capabilities/bizservice.js +0 -0
- package/dist/intent/capabilities/businessrule.js +0 -0
- package/dist/intent/capabilities/datarule.js +0 -0
- package/dist/intent/capabilities/field.js +0 -0
- package/dist/intent/capabilities/form.js +0 -0
- package/dist/intent/capabilities/index.js +0 -0
- package/dist/intent/capabilities/listview.js +0 -0
- package/dist/intent/capabilities/model.js +0 -0
- package/dist/intent/capabilities/onlinejs.js +0 -0
- package/dist/intent/capabilities/report.js +0 -0
- package/dist/intent/capabilities/workflow.js +0 -0
- package/dist/intent/capability.js +0 -0
- package/dist/intent/cli.js +0 -0
- package/dist/intent/compare.js +0 -0
- package/dist/intent/compiler.js +0 -0
- package/dist/intent/dsl.js +0 -0
- package/dist/intent/plan-file.js +0 -0
- package/dist/intent/readback.js +0 -0
- package/dist/intent/schema.js +0 -0
- package/dist/intent/validation.js +0 -0
- package/dist/intent/yaml.js +0 -0
- package/dist/json-column.js +0 -0
- package/dist/lanes/app-contract.js +0 -0
- package/dist/lanes/app-coverage.js +0 -0
- package/dist/lanes/app.js +0 -0
- package/dist/lanes/apply-changes.js +14 -1
- package/dist/lanes/b2-registration-contract.js +5 -0
- package/dist/lanes/b2-registration-coverage.js +1 -0
- package/dist/lanes/b2-registration.js +55 -6
- package/dist/lanes/businessrule-contract.js +0 -0
- package/dist/lanes/businessrule-coverage.js +0 -0
- package/dist/lanes/businessrule.js +0 -0
- package/dist/lanes/contract-support.js +0 -0
- package/dist/lanes/coverage-declaration.js +0 -0
- package/dist/lanes/datarule-contract.js +0 -0
- package/dist/lanes/datarule-coverage.js +0 -0
- package/dist/lanes/datarule-protocol.js +0 -0
- package/dist/lanes/datarule.js +0 -0
- package/dist/lanes/dictionary-contract.js +0 -0
- package/dist/lanes/dictionary-coverage.js +0 -0
- package/dist/lanes/dictionary.js +0 -0
- package/dist/lanes/environment-contract.js +0 -0
- package/dist/lanes/environment-coverage.js +0 -0
- package/dist/lanes/environment.js +0 -0
- package/dist/lanes/field-contract.js +3 -2
- package/dist/lanes/field-coverage.js +0 -0
- package/dist/lanes/field.js +268 -172
- package/dist/lanes/form-contract.js +2 -2
- package/dist/lanes/form-coverage.js +0 -0
- package/dist/lanes/form.js +4 -0
- package/dist/lanes/lane-ids.js +0 -0
- package/dist/lanes/list-view-contract.js +0 -0
- package/dist/lanes/list-view-coverage.js +0 -0
- package/dist/lanes/list-view-shapes.js +0 -0
- package/dist/lanes/list-view.js +0 -0
- package/dist/lanes/meta-contract.js +0 -0
- package/dist/lanes/meta.js +11 -1
- package/dist/lanes/model-contract.js +0 -0
- package/dist/lanes/model-coverage.js +0 -0
- package/dist/lanes/model.js +1 -1
- package/dist/lanes/online-js-contract.js +0 -0
- package/dist/lanes/online-js-coverage.js +0 -0
- package/dist/lanes/online-js.js +0 -0
- package/dist/lanes/report-contract.js +0 -0
- package/dist/lanes/report-coverage.js +0 -0
- package/dist/lanes/report.js +0 -0
- package/dist/lanes/rule-graph.js +0 -0
- package/dist/lanes/rule-lifecycle-contract.js +0 -0
- package/dist/lanes/rule-lifecycle-coverage.js +0 -0
- package/dist/lanes/rule-lifecycle.js +0 -0
- package/dist/lanes/runtime-data-contract.js +0 -0
- package/dist/lanes/runtime-data-coverage.js +0 -0
- package/dist/lanes/runtime-data.js +0 -0
- package/dist/lanes/workflow-contract.js +0 -0
- package/dist/lanes/workflow-coverage.js +0 -0
- package/dist/lanes/workflow.js +2 -0
- package/dist/omc-core-lazy.js +6 -0
- package/dist/online-js-layout.js +0 -0
- package/dist/online-js-source.js +28 -28
- package/dist/package-tool.js +0 -0
- package/dist/package.js +0 -0
- package/dist/plan.js +11 -1
- package/dist/read.js +0 -0
- package/dist/redact.js +0 -0
- package/dist/rule-support.js +0 -0
- package/dist/runtime-support.js +0 -0
- package/dist/service-doctor.js +28 -0
- package/dist/session-manager.js +0 -0
- package/dist/session.js +38 -12
- package/dist/skills.js +0 -0
- package/dist/sqlite-warning-filter.js +27 -0
- package/dist/support.js +0 -0
- package/dist/tool-types.js +0 -0
- package/dist/tools.js +0 -0
- package/dist/usage-log.js +4 -0
- package/dist/wire.js +0 -0
- package/dist/workflow-support.js +0 -0
- package/dist/write-lease.js +0 -0
- package/dist/write-lock.js +0 -0
- package/dist/write.js +24 -2
- package/dist/zip.js +0 -0
- package/docs/tool-surface-map.md +42 -42
- package/package.json +71 -71
- package/skills/omc-acceptance-criteria.md +47 -47
- package/skills/omc-business-configuration.md +258 -257
- package/skills/omc-capabilities.md +197 -197
- package/skills/omc-capability-scouting.md +55 -55
- package/skills/omc-config-draft-review.md +171 -171
- package/skills/omc-five-piece-flow.md +35 -35
- package/skills/omc-glossary.md +86 -86
- package/skills/omc-refusals.md +110 -110
- package/skills/omc-requirement-analysis.md +161 -161
- package/skills/omc-requirement-vocabulary.md +51 -51
- package/skills/omc-start-here.md +82 -82
- package/skills/omc-tool-selection.md +119 -119
- package/skills/omc-write-hazards.md +88 -87
- package/dist/approval-token.js +0 -102
- package/dist/server.js +0 -92
package/dist/lanes/field.js
CHANGED
|
@@ -10,6 +10,7 @@ import { syncFormConditionDataRules } from "./datarule.js";
|
|
|
10
10
|
import { FORM_CONDITION_OPTION_KEYS } from "./datarule-protocol.js";
|
|
11
11
|
import { defineTool, ToolRefusalError } from "../tool-types.js";
|
|
12
12
|
import { wireCall, wireRows, WireError } from "../wire.js";
|
|
13
|
+
import { executeFormDraftWrite, executeFormPublishWrite } from "../form-support.js";
|
|
13
14
|
import { FIELD_LANE_CONTRACT } from "./field-contract.js";
|
|
14
15
|
const FIELD_LANE = FIELD_LANE_CONTRACT.lane;
|
|
15
16
|
const FIELD_TOOLS = FIELD_LANE_CONTRACT.tools;
|
|
@@ -181,184 +182,21 @@ export function registerFieldLane(tools) {
|
|
|
181
182
|
relativeCode: relativeCodeArg,
|
|
182
183
|
subFields: subFieldArg,
|
|
183
184
|
propertyEmpty: z.boolean().optional().describe("覆盖数据模型层的不可为空声明;传 false 可把历史误建的字段改回可为空(必填/只读应在表单与流程节点配置)"),
|
|
185
|
+
cascadeRepublish: z
|
|
186
|
+
.boolean()
|
|
187
|
+
.optional()
|
|
188
|
+
.describe("true 时一调级联生效:field.update → field.publish → form.draft(rebuild) → form.publish(force)。表单按 published fields 全量重建默认表单脚手架(保留 non-structural 控件 options);无跨端点事务,中途失败按 partial steps(failedStep+envelope)如实报告,默认不级联"),
|
|
184
189
|
}, async (session, args) => {
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
rows = wireRows(published.data);
|
|
190
|
-
}
|
|
191
|
-
const record = rows.find((row) => (row.code ?? row.propertyCode) === args.code);
|
|
192
|
-
if (!record)
|
|
193
|
-
throw new WireError("business", `field ${args.code} not found on ${args.schemaCode}`, 301001);
|
|
194
|
-
const update = {
|
|
195
|
-
schemaCode: args.schemaCode,
|
|
196
|
-
code: args.code,
|
|
197
|
-
base: record,
|
|
198
|
-
...(args.name !== undefined ? { name: args.name } : {}),
|
|
199
|
-
...(args.name_i18n !== undefined ? { name_i18n: args.name_i18n } : {}),
|
|
200
|
-
...(args.options !== undefined ? { options: args.options } : {}),
|
|
201
|
-
...(args.choices !== undefined ? { choices: args.choices } : {}),
|
|
202
|
-
...(args.relativeCode !== undefined ? { relativeCode: args.relativeCode } : {}),
|
|
203
|
-
...(args.subFields !== undefined ? { subFields: args.subFields } : {}),
|
|
204
|
-
...(args.propertyEmpty !== undefined ? { propertyEmpty: args.propertyEmpty } : {}),
|
|
205
|
-
};
|
|
206
|
-
const prepared = prepareFieldUpdate(update);
|
|
207
|
-
const performed = await wireCall(session, { path: ENDPOINTS.fieldUpdate, method: "POST", body: fieldUpdateWireBody(update, prepared) });
|
|
208
|
-
const readback = await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: "false" } });
|
|
209
|
-
const after = wireRows(readback.data).find((row) => (row.code ?? row.propertyCode) === args.code);
|
|
210
|
-
// Round 4 (B): a form-interaction condition must exist on BOTH faces to be
|
|
211
|
-
// complete — field options (portal runtime) AND a bizdatarule type3/4/5
|
|
212
|
-
// row (designer design-time Set/Unset). field.update historically wrote
|
|
213
|
-
// only options, so CLI-authored conditions showed "Unset" in the designer.
|
|
214
|
-
// Pair the changed condition key(s) into the rule projection; a sync
|
|
215
|
-
// failure never hides the field write (it is surfaced as formConditionSync.error).
|
|
216
|
-
let formConditionSync;
|
|
217
|
-
let formConditionSyncError;
|
|
218
|
-
if (args.options !== undefined && !prepared.isChildTable) {
|
|
219
|
-
const previous = decodeJsonObjectColumn(record.options, { invalid: "throw", array: "accept", object: "accept" }) ?? {};
|
|
220
|
-
try {
|
|
221
|
-
const sync = await syncFormConditionDataRules(session, args.schemaCode, args.code, previous, prepared.options);
|
|
222
|
-
if (sync.created.length + sync.updated.length + sync.deleted.length + sync.skipped.length > 0)
|
|
223
|
-
formConditionSync = sync;
|
|
224
|
-
}
|
|
225
|
-
catch (error) {
|
|
226
|
-
formConditionSyncError = error instanceof Error ? error.message : String(error);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
// Round 4: echo the EFFECTIVE stored options (parsed), not just presence.
|
|
230
|
-
// An agent setting readonlyFormula/requiredFormula/scale had no way to
|
|
231
|
-
// confirm the write landed — the readback only named the field. Field
|
|
232
|
-
// options are the form-interaction contract (ADR 0005), so read them back.
|
|
233
|
-
const storedOptions = after !== undefined ? parseFieldOptionsForReadback(after.options) : undefined;
|
|
234
|
-
return {
|
|
235
|
-
performed: { errcode: performed.errcode },
|
|
236
|
-
readback: {
|
|
237
|
-
fieldPresent: after !== undefined,
|
|
238
|
-
...(after?.name !== undefined ? { name: after.name } : {}),
|
|
239
|
-
...(typeof after?.name_i18n === "string" ? { name_i18n: after.name_i18n } : {}),
|
|
240
|
-
...(after?.type !== undefined || after?.propertyType !== undefined ? { type: after?.type ?? after?.propertyType } : {}),
|
|
241
|
-
...(storedOptions !== undefined && storedOptions !== null ? { options: storedOptions } : {}),
|
|
242
|
-
...(formConditionSync !== undefined ? { formConditionSync } : {}),
|
|
243
|
-
...(formConditionSyncError !== undefined ? { formConditionSync: { error: formConditionSyncError } } : {}),
|
|
244
|
-
},
|
|
245
|
-
};
|
|
190
|
+
const outcome = await runFieldUpdate(session, args);
|
|
191
|
+
if (args.cascadeRepublish !== true)
|
|
192
|
+
return outcome;
|
|
193
|
+
return runFieldUpdateCascade(session, args, outcome);
|
|
246
194
|
}, (args) => assertNameI18nArg(args.name_i18n));
|
|
247
195
|
writeTool(tools, "field.publish", FIELD_LANE, FIELD_TOOLS["field.publish"].scope, FIELD_TOOLS["field.publish"].description, {
|
|
248
196
|
schemaCode: z.string().min(1),
|
|
249
197
|
code: z.string().min(1).optional().describe("optional single field to verify on the published face"),
|
|
250
198
|
codes: z.array(z.string().min(1)).optional().describe("optional list of fields to verify; publishing is model-scoped, do NOT call once per field"),
|
|
251
|
-
}, async (session, args) =>
|
|
252
|
-
// bizproperty/publish is MODEL-SCOPED: one call publishes every draft
|
|
253
|
-
// field of the schema. `code`/`codes` only select which fields to verify
|
|
254
|
-
// on the published face after the single publish; omit them to publish
|
|
255
|
-
// and read back all codes (the batch path).
|
|
256
|
-
//
|
|
257
|
-
// Ticket 81 idempotent retry: with explicit code/codes, read the
|
|
258
|
-
// published face FIRST (a retry after a timeout hits the wire read
|
|
259
|
-
// cache — zero extra wires) and skip the publish POST entirely when
|
|
260
|
-
// every requested field is already published. A genuinely missing
|
|
261
|
-
// field still drives the publish.
|
|
262
|
-
const requestedCodes = [...(args.code ? [args.code] : []), ...(args.codes ?? [])];
|
|
263
|
-
const codeOf = (row) => String(row.code ?? row.propertyCode ?? "");
|
|
264
|
-
// cpdocs base3.0 parity (cp-bizproperty verifyPlan): presence on the
|
|
265
|
-
// published LIST face is proven later, against a map of each field's
|
|
266
|
-
// pre-publish DRAFT shape (name + type). The draft face must be
|
|
267
|
-
// snapshotted BEFORE the publish because a model-scoped publish can leave
|
|
268
|
-
// it empty; the draft row is the caller-visible expectation.
|
|
269
|
-
const draftSnapshot = new Map(wireRows((await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: "false" } })).data)
|
|
270
|
-
.map((row) => [codeOf(row), { name: String(row.name ?? ""), propertyType: Number(row.type ?? row.propertyType) }]));
|
|
271
|
-
const verifyFieldDetails = async (targets) => {
|
|
272
|
-
const bounded = targets.slice(0, 25);
|
|
273
|
-
const mismatches = [];
|
|
274
|
-
for (const code of bounded) {
|
|
275
|
-
const expected = draftSnapshot.get(code);
|
|
276
|
-
if (!expected)
|
|
277
|
-
continue;
|
|
278
|
-
let detail;
|
|
279
|
-
try {
|
|
280
|
-
detail = (await wireCall(session, { path: ENDPOINTS.fieldGet, query: { schemaCode: args.schemaCode, propertyCode: code } })).data;
|
|
281
|
-
}
|
|
282
|
-
catch {
|
|
283
|
-
// The detail endpoint is optional across builds: report the
|
|
284
|
-
// verification as unavailable rather than failing the publish.
|
|
285
|
-
return { mode: "unavailable", verified: false, note: "bizproperty/get 不可用,字段详情未核对(仅列表面存在性)" };
|
|
286
|
-
}
|
|
287
|
-
const actual = { name: String(detail.name ?? ""), propertyType: Number(detail.propertyType ?? detail.type) };
|
|
288
|
-
if (actual.name !== expected.name || actual.propertyType !== expected.propertyType)
|
|
289
|
-
mismatches.push({ code, expected, actual });
|
|
290
|
-
}
|
|
291
|
-
return { mode: "detail", verified: true, checked: bounded.length, ok: mismatches.length === 0, ...(mismatches.length > 0 ? { mismatches } : {}) };
|
|
292
|
-
};
|
|
293
|
-
const preRead = await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: "true" } });
|
|
294
|
-
const prePublished = wireRows(preRead.data).map(codeOf).filter((code) => code.length > 0);
|
|
295
|
-
if (requestedCodes.length > 0 && requestedCodes.every((code) => prePublished.includes(code))) {
|
|
296
|
-
return {
|
|
297
|
-
performed: { skipped: "already-published", scope: "model" },
|
|
298
|
-
readback: {
|
|
299
|
-
publishedCodes: prePublished,
|
|
300
|
-
...(args.code ? { publishedFieldPresent: true } : {}),
|
|
301
|
-
...(args.codes ? { publishedFieldsPresent: Object.fromEntries(args.codes.map((code) => [code, true])) } : {}),
|
|
302
|
-
allRequestedPresent: true,
|
|
303
|
-
verification: await verifyFieldDetails(requestedCodes),
|
|
304
|
-
},
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
// Ticket 79 live-fidelity (docker log evidence, 2026-09-13 21:43-21:45,
|
|
308
|
-
// cloud8_launcher log_error.log + nginx access timings): under DB
|
|
309
|
-
// connection-pool contention a model publish can fail with a transient
|
|
310
|
-
// `表单:<t>_<schema>不存在列:<col>` 50000 — the addIndexes/updateColumns
|
|
311
|
-
// assert reads the serializer view while the failed attempt's DDL
|
|
312
|
-
// auto-commits, so the state SELF-HEALS across retries (live: three
|
|
313
|
-
// consecutive attempts each healed one column, the fourth succeeded).
|
|
314
|
-
// Retry that exact signature up to 3 attempts; anything else rethrows.
|
|
315
|
-
let performed;
|
|
316
|
-
// Backoff matches the observed healing cadence on the small env (live:
|
|
317
|
-
// attempts ~25s/73s apart each reconciled one column).
|
|
318
|
-
const backoffMs = [2000, 5000];
|
|
319
|
-
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
320
|
-
try {
|
|
321
|
-
performed = await wireCall(session, {
|
|
322
|
-
path: ENDPOINTS.fieldPublish,
|
|
323
|
-
method: "POST",
|
|
324
|
-
body: { bizSchemaCode: args.schemaCode, sendMessage: false },
|
|
325
|
-
});
|
|
326
|
-
break;
|
|
327
|
-
}
|
|
328
|
-
catch (error) {
|
|
329
|
-
const transientColumnSync = error instanceof WireError && error.kind === "business" && error.errcode === 50000 && error.message.includes("不存在列");
|
|
330
|
-
if (!(transientColumnSync && attempt < 3))
|
|
331
|
-
throw error;
|
|
332
|
-
await new Promise((resolve) => setTimeout(resolve, backoffMs[attempt - 1] ?? 5000));
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
const publishOutcome = performed;
|
|
336
|
-
const readback = await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: "true" } });
|
|
337
|
-
const publishedCodes = wireRows(readback.data).map(codeOf).filter((code) => code.length > 0);
|
|
338
|
-
const requested = [...(args.code ? [args.code] : []), ...(args.codes ?? [])];
|
|
339
|
-
const present = requested.filter((code) => publishedCodes.includes(code));
|
|
340
|
-
const targets = (requested.length > 0 ? requested : [...draftSnapshot.keys()]).filter((code) => publishedCodes.includes(code));
|
|
341
|
-
const verification = await verifyFieldDetails(targets);
|
|
342
|
-
const mismatches = verification.mismatches;
|
|
343
|
-
if (mismatches !== undefined)
|
|
344
|
-
throw new ToolRefusalError({
|
|
345
|
-
status: "refused",
|
|
346
|
-
blocker: "field-publish-readback-mismatch",
|
|
347
|
-
performed: { errcode: publishOutcome.errcode, scope: "model" },
|
|
348
|
-
mismatches,
|
|
349
|
-
message: `字段已发布,但发布后详情回读与草稿面不一致(${mismatches.map((item) => String(item.code)).join("、")}):这是元数据与预期形状相异的信号,先核对再决定后续;不要当作成功`,
|
|
350
|
-
});
|
|
351
|
-
return {
|
|
352
|
-
performed: { errcode: publishOutcome.errcode, scope: "model" },
|
|
353
|
-
readback: {
|
|
354
|
-
publishedCodes,
|
|
355
|
-
...(args.code ? { publishedFieldPresent: publishedCodes.includes(args.code) } : {}),
|
|
356
|
-
...(args.codes ? { publishedFieldsPresent: Object.fromEntries(args.codes.map((code) => [code, publishedCodes.includes(code)])) } : {}),
|
|
357
|
-
...(requested.length > 0 ? { allRequestedPresent: present.length === requested.length } : {}),
|
|
358
|
-
verification,
|
|
359
|
-
},
|
|
360
|
-
};
|
|
361
|
-
});
|
|
199
|
+
}, async (session, args) => executeFieldPublish(session, args));
|
|
362
200
|
writeTool(tools, "field.delete", FIELD_LANE, FIELD_TOOLS["field.delete"].scope, FIELD_TOOLS["field.delete"].description, { schemaCode: z.string().min(1), code: z.string().min(1) }, async (session, args) => {
|
|
363
201
|
const performed = await wireCall(session, {
|
|
364
202
|
path: ENDPOINTS.fieldForceDelete,
|
|
@@ -371,4 +209,262 @@ export function registerFieldLane(tools) {
|
|
|
371
209
|
return { performed: { errcode: performed.errcode }, readback: { absentDraft: gone(draft.data), absentPublished: gone(published.data) } };
|
|
372
210
|
});
|
|
373
211
|
}
|
|
212
|
+
/** The proven field.update write body — shared by the write tool and the cascade path. */
|
|
213
|
+
async function runFieldUpdate(session, args) {
|
|
214
|
+
const listed = await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: "false" } });
|
|
215
|
+
let rows = wireRows(listed.data);
|
|
216
|
+
if (!rows.some((row) => (row.code ?? row.propertyCode) === args.code)) {
|
|
217
|
+
const published = await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: "true" } });
|
|
218
|
+
rows = wireRows(published.data);
|
|
219
|
+
}
|
|
220
|
+
const record = rows.find((row) => (row.code ?? row.propertyCode) === args.code);
|
|
221
|
+
if (!record)
|
|
222
|
+
throw new WireError("business", `field ${args.code} not found on ${args.schemaCode}`, 301001);
|
|
223
|
+
const update = {
|
|
224
|
+
schemaCode: args.schemaCode,
|
|
225
|
+
code: args.code,
|
|
226
|
+
base: record,
|
|
227
|
+
...(args.name !== undefined ? { name: args.name } : {}),
|
|
228
|
+
...(args.name_i18n !== undefined ? { name_i18n: args.name_i18n } : {}),
|
|
229
|
+
...(args.options !== undefined ? { options: args.options } : {}),
|
|
230
|
+
...(args.choices !== undefined ? { choices: args.choices } : {}),
|
|
231
|
+
...(args.relativeCode !== undefined ? { relativeCode: args.relativeCode } : {}),
|
|
232
|
+
...(args.subFields !== undefined ? { subFields: args.subFields } : {}),
|
|
233
|
+
...(args.propertyEmpty !== undefined ? { propertyEmpty: args.propertyEmpty } : {}),
|
|
234
|
+
};
|
|
235
|
+
const prepared = prepareFieldUpdate(update);
|
|
236
|
+
const performed = await wireCall(session, { path: ENDPOINTS.fieldUpdate, method: "POST", body: fieldUpdateWireBody(update, prepared) });
|
|
237
|
+
const readback = await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: "false" } });
|
|
238
|
+
const after = wireRows(readback.data).find((row) => (row.code ?? row.propertyCode) === args.code);
|
|
239
|
+
// Round 4 (B): a form-interaction condition must exist on BOTH faces to be
|
|
240
|
+
// complete — field options (portal runtime) AND a bizdatarule type3/4/5
|
|
241
|
+
// row (designer design-time Set/Unset). field.update historically wrote
|
|
242
|
+
// only options, so CLI-authored conditions showed "Unset" in the designer.
|
|
243
|
+
// Pair the changed condition key(s) into the rule projection; a sync
|
|
244
|
+
// failure never hides the field write (it is surfaced as formConditionSync.error).
|
|
245
|
+
let formConditionSync;
|
|
246
|
+
let formConditionSyncError;
|
|
247
|
+
if (args.options !== undefined && !prepared.isChildTable) {
|
|
248
|
+
const previous = decodeJsonObjectColumn(record.options, { invalid: "throw", array: "accept", object: "accept" }) ?? {};
|
|
249
|
+
try {
|
|
250
|
+
const sync = await syncFormConditionDataRules(session, args.schemaCode, args.code, previous, prepared.options);
|
|
251
|
+
if (sync.created.length + sync.updated.length + sync.deleted.length + sync.skipped.length > 0)
|
|
252
|
+
formConditionSync = sync;
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
formConditionSyncError = error instanceof Error ? error.message : String(error);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Round 4: echo the EFFECTIVE stored options (parsed), not just presence.
|
|
259
|
+
// An agent setting readonlyFormula/requiredFormula/scale had no way to
|
|
260
|
+
// confirm the write landed — the readback only named the field. Field
|
|
261
|
+
// options are the form-interaction contract (ADR 0005), so read them back.
|
|
262
|
+
const storedOptions = after !== undefined ? parseFieldOptionsForReadback(after.options) : undefined;
|
|
263
|
+
return {
|
|
264
|
+
performed: { errcode: performed.errcode },
|
|
265
|
+
readback: {
|
|
266
|
+
fieldPresent: after !== undefined,
|
|
267
|
+
...(after?.name !== undefined ? { name: after.name } : {}),
|
|
268
|
+
...(typeof after?.name_i18n === "string" ? { name_i18n: after.name_i18n } : {}),
|
|
269
|
+
...(after?.type !== undefined || after?.propertyType !== undefined ? { type: after?.type ?? after?.propertyType } : {}),
|
|
270
|
+
...(storedOptions !== undefined && storedOptions !== null ? { options: storedOptions } : {}),
|
|
271
|
+
...(formConditionSync !== undefined ? { formConditionSync } : {}),
|
|
272
|
+
...(formConditionSyncError !== undefined ? { formConditionSync: { error: formConditionSyncError } } : {}),
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* The proven field.publish write body. Shared by the field.publish write tool
|
|
278
|
+
* and the field.update cascade path — a cascade must never bypass the
|
|
279
|
+
* already-published short-circuit semantics, the transient-column retry, or
|
|
280
|
+
* the post-publish detail verification by inlining a bare wire call.
|
|
281
|
+
* `force:true` (cascade only) skips the already-published short-circuit: a
|
|
282
|
+
* cascaded republish exists precisely because the PUBLISHED face carries the
|
|
283
|
+
* stale shape while the field is present there.
|
|
284
|
+
*/
|
|
285
|
+
export async function executeFieldPublish(session, args) {
|
|
286
|
+
// bizproperty/publish is MODEL-SCOPED: one call publishes every draft
|
|
287
|
+
// field of the schema. `code`/`codes` only select which fields to verify
|
|
288
|
+
// on the published face after the single publish; omit them to publish
|
|
289
|
+
// and read back all codes (the batch path).
|
|
290
|
+
//
|
|
291
|
+
// Ticket 81 idempotent retry: with explicit code/codes, read the
|
|
292
|
+
// published face FIRST (a retry after a timeout hits the wire read
|
|
293
|
+
// cache — zero extra wires) and skip the publish POST entirely when
|
|
294
|
+
// every requested field is already published. A genuinely missing
|
|
295
|
+
// field still drives the publish.
|
|
296
|
+
const requestedCodes = [...(args.code ? [args.code] : []), ...(args.codes ?? [])];
|
|
297
|
+
const codeOf = (row) => String(row.code ?? row.propertyCode ?? "");
|
|
298
|
+
// cpdocs base3.0 parity (cp-bizproperty verifyPlan): presence on the
|
|
299
|
+
// published LIST face is proven later, against a map of each field's
|
|
300
|
+
// pre-publish DRAFT shape (name + type). The draft face must be
|
|
301
|
+
// snapshotted BEFORE the publish because a model-scoped publish can leave
|
|
302
|
+
// it empty; the draft row is the caller-visible expectation.
|
|
303
|
+
const draftSnapshot = new Map(wireRows((await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: "false" } })).data)
|
|
304
|
+
.map((row) => [codeOf(row), { name: String(row.name ?? ""), propertyType: Number(row.type ?? row.propertyType) }]));
|
|
305
|
+
const verifyFieldDetails = async (targets) => {
|
|
306
|
+
const bounded = targets.slice(0, 25);
|
|
307
|
+
const mismatches = [];
|
|
308
|
+
for (const code of bounded) {
|
|
309
|
+
const expected = draftSnapshot.get(code);
|
|
310
|
+
if (!expected)
|
|
311
|
+
continue;
|
|
312
|
+
let detail;
|
|
313
|
+
try {
|
|
314
|
+
detail = (await wireCall(session, { path: ENDPOINTS.fieldGet, query: { schemaCode: args.schemaCode, propertyCode: code } })).data;
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
// The detail endpoint is optional across builds: report the
|
|
318
|
+
// verification as unavailable rather than failing the publish.
|
|
319
|
+
return { mode: "unavailable", verified: false, note: "bizproperty/get 不可用,字段详情未核对(仅列表面存在性)" };
|
|
320
|
+
}
|
|
321
|
+
const actual = { name: String(detail.name ?? ""), propertyType: Number(detail.propertyType ?? detail.type) };
|
|
322
|
+
if (actual.name !== expected.name || actual.propertyType !== expected.propertyType)
|
|
323
|
+
mismatches.push({ code, expected, actual });
|
|
324
|
+
}
|
|
325
|
+
return { mode: "detail", verified: true, checked: bounded.length, ok: mismatches.length === 0, ...(mismatches.length > 0 ? { mismatches } : {}) };
|
|
326
|
+
};
|
|
327
|
+
const preRead = await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: "true" } });
|
|
328
|
+
const prePublished = wireRows(preRead.data).map(codeOf).filter((code) => code.length > 0);
|
|
329
|
+
if (args.force !== true && requestedCodes.length > 0 && requestedCodes.every((code) => prePublished.includes(code))) {
|
|
330
|
+
return {
|
|
331
|
+
performed: { skipped: "already-published", scope: "model" },
|
|
332
|
+
readback: {
|
|
333
|
+
publishedCodes: prePublished,
|
|
334
|
+
...(args.code ? { publishedFieldPresent: true } : {}),
|
|
335
|
+
...(args.codes ? { publishedFieldsPresent: Object.fromEntries(args.codes.map((code) => [code, true])) } : {}),
|
|
336
|
+
allRequestedPresent: true,
|
|
337
|
+
verification: await verifyFieldDetails(requestedCodes),
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
// Ticket 79 live-fidelity (docker log evidence, 2026-09-13 21:43-21:45,
|
|
342
|
+
// cloud8_launcher log_error.log + nginx access timings): under DB
|
|
343
|
+
// connection-pool contention a model publish can fail with a transient
|
|
344
|
+
// `表单:<t>_<schema>不存在列:<col>` 50000 — the addIndexes/updateColumns
|
|
345
|
+
// assert reads the serializer view while the failed attempt's DDL
|
|
346
|
+
// auto-commits, so the state SELF-HEALS across retries (live: three
|
|
347
|
+
// consecutive attempts each healed one column, the fourth succeeded).
|
|
348
|
+
// Retry that exact signature up to 3 attempts; anything else rethrows.
|
|
349
|
+
let performed;
|
|
350
|
+
// Backoff matches the observed healing cadence on the small env (live:
|
|
351
|
+
// attempts ~25s/73s apart each reconciled one column).
|
|
352
|
+
const backoffMs = [2000, 5000];
|
|
353
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
354
|
+
try {
|
|
355
|
+
performed = await wireCall(session, {
|
|
356
|
+
path: ENDPOINTS.fieldPublish,
|
|
357
|
+
method: "POST",
|
|
358
|
+
body: { bizSchemaCode: args.schemaCode, sendMessage: false },
|
|
359
|
+
});
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
const transientColumnSync = error instanceof WireError && error.kind === "business" && error.errcode === 50000 && error.message.includes("不存在列");
|
|
364
|
+
if (!(transientColumnSync && attempt < 3))
|
|
365
|
+
throw error;
|
|
366
|
+
await new Promise((resolve) => setTimeout(resolve, backoffMs[attempt - 1] ?? 5000));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const publishOutcome = performed;
|
|
370
|
+
const readback = await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: "true" } });
|
|
371
|
+
const publishedCodes = wireRows(readback.data).map(codeOf).filter((code) => code.length > 0);
|
|
372
|
+
const requested = [...(args.code ? [args.code] : []), ...(args.codes ?? [])];
|
|
373
|
+
const present = requested.filter((code) => publishedCodes.includes(code));
|
|
374
|
+
const targets = (requested.length > 0 ? requested : [...draftSnapshot.keys()]).filter((code) => publishedCodes.includes(code));
|
|
375
|
+
const verification = await verifyFieldDetails(targets);
|
|
376
|
+
const mismatches = verification.mismatches;
|
|
377
|
+
if (mismatches !== undefined)
|
|
378
|
+
throw new ToolRefusalError({
|
|
379
|
+
status: "refused",
|
|
380
|
+
blocker: "field-publish-readback-mismatch",
|
|
381
|
+
performed: { errcode: publishOutcome.errcode, scope: "model" },
|
|
382
|
+
mismatches,
|
|
383
|
+
message: `字段已发布,但发布后详情回读与草稿面不一致(${mismatches.map((item) => String(item.code)).join("、")}):这是元数据与预期形状相异的信号,先核对再决定后续;不要当作成功`,
|
|
384
|
+
});
|
|
385
|
+
return {
|
|
386
|
+
performed: { errcode: publishOutcome.errcode, scope: "model" },
|
|
387
|
+
readback: {
|
|
388
|
+
publishedCodes,
|
|
389
|
+
...(args.code ? { publishedFieldPresent: publishedCodes.includes(args.code) } : {}),
|
|
390
|
+
...(args.codes ? { publishedFieldsPresent: Object.fromEntries(args.codes.map((code) => [code, publishedCodes.includes(code)])) } : {}),
|
|
391
|
+
...(requested.length > 0 ? { allRequestedPresent: present.length === requested.length } : {}),
|
|
392
|
+
verification,
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Ticket T06 — field.update cascadeRepublish. After a successful field.update,
|
|
398
|
+
* republish the model's fields, rebuild the default form from the PUBLISHED
|
|
399
|
+
* face (non-structural control options are preserved by the shared scaffold),
|
|
400
|
+
* and force-promote the rebuilt draft. Steps follow the configure_model
|
|
401
|
+
* protocol: a mid-chain failure returns {status:"partial", steps,
|
|
402
|
+
* failedStep, envelope} — the completed prefix is reported honestly; there is
|
|
403
|
+
* no cross-endpoint transaction to roll back to.
|
|
404
|
+
*/
|
|
405
|
+
async function runFieldUpdateCascade(session, args, update) {
|
|
406
|
+
const steps = [{ step: "field.update", errcode: update.performed.errcode, readback: update.readback }];
|
|
407
|
+
// The cascade targets the model's default form slot (sheetCode = schemaCode,
|
|
408
|
+
// the form lane's proven default rule).
|
|
409
|
+
const sheetCode = args.schemaCode;
|
|
410
|
+
const stop = (index, tool, error) => {
|
|
411
|
+
const envelope = error instanceof ToolRefusalError
|
|
412
|
+
? Object.fromEntries(Object.entries(error.payload).filter(([key]) => key !== "status"))
|
|
413
|
+
: error instanceof WireError
|
|
414
|
+
? { blocker: `wire-${error.kind}`, errcode: error.errcode, message: error.message }
|
|
415
|
+
: { blocker: "field-update-cascade-step-failed", message: error instanceof Error ? error.message : String(error) };
|
|
416
|
+
return {
|
|
417
|
+
status: "partial",
|
|
418
|
+
steps,
|
|
419
|
+
failedStep: { index, tool },
|
|
420
|
+
envelope: { ...envelope, message: `${String(envelope.message ?? "步骤失败")};后续步骤未执行` },
|
|
421
|
+
};
|
|
422
|
+
};
|
|
423
|
+
const stepOf = (step, outcome) => {
|
|
424
|
+
const skipped = outcome.performed.skipped;
|
|
425
|
+
return { step, ...(skipped !== undefined ? { skipped } : { errcode: outcome.performed.errcode }), readback: outcome.readback };
|
|
426
|
+
};
|
|
427
|
+
let published;
|
|
428
|
+
let formPublished;
|
|
429
|
+
try {
|
|
430
|
+
// force: the field IS present on the published face — with the stale
|
|
431
|
+
// shape. The cascade exists to replace that shape, so the
|
|
432
|
+
// already-published short-circuit must not skip the publish.
|
|
433
|
+
published = await executeFieldPublish(session, { schemaCode: args.schemaCode, code: args.code, force: true });
|
|
434
|
+
steps.push(stepOf("field.publish", published));
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
return stop(1, "field.publish", error);
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
// No fieldCodes / no caller layout: the scaffold binds EVERY published
|
|
441
|
+
// field (a draftAttributesJson would bypass the published-face source).
|
|
442
|
+
const draft = await executeFormDraftWrite(session, { schemaCode: args.schemaCode, sheetCode, rebuild: true });
|
|
443
|
+
steps.push(stepOf("form.draft", draft));
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
return stop(2, "form.draft", error);
|
|
447
|
+
}
|
|
448
|
+
try {
|
|
449
|
+
// force: the form was published before the cascade; without force the
|
|
450
|
+
// promotion would short-circuit on `published === true`.
|
|
451
|
+
formPublished = await executeFormPublishWrite(session, { schemaCode: args.schemaCode, sheetCode, force: true });
|
|
452
|
+
steps.push(stepOf("form.publish", formPublished));
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
return stop(3, "form.publish", error);
|
|
456
|
+
}
|
|
457
|
+
const fieldReadback = published.readback;
|
|
458
|
+
return {
|
|
459
|
+
performed: { errcode: steps[0].errcode, scope: "cascade" },
|
|
460
|
+
steps,
|
|
461
|
+
readback: {
|
|
462
|
+
...update.readback,
|
|
463
|
+
cascade: {
|
|
464
|
+
fieldPublished: fieldReadback.publishedFieldPresent === true,
|
|
465
|
+
formPublished: formPublished.readback.published === true,
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
}
|
|
374
470
|
//# sourceMappingURL=field.js.map
|
|
@@ -19,7 +19,7 @@ export const FORM_WIRE = Object.freeze({
|
|
|
19
19
|
});
|
|
20
20
|
const FORM_GET_DESCRIPTION = '读取默认表单(bizsheet/get)的发布状态与控件清单(紧凑默认)。**发布态以返回的 `published` / `existDraft` 布尔为准**(`published:true`=已发布;`published:false,existDraft:true`=只有草稿,**未发布**)——不要用 `publishedControlCodes` 是否非空来推断(草稿字段会让它非空,误判为已发布)。**每个模型交付前其默认表单必须 `published:true`**:用 `configure_form` 或 `form.draft(rebuild:true)`+`form.publish` 发布,并逐模型 `form.get` 核对。传 `controls:["code",…]` 只取指定控件的 options(如关联控件的 mappings);传 `full:true` 才返回 draft/published 的完整 view/attributes/html/actions JSON(actions 列为设计器保存/发布的表单级按钮动作)。体积大,仅在需要整体布局时用。';
|
|
21
21
|
const FORM_DRAFT_DESCRIPTION = "Save the default-form DRAFT (bizsheet/update). Reads the current sheet, merges the caller's explicit draftViewJson/draftAttributesJson (or preserves the existing draft); when both are absent on a fresh default form, composes the designer-standard scaffold from the model's published fields. A redundant field (relation-mapping carry-through target) always stays bound; with no workflow it is hidden in the design (options.visible=false), with a workflow it stays visible for per-node propertyPermissions. Pass `draftHtmlJson` to thread custom online-script HTML through. Empty-layout guard: explicitly passing \"[]\"/\"{}\" view/attributes is refused (form-empty-layout) — the platform designer's Save is NOT lossless and clears drafts to exactly that shape; escape with allowEmptyLayout:true, and recover cleared drafts from the form version history (/api/api/app/bizsheet/history/list).";
|
|
22
|
-
const FORM_PUBLISH_DESCRIPTION = "Publish the default form (bizsheet/publish, proven shape: draft*Json promoted to published*Json verbatim) with published-face readback. Degrade guard: refuses to promote a draft carrying FEWER controls than the published face (form-publish-degrade) — the designer's Save clears drafts non-losslessly; recover via the form version history (/api/api/app/bizsheet/history/list) and rewrite the draft first, or pass allowDegrade:true to force the reduction.";
|
|
22
|
+
const FORM_PUBLISH_DESCRIPTION = "Publish the default form (bizsheet/publish, proven shape: draft*Json promoted to published*Json verbatim) with published-face readback. Degrade guard: refuses to promote a draft carrying FEWER controls than the published face (form-publish-degrade) — the designer's Save clears drafts non-losslessly; recover via the form version history (/api/api/app/bizsheet/history/list) and rewrite the draft first, or pass allowDegrade:true to force the reduction. rebuild:true is the one-call refresh: discard the existing draft, re-scaffold from the published fields (non-structural control options preserved), then force-promote — use it after a field shape change.";
|
|
23
23
|
const CONFIGURE_FORM_DESCRIPTION = "High-level: bind fields into the default form and publish it (form.draft → form.publish), subject to the write permission gate. Delegates to the proven form.draft scaffold + form.publish paths so the real empty-form layout (and any caller-supplied draftHtmlJson) is persisted and promoted — never bypasses with a shortcut body. `sheetCode` is optional: omitted means the model's default sheet (sheetCode = schemaCode).";
|
|
24
24
|
export const FORM_LANE_CONTRACT = Object.freeze({
|
|
25
25
|
lane: LANES.form,
|
|
@@ -77,7 +77,7 @@ export function assertFormContract() {
|
|
|
77
77
|
assertDescriptionCarries(FORM_LANE_CONTRACT, "form.draft", ["bizsheet/update", "draftViewJson", "draftAttributesJson", "scaffold", "draftHtmlJson", "form-empty-layout"]);
|
|
78
78
|
if (!FORM_WIRE.redundantFieldVisibility.includes("propertyPermissions"))
|
|
79
79
|
throw new Error("form contract must carry the redundant-field visibility (propertyPermissions) fact");
|
|
80
|
-
assertDescriptionCarries(FORM_LANE_CONTRACT, "form.publish", ["bizsheet/publish", "draft*Json", "published*Json", "verbatim", "form-publish-degrade", "history/list"]);
|
|
80
|
+
assertDescriptionCarries(FORM_LANE_CONTRACT, "form.publish", ["bizsheet/publish", "draft*Json", "published*Json", "verbatim", "form-publish-degrade", "history/list", "rebuild:true", "force-promote"]);
|
|
81
81
|
assertDescriptionCarries(FORM_LANE_CONTRACT, "configure_form", ["form.draft", "form.publish", "scaffold", "draftHtmlJson"]);
|
|
82
82
|
if (!FORM_WIRE.draftBody.includes("bizsheet/update"))
|
|
83
83
|
throw new Error("form contract must pin the draft update endpoint");
|
|
File without changes
|
package/dist/lanes/form.js
CHANGED
|
@@ -134,6 +134,10 @@ export function registerFormLane(tools) {
|
|
|
134
134
|
publishRemarks: z.string().optional(),
|
|
135
135
|
force: z.boolean().optional().describe("强制重新提拔(跳过 already-published 短路);重写草稿后的再发布需要它"),
|
|
136
136
|
allowDegrade: z.boolean().optional().describe("显式放行降级提拔(草稿控件数 < 已发布面);默认拒绝(form-publish-degrade),防把空/残草稿提拔成发布版"),
|
|
137
|
+
rebuild: z
|
|
138
|
+
.boolean()
|
|
139
|
+
.optional()
|
|
140
|
+
.describe("true 时一步刷新:先丢弃现有草稿、按已发布字段重建脚手架(保留非结构性控件 options),再强制提拔——改字段形状后刷新表单用一个调用;默认只发布现有草稿(无草稿时自动补默认)"),
|
|
137
141
|
}, async (session, args) => executeFormPublishWrite(session, { ...args, sheetCode: resolveSheetCode(args.schemaCode, args.sheetCode) }));
|
|
138
142
|
registerPackageTool(tools, {
|
|
139
143
|
name: "configure_form",
|
package/dist/lanes/lane-ids.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/dist/lanes/list-view.js
CHANGED
|
File without changes
|
|
File without changes
|
package/dist/lanes/meta.js
CHANGED
|
@@ -197,7 +197,7 @@ export function registerMetaLane(tools) {
|
|
|
197
197
|
layer: "meta",
|
|
198
198
|
kind: "meta",
|
|
199
199
|
description: META_TOOLS["doctor"].description,
|
|
200
|
-
inputSchema: { environment: environmentArg },
|
|
200
|
+
inputSchema: { environment: environmentArg, serviceCode: z.string().min(1).optional().describe("可选:体检单个业务集成服务的鉴权参数形状(掩码/占位/全 0 检出;非致命 WARN)") },
|
|
201
201
|
handler: async (ctx, args, self) => {
|
|
202
202
|
const store = ctx.sessions.config();
|
|
203
203
|
const steps = [];
|
|
@@ -243,6 +243,16 @@ export function registerMetaLane(tools) {
|
|
|
243
243
|
note: familyOk === null ? "缺少版本证据;写入将保守拒绝" : familyOk ? "兼容" : "不兼容;写入将保守拒绝",
|
|
244
244
|
});
|
|
245
245
|
steps.push({ step: "环境指纹", ok: true, fingerprint: session.environmentFingerprint() });
|
|
246
|
+
// T09 — optional per-service credential shape probe (non-fatal).
|
|
247
|
+
if (args.serviceCode !== undefined) {
|
|
248
|
+
const { probeServiceCredentials } = await import("../service-doctor.js");
|
|
249
|
+
const probe = await probeServiceCredentials(session, args.serviceCode);
|
|
250
|
+
steps.push(probe.status === "not-found"
|
|
251
|
+
? { step: "服务凭据", ok: null, serviceCode: probe.serviceCode, note: "未找到(get_by_code 无行)" }
|
|
252
|
+
: probe.status === "error"
|
|
253
|
+
? { step: "服务凭据", ok: null, serviceCode: probe.serviceCode, note: `探测失败:${probe.message}` }
|
|
254
|
+
: { step: "服务凭据", ok: probe.findings.length === 0, serviceCode: probe.serviceCode, adapterCode: probe.adapterCode, parameterCount: probe.parameterCount, findings: probe.findings, note: probe.findings.length === 0 ? "掩码/占位/全 0 均未检出" : "掩码/占位值不可用于真实调用;真值从任务环境事实取,不要抄 service.get 读回" });
|
|
255
|
+
}
|
|
246
256
|
return { status: "ok", steps };
|
|
247
257
|
}
|
|
248
258
|
catch (error) {
|
|
File without changes
|
|
File without changes
|
package/dist/lanes/model.js
CHANGED
|
@@ -916,7 +916,7 @@ export function registerModelLane(tools) {
|
|
|
916
916
|
throw new ToolRefusalError({ status: "refused", blocker: "configure-model-listview-unavailable", message: "listview.configure 未注册写执行" });
|
|
917
917
|
failed = await run(results.length, "listview.configure", async () => {
|
|
918
918
|
const outcome = await target.writeExecute(session, { schemaCode: args.code, ...args.listview });
|
|
919
|
-
return { step: "listview.configure", readback: outcome.readback };
|
|
919
|
+
return { step: "listview.configure", readback: "readback" in outcome ? outcome.readback : outcome.envelope };
|
|
920
920
|
});
|
|
921
921
|
if (failed)
|
|
922
922
|
return failed;
|
|
File without changes
|
|
File without changes
|
package/dist/lanes/online-js.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/dist/lanes/report.js
CHANGED
|
File without changes
|
package/dist/lanes/rule-graph.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/dist/lanes/workflow.js
CHANGED
|
@@ -1614,6 +1614,8 @@ export function registerWorkflowLane(tools) {
|
|
|
1614
1614
|
throw new ToolRefusalError({ status: "refused", blocker: "configure-workflow-step-unavailable", message: `configure_workflow 需要 ${toolName},但它未注册写执行` });
|
|
1615
1615
|
try {
|
|
1616
1616
|
const outcome = await target.writeExecute(session, toolArgs);
|
|
1617
|
+
if ("status" in outcome)
|
|
1618
|
+
throw new ToolRefusalError({ status: "refused", blocker: "configure-workflow-inner-stop", message: `步骤 ${toolName} 返回中途停止:${String(outcome.envelope?.message ?? outcome.status)}` });
|
|
1617
1619
|
steps.push({ step: toolName, ...outcome });
|
|
1618
1620
|
return outcome.readback;
|
|
1619
1621
|
}
|
package/dist/online-js-layout.js
CHANGED
|
File without changes
|