@stalfh233/omc-cli 0.4.15 → 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 +22 -2
- 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/args.js
CHANGED
|
File without changes
|
package/dist/artifacts.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/dist/bench/driver.js
CHANGED
|
File without changes
|
package/dist/bench/evals.js
CHANGED
|
File without changes
|
|
File without changes
|
package/dist/bench/instrument.js
CHANGED
|
File without changes
|
|
@@ -122,24 +122,24 @@ function freshHome(url) {
|
|
|
122
122
|
writeFileSync(join(home, "credentials"), JSON.stringify({ eval: { username: "tester", password: "eval-face-password" } }));
|
|
123
123
|
return home;
|
|
124
124
|
}
|
|
125
|
-
const LADDER_INTENT = `intentVersion: 1
|
|
126
|
-
models:
|
|
127
|
-
- code: eval_m1
|
|
128
|
-
name: 评测建模
|
|
129
|
-
fields:
|
|
130
|
-
- code: f_title
|
|
131
|
-
name: 标题
|
|
132
|
-
type: SHORT_TEXT
|
|
133
|
-
- code: f_amount
|
|
134
|
-
name: 金额
|
|
135
|
-
type: NUMERICAL
|
|
136
|
-
options:
|
|
137
|
-
scale: 3
|
|
138
|
-
- code: f_when
|
|
139
|
-
name: 日期
|
|
140
|
-
type: DATE
|
|
141
|
-
form:
|
|
142
|
-
publish: true
|
|
125
|
+
const LADDER_INTENT = `intentVersion: 1
|
|
126
|
+
models:
|
|
127
|
+
- code: eval_m1
|
|
128
|
+
name: 评测建模
|
|
129
|
+
fields:
|
|
130
|
+
- code: f_title
|
|
131
|
+
name: 标题
|
|
132
|
+
type: SHORT_TEXT
|
|
133
|
+
- code: f_amount
|
|
134
|
+
name: 金额
|
|
135
|
+
type: NUMERICAL
|
|
136
|
+
options:
|
|
137
|
+
scale: 3
|
|
138
|
+
- code: f_when
|
|
139
|
+
name: 日期
|
|
140
|
+
type: DATE
|
|
141
|
+
form:
|
|
142
|
+
publish: true
|
|
143
143
|
`;
|
|
144
144
|
export const INTENT_FACE_TASKS = [
|
|
145
145
|
{
|
|
@@ -163,12 +163,12 @@ export const INTENT_FACE_TASKS = [
|
|
|
163
163
|
id: "rule-backfill-ladder",
|
|
164
164
|
description: "two models via intent + DYNAMIC backfill rule + enable + trigger row + verified target readback (mixed face: intent + escape hatch)",
|
|
165
165
|
async run(agent, server) {
|
|
166
|
-
const modelsIntent = agent.writeIntent("models.yaml", `intentVersion: 1
|
|
167
|
-
models:
|
|
168
|
-
- code: eval_rs
|
|
169
|
-
name: 回填源
|
|
170
|
-
- code: eval_rt
|
|
171
|
-
name: 回填目标
|
|
166
|
+
const modelsIntent = agent.writeIntent("models.yaml", `intentVersion: 1
|
|
167
|
+
models:
|
|
168
|
+
- code: eval_rs
|
|
169
|
+
name: 回填源
|
|
170
|
+
- code: eval_rt
|
|
171
|
+
name: 回填目标
|
|
172
172
|
`);
|
|
173
173
|
await agent.intentValidate(modelsIntent);
|
|
174
174
|
const { plan, planFile } = await agent.intentPlan(modelsIntent);
|
|
@@ -209,27 +209,27 @@ models:
|
|
|
209
209
|
id: "input-error-recovery",
|
|
210
210
|
description: "bad intent -> validate exit 10 with source location -> fix -> plan/apply -> idempotent re-plan/re-apply with zero duplicate wires",
|
|
211
211
|
async run(agent, server) {
|
|
212
|
-
const bad = agent.writeIntent("intent.yaml", `intentVersion: 1
|
|
213
|
-
models:
|
|
214
|
-
- code: eval_idem
|
|
215
|
-
name: 幂等模型
|
|
216
|
-
fields:
|
|
217
|
-
- code: f_bad
|
|
218
|
-
name: 坏类型
|
|
219
|
-
type: HOLOGRAM
|
|
212
|
+
const bad = agent.writeIntent("intent.yaml", `intentVersion: 1
|
|
213
|
+
models:
|
|
214
|
+
- code: eval_idem
|
|
215
|
+
name: 幂等模型
|
|
216
|
+
fields:
|
|
217
|
+
- code: f_bad
|
|
218
|
+
name: 坏类型
|
|
219
|
+
type: HOLOGRAM
|
|
220
220
|
`);
|
|
221
221
|
const rejected = await agent.run(["intent", "validate", bad]);
|
|
222
222
|
ok(rejected.code === 10 && rejected.envelope.error?.code === "invalid-input", `expected invalid-input exit 10, got ${String(rejected.code)}`);
|
|
223
223
|
const first = rejected.envelope.result.errors[0];
|
|
224
224
|
ok(first.code === "intent-field-type-unknown" && typeof first.line === "number", `located error: ${JSON.stringify(first).slice(0, 200)}`);
|
|
225
|
-
const fixed = agent.writeIntent("intent.yaml", `intentVersion: 1
|
|
226
|
-
models:
|
|
227
|
-
- code: eval_idem
|
|
228
|
-
name: 幂等模型
|
|
229
|
-
fields:
|
|
230
|
-
- code: f_bad
|
|
231
|
-
name: 改正的字段
|
|
232
|
-
type: SHORT_TEXT
|
|
225
|
+
const fixed = agent.writeIntent("intent.yaml", `intentVersion: 1
|
|
226
|
+
models:
|
|
227
|
+
- code: eval_idem
|
|
228
|
+
name: 幂等模型
|
|
229
|
+
fields:
|
|
230
|
+
- code: f_bad
|
|
231
|
+
name: 改正的字段
|
|
232
|
+
type: SHORT_TEXT
|
|
233
233
|
`);
|
|
234
234
|
const validated = await agent.intentValidate(fixed);
|
|
235
235
|
ok(validated.valid === true, `fixed validate: ${JSON.stringify(validated).slice(0, 200)}`);
|
package/dist/bench/run.js
CHANGED
|
File without changes
|
package/dist/bench/scenario.js
CHANGED
|
File without changes
|
package/dist/bench/types.js
CHANGED
|
File without changes
|
package/dist/bi-wire.js
CHANGED
|
File without changes
|
|
@@ -177,36 +177,56 @@ export function assertStructuredConfig(value, label) {
|
|
|
177
177
|
* 全 0 串,则拒绝并要求从任务环境事实取真值。
|
|
178
178
|
*/
|
|
179
179
|
const MASKED_SECRET_RE = /^\*{3,}$/;
|
|
180
|
-
|
|
180
|
+
// 0.4.16:含连字符——r6 历史占位形态 "RELEVANCE-client-000…" 本就带连字符,
|
|
181
|
+
// 旧 [A-Za-z0-9]+ 恰好漏拦它自己要拦的形态(T05 测试发现)。
|
|
182
|
+
const PLACEHOLDER_CORP_RE = /^RELEVANCE-[A-Za-z0-9-]{4,}$/;
|
|
181
183
|
const ALL_ZERO_CREDENTIAL_RE = /^0{16,}$/;
|
|
182
|
-
|
|
184
|
+
/**
|
|
185
|
+
* 纯分类函数(不 throw、不碰环境):鉴权 parameterlist 里的掩码/占位值识别。
|
|
186
|
+
* 写面守卫(发现即拒)与 doctor --service(全量报告)共用同一套规则:
|
|
187
|
+
* 字符串三类(legacy "***" / RELEVANCE-xxx / 全 0)+ 结构化掩码对象
|
|
188
|
+
* (masked:true——T05 读面形状,防回灌)。
|
|
189
|
+
*/
|
|
190
|
+
export function classifyAuthParameterlist(parameterlist, _adapterCode) {
|
|
191
|
+
const findings = [];
|
|
183
192
|
for (const entry of parameterlist) {
|
|
184
193
|
if (!isRecord(entry))
|
|
185
194
|
continue;
|
|
186
195
|
const value = entry.value;
|
|
196
|
+
if (isRecord(value) && value.masked === true) {
|
|
197
|
+
findings.push({ code: String(entry.code ?? ""), reason: "结构化读回掩码 masked:true(真值必须从任务环境事实取得,不可抄写)", valueKind: "masked-object" });
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
187
200
|
if (typeof value !== "string")
|
|
188
201
|
continue;
|
|
189
202
|
const t = value.trim();
|
|
190
|
-
let why = "";
|
|
191
203
|
if (MASKED_SECRET_RE.test(t))
|
|
192
|
-
|
|
204
|
+
findings.push({ code: String(entry.code ?? ""), reason: "读回掩码 ***(读面按 ticket 115 掩码,不可抄写)", valueKind: "masked-string" });
|
|
193
205
|
else if (PLACEHOLDER_CORP_RE.test(t))
|
|
194
|
-
|
|
206
|
+
findings.push({ code: String(entry.code ?? ""), reason: "自造 RELEVANCE-xxx 占位(沙盒真实 corpId 才是 RELEVANCE-<真实id>)", valueKind: "placeholder-corp" });
|
|
195
207
|
else if (ALL_ZERO_CREDENTIAL_RE.test(t))
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
208
|
+
findings.push({ code: String(entry.code ?? ""), reason: "全 0 串(脱敏显示值,非真实凭据)", valueKind: "all-zero" });
|
|
209
|
+
}
|
|
210
|
+
return findings;
|
|
211
|
+
}
|
|
212
|
+
function scanAuthParameterlistForPlaceholders(parameterlist, adapterCode) {
|
|
213
|
+
for (const finding of classifyAuthParameterlist(parameterlist, adapterCode)) {
|
|
214
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `${adapterCode} 服务鉴权凭据 ${finding.code} 包含${finding.reason}——平台 service/create 不校验凭据内容,注册成功但运行时自调必败(r6-onlinejs 真机:全 0 corpId 注册成功、自调 401/500)。凭据必须从任务环境事实取真值(如 doctor 显示的 corpId、任务提供的 client_secret)。`, { key: `auth.parameterlist.${finding.code}`, value: finding.valueKind });
|
|
199
215
|
}
|
|
200
216
|
}
|
|
201
217
|
function requireAuthAndUrl(serviceConfig, adapterCode) {
|
|
202
218
|
const auth = serviceConfig.auth;
|
|
219
|
+
// T05:掩码/占位分类先于结构校验——结构化掩码对象 {masked:true}(读回形状回灌)
|
|
220
|
+
// 在 string-only 的 validateRestfulAuth 之前就能拿到专属拒因,而不是笼统的
|
|
221
|
+
// "必须是非空字符串"。两道防线都保留(INTERNAL 路径只依赖本扫描)。
|
|
222
|
+
if (isRecord(auth) && Array.isArray(auth.parameterlist))
|
|
223
|
+
scanAuthParameterlistForPlaceholders(auth.parameterlist, adapterCode);
|
|
203
224
|
if (adapterCode === "RESTFUL" || adapterCode === "SOAP")
|
|
204
225
|
validateRestfulAuth(auth);
|
|
205
226
|
if (!isRecord(auth) || typeof auth.code !== "string" || !auth.code || !Array.isArray(auth.parameterlist))
|
|
206
227
|
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `${adapterCode} 服务 configJson 需要 auth {code, parameterlist[]}`, { key: "auth" });
|
|
207
228
|
if (!commonlistValue(serviceConfig, "url"))
|
|
208
229
|
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `${adapterCode} 服务 configJson 需要 commonlist [{code:"url", value}]`, { key: "commonlist.url" });
|
|
209
|
-
scanAuthParameterlistForPlaceholders(auth.parameterlist, adapterCode);
|
|
210
230
|
}
|
|
211
231
|
function requireCommonlist(serviceConfig, key, adapterCode) {
|
|
212
232
|
if (!commonlistValue(serviceConfig, key))
|
|
@@ -504,12 +524,17 @@ export function normalizeMethodConfigReadback(adapterCode, config) {
|
|
|
504
524
|
* `auth.parameterlist[].value` carries cleartext credentials (basic
|
|
505
525
|
* password, oauth client_secret…); handoff §5 says method-config secrets
|
|
506
526
|
* never echo. Applied ONLY to readback projections (service.get /
|
|
507
|
-
* method.get / method update readback): the VALUES become
|
|
508
|
-
*
|
|
527
|
+
* method.get / method update readback): the VALUES become a STRUCTURED
|
|
528
|
+
* mask `{masked:true, hint}` (0.4.16 T05 — a bare "***" string was
|
|
529
|
+
* copy-pastable; agents抄回写即注册垃圾凭据,r4-r6 全 0 悬案的根因链).
|
|
530
|
+
* The code/name structure stays readable, so ticket 94's shape-readback
|
|
509
531
|
* semantics survive. NEVER apply this on a write/echo path — the update
|
|
510
532
|
* echo must carry the original config verbatim or the mask would be
|
|
511
|
-
* persisted over the stored credential.
|
|
533
|
+
* persisted over the stored credential. The write-face guard
|
|
534
|
+
* (classifyAuthParameterlist) rejects both the structured mask and any
|
|
535
|
+
* legacy "***" string on write.
|
|
512
536
|
*/
|
|
537
|
+
export const MASK_HINT = "读回掩码——真值从任务环境事实取,不可抄写";
|
|
513
538
|
export function maskServiceConfigSecrets(config) {
|
|
514
539
|
if (!isRecord(config))
|
|
515
540
|
return config;
|
|
@@ -519,7 +544,7 @@ export function maskServiceConfigSecrets(config) {
|
|
|
519
544
|
const maskedParameterlist = auth.parameterlist.map((entry) => {
|
|
520
545
|
if (!isRecord(entry))
|
|
521
546
|
return entry;
|
|
522
|
-
return { ...entry, ...(entry.value !== undefined ? { value:
|
|
547
|
+
return { ...entry, ...(entry.value !== undefined ? { value: { masked: true, hint: MASK_HINT } } : {}) };
|
|
523
548
|
});
|
|
524
549
|
return { ...config, auth: { ...auth, parameterlist: maskedParameterlist } };
|
|
525
550
|
}
|
package/dist/call.js
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -133,8 +133,8 @@ const SURFACES = [
|
|
|
133
133
|
id: "bizservice",
|
|
134
134
|
title: "业务集成",
|
|
135
135
|
lane: LANES.b2,
|
|
136
|
-
description: "业务服务与方法注册(RESTFUL/DATA_SQL/DATA_TABLE/SAP/KAFKA/SOAP
|
|
137
|
-
operations: ["register", "update", "delete", "export", "import", "check", "test"],
|
|
136
|
+
description: "业务服务与方法注册(RESTFUL/DATA_SQL/DATA_TABLE/SAP/KAFKA/SOAP)、服务清单(按分类/关键词,凭据不回显)、导入导出、设计器测试。",
|
|
137
|
+
operations: ["list", "register", "update", "delete", "export", "import", "check", "test"],
|
|
138
138
|
requires: [],
|
|
139
139
|
intent: { level: "partial", note: "声明式支持新建 service.register(可带一个 method)与既有 service/method 的 update(结构化 config、AES 加密、patch 语义);删除、给既有服务加方法、分类/连接池/IE 仍走 escape hatch。" },
|
|
140
140
|
notes: "方法 configJson 走 AES-128-ECB;RESTFUL/DATA_SQL 绑定前须带适配器必需配置(否则是注册壳)。",
|
package/dist/capability-shape.js
CHANGED
|
File without changes
|
package/dist/cli-contract.js
CHANGED
|
File without changes
|
package/dist/cli-output.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { writeFileSync } from "node:fs";
|
|
2
2
|
import { serializeEnvelope } from "./cli-contract.js";
|
|
3
3
|
import { recordUsageOutput } from "./usage-log.js";
|
|
4
|
-
let options = { quiet: false };
|
|
5
|
-
/** Parse `--quiet` and `--out <file>` from the raw argv once, before dispatch. */
|
|
4
|
+
let options = { quiet: false, verbose: false };
|
|
5
|
+
/** Parse `--quiet`, `--verbose` and `--out <file>` from the raw argv once, before dispatch. */
|
|
6
6
|
export function configureOutput(argv) {
|
|
7
7
|
const outIndex = argv.indexOf("--out");
|
|
8
8
|
let outFile;
|
|
@@ -14,7 +14,11 @@ export function configureOutput(argv) {
|
|
|
14
14
|
throw new Error("--out requires a file path");
|
|
15
15
|
outFile = value;
|
|
16
16
|
}
|
|
17
|
-
options = {
|
|
17
|
+
options = {
|
|
18
|
+
quiet: argv.includes("--quiet"),
|
|
19
|
+
verbose: argv.includes("--verbose"),
|
|
20
|
+
...(outFile ? { outFile } : {}),
|
|
21
|
+
};
|
|
18
22
|
return options;
|
|
19
23
|
}
|
|
20
24
|
/**
|
|
@@ -25,7 +29,7 @@ export function stripOutputFlags(argv) {
|
|
|
25
29
|
const out = [];
|
|
26
30
|
for (let index = 0; index < argv.length; index += 1) {
|
|
27
31
|
const token = argv[index];
|
|
28
|
-
if (token === "--quiet")
|
|
32
|
+
if (token === "--quiet" || token === "--verbose")
|
|
29
33
|
continue;
|
|
30
34
|
if (token === "--out") {
|
|
31
35
|
index += 1;
|
|
@@ -38,29 +42,51 @@ export function stripOutputFlags(argv) {
|
|
|
38
42
|
export function outputOptions() {
|
|
39
43
|
return options;
|
|
40
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Serialize-boundary compaction: a success envelope (error == null) drops the
|
|
47
|
+
* envelope-level `status`/`requestId`/`error:null` unless `--verbose`. Failures
|
|
48
|
+
* keep the full field set. Non-envelope values pass through untouched.
|
|
49
|
+
*/
|
|
50
|
+
function compactForOutput(value) {
|
|
51
|
+
if (options.verbose)
|
|
52
|
+
return value;
|
|
53
|
+
if (!value || typeof value !== "object" || !("ok" in value))
|
|
54
|
+
return value;
|
|
55
|
+
const record = value;
|
|
56
|
+
if (record.error !== null && record.error !== undefined)
|
|
57
|
+
return value;
|
|
58
|
+
return { ok: record.ok, result: record.result };
|
|
59
|
+
}
|
|
41
60
|
/**
|
|
42
61
|
* Write the single stdout envelope. When `--out` is set, the identical bytes are
|
|
43
62
|
* written to that file (UTF-8) FIRST, so a bad path fails before any envelope
|
|
44
63
|
* reaches stdout — keeping the "exactly one envelope" contract even on failure.
|
|
45
|
-
* stdout and the file can never diverge.
|
|
64
|
+
* stdout and the file can never diverge (both follow the same verbose rule).
|
|
46
65
|
*/
|
|
47
66
|
export function writeEnvelope(value) {
|
|
48
|
-
const
|
|
67
|
+
const outputValue = compactForOutput(value);
|
|
68
|
+
const text = serializeEnvelope(outputValue);
|
|
49
69
|
if (!options.outFile) {
|
|
50
70
|
recordUsageOutput(value, Buffer.byteLength(text, "utf8"));
|
|
51
71
|
process.stdout.write(text);
|
|
52
72
|
return;
|
|
53
73
|
}
|
|
54
|
-
const pretty = `${JSON.stringify(
|
|
74
|
+
const pretty = `${JSON.stringify(outputValue, null, 2)}\n`;
|
|
55
75
|
writeFileSync(options.outFile, pretty, "utf8");
|
|
56
|
-
const envelopeValue =
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
76
|
+
const envelopeValue = outputValue && typeof outputValue === "object" ? outputValue : {};
|
|
77
|
+
const failure = envelopeValue.error !== null && envelopeValue.error !== undefined;
|
|
78
|
+
const confirmation = failure || options.verbose
|
|
79
|
+
? {
|
|
80
|
+
ok: envelopeValue.ok ?? failure ? false : true,
|
|
81
|
+
status: envelopeValue.status ?? (failure ? "error" : "ok"),
|
|
82
|
+
result: { out: options.outFile, bytes: Buffer.byteLength(pretty, "utf8") },
|
|
83
|
+
requestId: envelopeValue.requestId ?? null,
|
|
84
|
+
error: envelopeValue.error ?? null,
|
|
85
|
+
}
|
|
86
|
+
: {
|
|
87
|
+
ok: envelopeValue.ok ?? true,
|
|
88
|
+
result: { out: options.outFile, bytes: Buffer.byteLength(pretty, "utf8") },
|
|
89
|
+
};
|
|
64
90
|
const confirmationText = serializeEnvelope(confirmation);
|
|
65
91
|
recordUsageOutput(value, Buffer.byteLength(confirmationText, "utf8"), Buffer.byteLength(pretty, "utf8"));
|
|
66
92
|
process.stdout.write(confirmationText);
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// T04:SQLite 警告过滤器必须保持第一个 import(先于任何拉起 omc-core/node:sqlite 的模块)。
|
|
3
|
+
import "./sqlite-warning-filter.js";
|
|
2
4
|
import { createInterface } from "node:readline/promises";
|
|
3
5
|
import { stdin, argv } from "node:process";
|
|
4
6
|
import { createRequire } from "node:module";
|
|
@@ -116,7 +118,7 @@ async function cmdSetup(store, args) {
|
|
|
116
118
|
console.error(`环境 ${name} 已保存(幂等,可随时重跑 omc setup)。凭据存储模式:${store.credentialsMode()}`);
|
|
117
119
|
return 0;
|
|
118
120
|
}
|
|
119
|
-
async function cmdDoctor(store, environment) {
|
|
121
|
+
async function cmdDoctor(store, environment, serviceCode) {
|
|
120
122
|
const manifest = loadCoverageManifest();
|
|
121
123
|
const steps = [];
|
|
122
124
|
let ok = true;
|
|
@@ -166,6 +168,24 @@ async function cmdDoctor(store, environment) {
|
|
|
166
168
|
steps.push(`许可证 FAIL 许可证面探测失败:${redactText(error.message, [])}`);
|
|
167
169
|
}
|
|
168
170
|
}
|
|
171
|
+
// T09 — optional per-service credential shape check (--service <code>).
|
|
172
|
+
// Non-fatal: a masked/placeholder credential does not make the ENVIRONMENT
|
|
173
|
+
// unhealthy; it explains why that service's calls would 401/500. Findings
|
|
174
|
+
// come from the RAW wire read (get_by_code configJson), classified by the
|
|
175
|
+
// same pure function the write-side guard uses — never the masked
|
|
176
|
+
// projection re-parsed.
|
|
177
|
+
if (serviceCode !== undefined) {
|
|
178
|
+
const { probeServiceCredentials } = await import("./service-doctor.js");
|
|
179
|
+
const probe = await probeServiceCredentials(session, serviceCode);
|
|
180
|
+
if (probe.status === "not-found")
|
|
181
|
+
steps.push(`服务凭据 -- ${serviceCode} 未找到(get_by_code 无行)`);
|
|
182
|
+
else if (probe.status === "error")
|
|
183
|
+
steps.push(`服务凭据 WARN 探测失败:${redactText(probe.message, [])}`);
|
|
184
|
+
else if (probe.findings.length === 0)
|
|
185
|
+
steps.push(`服务凭据 ok ${serviceCode} 鉴权参数形状正常(${probe.parameterCount} 项;掩码/占位/全 0 均未检出)`);
|
|
186
|
+
else
|
|
187
|
+
steps.push(`服务凭据 WARN ${serviceCode} ${probe.findings.length} 项问题(${probe.findings.map((finding) => `${finding.code}:${finding.valueKind}`).join("、")})——掩码/占位值不可用于真实调用;真值从任务环境事实取,不要抄 service.get 读回`);
|
|
188
|
+
}
|
|
169
189
|
}
|
|
170
190
|
catch (error) {
|
|
171
191
|
ok = false;
|
|
@@ -1348,7 +1368,7 @@ async function main() {
|
|
|
1348
1368
|
command.command("setup").option("--list").option("--edit <environment>").option("--remove <environment>").action(async (options) => {
|
|
1349
1369
|
commandExitCode = await cmdSetup(new ConfigStore(defaultPaths()), [...(options.list ? ["--list"] : []), ...(options.edit ? ["--edit", options.edit] : []), ...(options.remove ? ["--remove", options.remove] : [])]);
|
|
1350
1370
|
});
|
|
1351
|
-
command.command("doctor").option("--environment <environment>").option("--env <environment>").action(async (options) => { commandExitCode = await cmdDoctor(new ConfigStore(defaultPaths()), options.environment ?? options.env); });
|
|
1371
|
+
command.command("doctor").option("--environment <environment>").option("--env <environment>").option("--service <serviceCode>", "可选:体检单个业务集成服务的鉴权参数形状(掩码/占位/全 0 检出,WARN 非致命)").action(async (options) => { commandExitCode = await cmdDoctor(new ConfigStore(defaultPaths()), options.environment ?? options.env, options.service); });
|
|
1352
1372
|
command.command("call <tool>").option("--env <environment>").option("--environment <environment>").option("--args <json>").option("--args-file <path>").option("--max-output-bytes <bytes>").option("--request-id <requestId>").option("--timeout-ms <milliseconds>").option("--wait <milliseconds>").option("--dry-run").addHelpText("after", `\n${onboardingHelp()}\n`).action(async (tool, options) => {
|
|
1353
1373
|
const rest = [tool, ...(options.env ? ["--env", options.env] : []), ...(options.environment ? ["--environment", options.environment] : []), ...(options.args !== undefined ? ["--args", options.args] : []), ...(options.argsFile !== undefined ? ["--args-file", options.argsFile] : []), ...(options.maxOutputBytes !== undefined ? ["--max-output-bytes", options.maxOutputBytes] : []), ...(options.requestId ? ["--request-id", options.requestId] : []), ...(options.timeoutMs ? ["--timeout-ms", options.timeoutMs] : []), ...(options.wait !== undefined ? ["--wait", options.wait] : []), ...(options.dryRun ? ["--dry-run"] : [])];
|
|
1354
1374
|
commandExitCode = await cmdCall(new ConfigStore(defaultPaths()), rest);
|
package/dist/code-rules.js
CHANGED
|
File without changes
|
|
File without changes
|
package/dist/config-store.js
CHANGED
|
File without changes
|
package/dist/context.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/dist/coverage.js
CHANGED
|
File without changes
|
package/dist/data-list-filter.js
CHANGED
|
File without changes
|
package/dist/discovery.js
CHANGED
|
File without changes
|
package/dist/endpoints.js
CHANGED
|
@@ -70,6 +70,8 @@ export const ENDPOINTS = {
|
|
|
70
70
|
bizServiceCreate: "/api/api/bizservice/service/create",
|
|
71
71
|
bizServiceUpdate: "/api/api/bizservice/service/update",
|
|
72
72
|
bizServiceGetByCode: "/api/api/bizservice/service/get_by_code",
|
|
73
|
+
bizServiceListByCategoryId: "/api/api/bizservice/service/list_by_category_id",
|
|
74
|
+
bizServiceListByCategoryIdFind: "/api/api/bizservice/service/list_by_category_id_find",
|
|
73
75
|
bizServiceCategoryList: "/api/api/bizservice/category/list",
|
|
74
76
|
bizServiceCategoryCreate: "/api/api/bizservice/category/create",
|
|
75
77
|
bizServiceCategoryUpdate: "/api/api/bizservice/category/update",
|
|
File without changes
|
|
@@ -738,7 +738,7 @@ const formConditionalRequired = Object.freeze({
|
|
|
738
738
|
conditionalRequirements: ["条件串:`{字段} 操作符 [值]`(操作符两侧留空格)。操作符用**中文**(真实设计器产出):等于/不等于/大于/小于/大于等于/小于等于/包含/不包含/包含全部/包含任意一个/等于任意一个/为空/不为空;也接受符号 == != > >= <= Contains IsEmpty。为空/不为空**不带值**(`{reason} 不为空`),其余带 JSON 值 {\"valueType\":...,\"value\":...}。多条件用 ` && `、多组用 ` || `。**不是** eq/gt/isnull——会让门户渲染 SyntaxError"],
|
|
739
739
|
priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认条件字段与目标字段都已发布" }],
|
|
740
740
|
dryRun: { supported: true, command: `omc call field.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验字段 options 形状,不写入" },
|
|
741
|
-
expectedReadback: { readback: { field: { code: "leave_reason", options: { requiredFormula: "<protocol>" } } }, next: ["field.
|
|
741
|
+
expectedReadback: { readback: { field: { code: "leave_reason", options: { requiredFormula: "<protocol>" } } }, next: ["(一调)field.update cascadeRepublish:true", "(分步)field.publish → form.draft(rebuild) → form.publish", "(只刷新表单)form.publish rebuild:true"] },
|
|
742
742
|
refusalRecovery: [
|
|
743
743
|
{ code: "capability-out-of-coverage", action: "条件串用中文或符号操作符(等于/大于/不为空 或 == />/IsEmpty);恒必填用字面量 \"true\"" },
|
|
744
744
|
...commonRecovery,
|
|
@@ -755,7 +755,7 @@ const formAlwaysReadonly = Object.freeze({
|
|
|
755
755
|
conditionalRequirements: ["恒只读用布尔 readonlyFormula:true;条件只读用 readonlyCondition 条件串"],
|
|
756
756
|
priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认字段已发布" }],
|
|
757
757
|
dryRun: { supported: true, command: `omc call field.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验 readonlyFormula 是布尔", },
|
|
758
|
-
expectedReadback: { readback: { field: { code: "approval_level", options: { readonlyFormula: true } } }, next: ["field.
|
|
758
|
+
expectedReadback: { readback: { field: { code: "approval_level", options: { readonlyFormula: true } } }, next: ["(一调)field.update cascadeRepublish:true", "(分步)field.publish → form.draft(rebuild) → form.publish", "(只刷新表单)form.publish rebuild:true"] },
|
|
759
759
|
refusalRecovery: [{ code: "capability-out-of-coverage", action: "readonlyFormula 必须是布尔 true;条件只读才用 readonlyCondition" }, ...commonRecovery],
|
|
760
760
|
requiresEnvironmentDerivedIds: false,
|
|
761
761
|
});
|
|
@@ -775,7 +775,7 @@ const formConditionalReadonly = Object.freeze({
|
|
|
775
775
|
conditionalRequirements: ["条件串协议同必填;恒只读改用 readonlyFormula:true"],
|
|
776
776
|
priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认字段已发布" }],
|
|
777
777
|
dryRun: { supported: true, command: `omc call field.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验条件串协议" },
|
|
778
|
-
expectedReadback: { readback: { field: { code: "approval_level", options: { readonlyCondition: "<protocol>" } } }, next: ["field.
|
|
778
|
+
expectedReadback: { readback: { field: { code: "approval_level", options: { readonlyCondition: "<protocol>" } } }, next: ["(一调)field.update cascadeRepublish:true", "(分步)field.publish → form.draft(rebuild) → form.publish", "(只刷新表单)form.publish rebuild:true"] },
|
|
779
779
|
refusalRecovery: [{ code: "capability-out-of-coverage", action: "使用中文或符号操作符与三段条件串" }, ...commonRecovery],
|
|
780
780
|
requiresEnvironmentDerivedIds: false,
|
|
781
781
|
});
|
|
@@ -972,7 +972,7 @@ const bizserviceSelfCallCorp = Object.freeze({
|
|
|
972
972
|
conditionalRequirements: [
|
|
973
973
|
"**参数 code 必须精确等于 `X-LowCode-Corpid`**——适配器按「参数 code = header 名」加入请求头(headerParam.add(key, value))。",
|
|
974
974
|
"**corpid 传递有两种实证形状**:①方法级入参 code 为 X-LowCode-Corpid、configJson.position 为 REQUEST_HEADER 整数 2(r3-wms 实证可用);②规则 BIZ_ACTION 的 inputMappings 加 CONST 映射(serviceMethodParameterCode 为 X-LowCode-Corpid、codeType 为 0、bizCode 放真实 corpId——r4-fin 实证:该环境下方法级 position:2 注入为空头值 [\"\"],此形状才生效)。**先用 ②,双跑验证头值;①失效(头空)就换 ②**。CONST 值必须放 bizCode(平台只读 bizCode)。",
|
|
975
|
-
"**corpid 头非空即可(r6-onlinejs R19 实证)**:沙盒 token corpId=null 时,全 0 corpId 头(RELEVANCE-0000…)也 errcode:0;仅空头才 50000。多租户路由才需真值。凭据值不可从读回抄(读面按 ticket 115
|
|
975
|
+
"**corpid 头非空即可(r6-onlinejs R19 实证)**:沙盒 token corpId=null 时,全 0 corpId 头(RELEVANCE-0000…)也 errcode:0;仅空头才 50000。多租户路由才需真值。凭据值不可从读回抄(读面按 ticket 115 掩码——0.4.16 起为结构化 {masked:true,hint},旧版 ***),必须用任务环境事实真值(doctor 显示的 corpId、任务提供的 client_secret)。",
|
|
976
976
|
"configJson.position 用 REQUEST_HEADER 名即可(工具写侧编码为整数索引 2;wire 上是整数——写名字平台侧反序列化失败、header 根本不进,源码 @JsonCreator get(int))。",
|
|
977
977
|
"BIZ_ACTION 节点调用该方法**无需传该常量参数**(方法级常量由平台注入);只需照常映射业务参数。",
|
|
978
978
|
],
|
package/dist/fake/app.js
CHANGED
|
File without changes
|
|
@@ -273,6 +273,32 @@ export const b2Route = (ctx, input) => {
|
|
|
273
273
|
{ sql: `DELETE FROM ${table} WHERE ...`, sqlType: "DELETE" },
|
|
274
274
|
];
|
|
275
275
|
}
|
|
276
|
+
case "/api/api/bizservice/service/list_by_category_id":
|
|
277
|
+
case "/api/api/bizservice/service/list_by_category_id_find": {
|
|
278
|
+
// Live (BizServiceController.java:167-204/264-291): categoryId="" lists
|
|
279
|
+
// ALL services; the _find variant filters name/code case-insensitively.
|
|
280
|
+
// Rows are the FULL BizServiceModel INCLUDING configJson (authorization
|
|
281
|
+
// config, possibly carrying credentials) — returned deliberately so the
|
|
282
|
+
// TOOL-layer whitelist projection is what tests assert.
|
|
283
|
+
const categoryId = String(q.categoryId ?? "");
|
|
284
|
+
const keyword = input.path.endsWith("_find") ? String(q.keyword ?? "") : "";
|
|
285
|
+
const lower = keyword.toLowerCase();
|
|
286
|
+
return [...ctx.state.services.values()]
|
|
287
|
+
.filter((service) => categoryId === "" || service.serviceCategoryId === categoryId)
|
|
288
|
+
.filter((service) => keyword === "" || String(service.code ?? "").toLowerCase().includes(lower) || String(service.name ?? "").toLowerCase().includes(lower))
|
|
289
|
+
.map((service) => ({
|
|
290
|
+
id: service.id,
|
|
291
|
+
code: service.code,
|
|
292
|
+
name: service.name ?? null,
|
|
293
|
+
adapterCode: service.adapterCode ?? null,
|
|
294
|
+
serviceCategoryId: service.serviceCategoryId ?? null,
|
|
295
|
+
description: null,
|
|
296
|
+
shared: service.shared ?? false,
|
|
297
|
+
configJson: service.configJson ?? null,
|
|
298
|
+
createdBy: "fake-admin",
|
|
299
|
+
modifiedBy: "fake-admin",
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
276
302
|
case "/api/api/bizservice/service/create": {
|
|
277
303
|
// Live: create with an empty adapterCode → 500005 业务服务关联适配器编码为空.
|
|
278
304
|
if (typeof body.adapterCode !== "string" || !body.adapterCode)
|
|
File without changes
|
package/dist/fake/datarule.js
CHANGED
|
File without changes
|
package/dist/fake/dictionary.js
CHANGED
|
File without changes
|
package/dist/fake/environment.js
CHANGED
|
File without changes
|
package/dist/fake/field.js
CHANGED
|
File without changes
|
package/dist/fake/form.js
CHANGED
|
File without changes
|
package/dist/fake/index.js
CHANGED
|
File without changes
|
package/dist/fake/list-view.js
CHANGED
|
File without changes
|
package/dist/fake/model.js
CHANGED
|
File without changes
|
package/dist/fake/online-js.js
CHANGED
|
File without changes
|
package/dist/fake/report.js
CHANGED
|
File without changes
|
package/dist/fake/routes.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/dist/fake/state.js
CHANGED
|
File without changes
|
package/dist/fake/workflow.js
CHANGED
|
File without changes
|
package/dist/field-change.js
CHANGED
|
File without changes
|
package/dist/field-families.js
CHANGED
|
File without changes
|
package/dist/form-layout.js
CHANGED
|
File without changes
|
package/dist/form-support.js
CHANGED
|
@@ -759,9 +759,18 @@ export async function executeFormPublishWrite(session, args) {
|
|
|
759
759
|
// creates the default form and scaffolds the published fields) so a single
|
|
760
760
|
// form.publish call is self-sufficient. A draft that already exists is left
|
|
761
761
|
// untouched — this only fills the gap.
|
|
762
|
+
//
|
|
763
|
+
// rebuild:true (ticket T06) is the OTHER path: discard the existing draft
|
|
764
|
+
// and re-scaffold from the PUBLISHED face before promoting — the one-call
|
|
765
|
+
// refresh after a field shape change (the cascade chain and a direct
|
|
766
|
+
// form.publish(rebuild:true) share this). Promotion then forces.
|
|
762
767
|
let pre = await readFormOrNull(session, args.schemaCode, args.sheetCode);
|
|
763
768
|
const hasDraft = (sheet) => sheet !== null && typeof sheet.draftViewJson === "string" && typeof sheet.draftAttributesJson === "string";
|
|
764
|
-
if (
|
|
769
|
+
if (args.rebuild === true) {
|
|
770
|
+
await executeFormDraftWrite(session, { schemaCode: args.schemaCode, sheetCode: args.sheetCode, rebuild: true });
|
|
771
|
+
pre = await readFormOrNull(session, args.schemaCode, args.sheetCode);
|
|
772
|
+
}
|
|
773
|
+
else if (!hasDraft(pre)) {
|
|
765
774
|
await executeFormDraftWrite(session, { schemaCode: args.schemaCode, sheetCode: args.sheetCode });
|
|
766
775
|
pre = await readFormOrNull(session, args.schemaCode, args.sheetCode);
|
|
767
776
|
}
|
|
@@ -769,7 +778,8 @@ export async function executeFormPublishWrite(session, args) {
|
|
|
769
778
|
throw new WireError("business", `form ${args.sheetCode} not found`, 302024);
|
|
770
779
|
if (pre.draftHtmlJson !== "[]")
|
|
771
780
|
assertHtmlRender(pre.draftHtmlJson, { schemaCode: args.schemaCode, sheetCode: args.sheetCode, face: "draftHtmlJson" });
|
|
772
|
-
|
|
781
|
+
const forcePromote = args.force === true || args.rebuild === true;
|
|
782
|
+
if (pre.published === true && !forcePromote)
|
|
773
783
|
return { performed: { skipped: "already-published" }, readback: { published: true } };
|
|
774
784
|
if (!hasDraft(pre))
|
|
775
785
|
throw new WireError("business", "form publish requires a saved draft (draftViewJson/draftAttributesJson)", 302020);
|
package/dist/goal-routes.js
CHANGED
|
File without changes
|
|
File without changes
|
package/dist/human-summary.js
CHANGED
|
File without changes
|
package/dist/identity.js
CHANGED
|
File without changes
|
package/dist/intent/baseline.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|