@stalfh233/omc-cli 0.0.0-stage → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +293 -3
- package/coverage/m1-coverage-manifest-v1.json +1278 -0
- package/dist/approval-token.js +102 -0
- package/dist/args.js +25 -0
- package/dist/artifacts.js +119 -0
- package/dist/bench/call-face-eval.js +256 -0
- package/dist/bench/context-attribution.js +151 -0
- package/dist/bench/discovery-cost-eval.js +230 -0
- package/dist/bench/driver.js +81 -0
- package/dist/bench/evals.js +236 -0
- package/dist/bench/fake-http-server.js +65 -0
- package/dist/bench/instrument.js +87 -0
- package/dist/bench/intent-face-eval.js +343 -0
- package/dist/bench/run.js +166 -0
- package/dist/bench/scenario.js +343 -0
- package/dist/bench/types.js +76 -0
- package/dist/bi-wire.js +41 -0
- package/dist/bizservice-config.js +581 -0
- package/dist/call.js +153 -0
- package/dist/capability-absences.js +23 -0
- package/dist/capability-overview.js +492 -0
- package/dist/capability-shape.js +154 -0
- package/dist/cli-contract.js +70 -0
- package/dist/cli-output.js +73 -0
- package/dist/cli.js +1418 -0
- package/dist/code-rules.js +69 -0
- package/dist/command-transport.js +155 -0
- package/dist/config-store.js +195 -0
- package/dist/context.js +21 -0
- package/dist/contract-consistency.js +66 -0
- package/dist/contract-resources.js +62 -0
- package/dist/coverage-consistency.js +62 -0
- package/dist/coverage-registry.js +76 -0
- package/dist/coverage.js +130 -0
- package/dist/data-list-filter.js +114 -0
- package/dist/discovery.js +390 -0
- package/dist/endpoints.js +154 -0
- package/dist/environment-policy.js +26 -0
- package/dist/execution-metadata.js +1092 -0
- package/dist/fake/app.js +45 -0
- package/dist/fake/b2-registration.js +594 -0
- package/dist/fake/businessrule.js +242 -0
- package/dist/fake/datarule.js +72 -0
- package/dist/fake/dictionary.js +108 -0
- package/dist/fake/environment.js +30 -0
- package/dist/fake/field.js +167 -0
- package/dist/fake/form.js +101 -0
- package/dist/fake/index.js +121 -0
- package/dist/fake/list-view.js +289 -0
- package/dist/fake/model.js +136 -0
- package/dist/fake/online-js.js +18 -0
- package/dist/fake/report.js +253 -0
- package/dist/fake/routes.js +47 -0
- package/dist/fake/rule-lifecycle.js +37 -0
- package/dist/fake/runtime-data.js +367 -0
- package/dist/fake/state.js +67 -0
- package/dist/fake/workflow.js +364 -0
- package/dist/field-change.js +200 -0
- package/dist/field-families.js +896 -0
- package/dist/form-layout.js +111 -0
- package/dist/form-support.js +821 -0
- package/dist/goal-routes.js +468 -0
- package/dist/governed-execution.js +87 -0
- package/dist/human-summary.js +212 -0
- package/dist/identity.js +62 -0
- package/dist/intent/baseline.js +57 -0
- package/dist/intent/capabilities/bizservice.js +274 -0
- package/dist/intent/capabilities/businessrule.js +493 -0
- package/dist/intent/capabilities/datarule.js +187 -0
- package/dist/intent/capabilities/field.js +545 -0
- package/dist/intent/capabilities/form.js +136 -0
- package/dist/intent/capabilities/index.js +64 -0
- package/dist/intent/capabilities/listview.js +157 -0
- package/dist/intent/capabilities/model.js +100 -0
- package/dist/intent/capabilities/onlinejs.js +108 -0
- package/dist/intent/capabilities/report.js +355 -0
- package/dist/intent/capabilities/workflow.js +458 -0
- package/dist/intent/capability.js +6 -0
- package/dist/intent/cli.js +91 -0
- package/dist/intent/compare.js +56 -0
- package/dist/intent/compiler.js +79 -0
- package/dist/intent/dsl.js +129 -0
- package/dist/intent/plan-file.js +63 -0
- package/dist/intent/readback.js +65 -0
- package/dist/intent/schema.js +158 -0
- package/dist/intent/validation.js +30 -0
- package/dist/intent/yaml.js +315 -0
- package/dist/json-column.js +68 -0
- package/dist/lanes/app-contract.js +95 -0
- package/dist/lanes/app-coverage.js +16 -0
- package/dist/lanes/app.js +174 -0
- package/dist/lanes/apply-changes.js +231 -0
- package/dist/lanes/b2-registration-contract.js +292 -0
- package/dist/lanes/b2-registration-coverage.js +48 -0
- package/dist/lanes/b2-registration.js +1187 -0
- package/dist/lanes/businessrule-contract.js +232 -0
- package/dist/lanes/businessrule-coverage.js +16 -0
- package/dist/lanes/businessrule.js +221 -0
- package/dist/lanes/contract-support.js +65 -0
- package/dist/lanes/coverage-declaration.js +9 -0
- package/dist/lanes/datarule-contract.js +155 -0
- package/dist/lanes/datarule-coverage.js +19 -0
- package/dist/lanes/datarule-protocol.js +308 -0
- package/dist/lanes/datarule.js +818 -0
- package/dist/lanes/dictionary-contract.js +78 -0
- package/dist/lanes/dictionary-coverage.js +24 -0
- package/dist/lanes/dictionary.js +235 -0
- package/dist/lanes/environment-contract.js +70 -0
- package/dist/lanes/environment-coverage.js +14 -0
- package/dist/lanes/environment.js +163 -0
- package/dist/lanes/field-contract.js +223 -0
- package/dist/lanes/field-coverage.js +27 -0
- package/dist/lanes/field.js +374 -0
- package/dist/lanes/form-contract.js +97 -0
- package/dist/lanes/form-coverage.js +16 -0
- package/dist/lanes/form.js +185 -0
- package/dist/lanes/lane-ids.js +34 -0
- package/dist/lanes/list-view-contract.js +175 -0
- package/dist/lanes/list-view-coverage.js +20 -0
- package/dist/lanes/list-view-shapes.js +1207 -0
- package/dist/lanes/list-view.js +578 -0
- package/dist/lanes/meta-contract.js +85 -0
- package/dist/lanes/meta.js +255 -0
- package/dist/lanes/model-contract.js +168 -0
- package/dist/lanes/model-coverage.js +20 -0
- package/dist/lanes/model.js +986 -0
- package/dist/lanes/online-js-contract.js +99 -0
- package/dist/lanes/online-js-coverage.js +28 -0
- package/dist/lanes/online-js.js +127 -0
- package/dist/lanes/report-contract.js +144 -0
- package/dist/lanes/report-coverage.js +21 -0
- package/dist/lanes/report.js +476 -0
- package/dist/lanes/rule-graph.js +1846 -0
- package/dist/lanes/rule-lifecycle-contract.js +92 -0
- package/dist/lanes/rule-lifecycle-coverage.js +20 -0
- package/dist/lanes/rule-lifecycle.js +176 -0
- package/dist/lanes/runtime-data-contract.js +264 -0
- package/dist/lanes/runtime-data-coverage.js +25 -0
- package/dist/lanes/runtime-data.js +1054 -0
- package/dist/lanes/workflow-contract.js +223 -0
- package/dist/lanes/workflow-coverage.js +40 -0
- package/dist/lanes/workflow.js +1813 -0
- package/dist/online-js-layout.js +58 -0
- package/dist/online-js-source.js +276 -0
- package/dist/package-tool.js +51 -0
- package/dist/package.js +73 -0
- package/dist/plan.js +73 -0
- package/dist/read.js +144 -0
- package/dist/redact.js +28 -0
- package/dist/rule-support.js +587 -0
- package/dist/runtime-support.js +134 -0
- package/dist/server.js +92 -0
- package/dist/session-manager.js +30 -0
- package/dist/session.js +149 -0
- package/dist/skills.js +112 -0
- package/dist/support.js +98 -0
- package/dist/tool-types.js +127 -0
- package/dist/tools.js +59 -0
- package/dist/usage-log.js +197 -0
- package/dist/wire.js +287 -0
- package/dist/workflow-support.js +99 -0
- package/dist/write-lease.js +26 -0
- package/dist/write-lock.js +109 -0
- package/dist/write.js +176 -0
- package/dist/zip.js +156 -0
- package/docs/tool-surface-map.md +42 -0
- package/package.json +71 -6
- package/skills/omc-acceptance-criteria.md +47 -0
- package/skills/omc-business-configuration.md +257 -0
- package/skills/omc-capabilities.md +197 -0
- package/skills/omc-capability-scouting.md +55 -0
- package/skills/omc-config-draft-review.md +171 -0
- package/skills/omc-five-piece-flow.md +35 -0
- package/skills/omc-glossary.md +86 -0
- package/skills/omc-refusals.md +110 -0
- package/skills/omc-requirement-analysis.md +161 -0
- package/skills/omc-requirement-vocabulary.md +51 -0
- package/skills/omc-start-here.md +82 -0
- package/skills/omc-tool-selection.md +119 -0
- package/skills/omc-write-hazards.md +87 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { ENGINE_OWNED_KEYS, RUNTIME_DATA_WIRE, RUNTIME_QUERY_OPERATORS, SUBMIT_STATUS_TRANSITIONS } from "../lanes/runtime-data-contract.js";
|
|
2
|
+
import { applyBusinessRules } from "./businessrule.js";
|
|
3
|
+
import { businessRefusal, NOT_HANDLED } from "./state.js";
|
|
4
|
+
import { completeWorkItem, spawnApprovalWorkItem, startWorkflowDraft, startWorkflowInstance } from "./workflow.js";
|
|
5
|
+
/**
|
|
6
|
+
* verifyReplay (ticket 22 source evidence): the platform registers tokens in
|
|
7
|
+
* Redis via getReplayToken->disposeReplay and consumes them single-use —
|
|
8
|
+
* blank or unknown token -> SYS_REPLAY_ERROR 100005 "频繁重复提交!" (ErrorCode.java:68,
|
|
9
|
+
* RedisCacheService.verifyFormId deletes on success). Only the two start
|
|
10
|
+
* branches run this gate (AbstractFormRuntimeController.handleSubmit workflowCode
|
|
11
|
+
* branch; FormRuntimeController.saveWorkflow isStartWf) — formType=2 and
|
|
12
|
+
* workItemId approval submits never check it.
|
|
13
|
+
*/
|
|
14
|
+
function verifyReplayToken(state, body) {
|
|
15
|
+
const token = typeof body.replayToken === "string" ? body.replayToken : "";
|
|
16
|
+
if (!token || !state.replayTokens.has(token)) {
|
|
17
|
+
return businessRefusal(RUNTIME_DATA_WIRE.replayTokenErrcode, "频繁重复提交!");
|
|
18
|
+
}
|
|
19
|
+
state.replayTokens.delete(token);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
/** M2-runtime-data lane: query/save/load/submit. Twin of src/lanes/runtime-data.ts. */
|
|
23
|
+
/**
|
|
24
|
+
* Ticket 78 live fidelity: a runtime write against a model whose application
|
|
25
|
+
* is UNPUBLISHED answers a stable 50000 (the same call succeeds on a
|
|
26
|
+
* published app's model).
|
|
27
|
+
*/
|
|
28
|
+
function unpublishedAppRefusal(ctx, schemaCode) {
|
|
29
|
+
const code = typeof schemaCode === "string" ? schemaCode : "";
|
|
30
|
+
const model = code ? ctx.state.models.get(code) : undefined;
|
|
31
|
+
const appCode = model?.appCode ?? "test";
|
|
32
|
+
const app = ctx.state.apps.get(appCode);
|
|
33
|
+
return app?.published === false ? businessRefusal(50000, "系统异常:应用未发布") : null;
|
|
34
|
+
}
|
|
35
|
+
export const runtimeDataRoute = (ctx, input) => {
|
|
36
|
+
const q = input.query ?? {};
|
|
37
|
+
const body = (input.body ?? {});
|
|
38
|
+
switch (input.path) {
|
|
39
|
+
case "/api/api/runtime/query/list": {
|
|
40
|
+
// QueryRuntimeController face: queryCondition is a 3-level
|
|
41
|
+
// and>or>and>leaf array (level1 AND across groups, level2 OR across
|
|
42
|
+
// and-groups, level3 AND across leaves), PageVO response.
|
|
43
|
+
const schemaCode = String(body.schemaCode ?? "");
|
|
44
|
+
const rows = [...ctx.state.dataRows.values()].filter((row) => row.schemaCode === schemaCode);
|
|
45
|
+
const groups = Array.isArray(body.queryCondition) ? body.queryCondition : [];
|
|
46
|
+
const filtered = groups.length > 0
|
|
47
|
+
? rows.filter((row) => groups.every((group) => (Array.isArray(group) ? group : [group]).some((andGroup) => (Array.isArray(andGroup) ? andGroup : [andGroup]).every((leaf) => matchesCondition(leaf, row)))))
|
|
48
|
+
: rows;
|
|
49
|
+
const page = typeof body.page === "number" ? body.page : 0;
|
|
50
|
+
const size = typeof body.size === "number" ? body.size : 20;
|
|
51
|
+
const start = page * size;
|
|
52
|
+
// An unconfigured default list view omits business columns from the
|
|
53
|
+
// projection: the stored value exists but the wire row does not carry
|
|
54
|
+
// it, so explicit display columns read back null (ticket 07).
|
|
55
|
+
const unconfigured = ctx.state.unconfiguredListViews.has(schemaCode);
|
|
56
|
+
const project = (row) => unconfigured
|
|
57
|
+
? Object.fromEntries(Object.entries(row).filter(([key]) => !ctx.state.fields.has(`${schemaCode}#${key}`)))
|
|
58
|
+
: { ...row };
|
|
59
|
+
return {
|
|
60
|
+
totalElements: filtered.length,
|
|
61
|
+
totalPages: size > 0 ? Math.ceil(filtered.length / size) : 0,
|
|
62
|
+
page,
|
|
63
|
+
size,
|
|
64
|
+
content: filtered.slice(start, start + size).map((row) => ({
|
|
65
|
+
bizObjectId: row.bizObjectId,
|
|
66
|
+
schemaCode,
|
|
67
|
+
data: project(row),
|
|
68
|
+
})),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
case "/api/api/runtime/form/save": {
|
|
72
|
+
const bizObject = (body.bizObject ?? {});
|
|
73
|
+
const data = bizObject.data ?? {};
|
|
74
|
+
// Ticket 78: unpublished-app models answer a stable 50000 on runtime saves.
|
|
75
|
+
const unpublished = unpublishedAppRefusal(ctx, bizObject.schemaCode);
|
|
76
|
+
if (unpublished)
|
|
77
|
+
return unpublished;
|
|
78
|
+
// Live 2026-09-24 (bench31): a SINGLE relation (propertyType 9) given an
|
|
79
|
+
// ARRAY answers a stable 50000 (object/{id}/id work; multi 11 and staff
|
|
80
|
+
// 50/51 legitimately take arrays).
|
|
81
|
+
for (const [key, field] of ctx.state.fields) {
|
|
82
|
+
if (!key.startsWith(`${bizObject.schemaCode}#`) || !field.published)
|
|
83
|
+
continue;
|
|
84
|
+
if (Number(field.type) !== 9)
|
|
85
|
+
continue;
|
|
86
|
+
const value = data[field.code];
|
|
87
|
+
if (Array.isArray(value) && value.length > 0)
|
|
88
|
+
return businessRefusal(50000, "服务异常");
|
|
89
|
+
}
|
|
90
|
+
// Portal draft-save shape (ticket 13): workflowCode + no workItemId +
|
|
91
|
+
// no workflowInstanceId -> FormRuntimeController.saveWorkflow with
|
|
92
|
+
// finishStart=FALSE. The row is created DRAFT (only because data carries
|
|
93
|
+
// sequenceStatus DRAFT; the tool injects it) together with a DRAFT
|
|
94
|
+
// instance and a pending START work item. FormSaveVO returns the triple
|
|
95
|
+
// { objectId, workflowInstanceId, workItem:{id} }.
|
|
96
|
+
if (typeof body.workflowCode === "string" && body.workflowCode && !body.workItemId && !body.workflowInstanceId) {
|
|
97
|
+
// saveWorkflow isStartWf branch runs verifyReplay before creating
|
|
98
|
+
// anything (FormRuntimeController:996) — no token, no draft.
|
|
99
|
+
const replayRefusal = verifyReplayToken(ctx.state, body);
|
|
100
|
+
if (replayRefusal)
|
|
101
|
+
return replayRefusal;
|
|
102
|
+
// The portal carries a client-chosen id in data.id (form SDK echo);
|
|
103
|
+
// the tool generates one when the caller omits it. The fake honors
|
|
104
|
+
// both, exactly like createAndGetBizObject keeping a supplied id.
|
|
105
|
+
const id = typeof data.id === "string" && data.id ? data.id : `wfdraft-${ctx.state.dataRows.size + 1}`;
|
|
106
|
+
const sequenceStatus = data.sequenceStatus === "DRAFT" ? "DRAFT" : "PROCESSING";
|
|
107
|
+
const created = { bizObjectId: id, schemaCode: bizObject.schemaCode, sequenceStatus, ...data, id };
|
|
108
|
+
ctx.state.dataRows.set(id, created);
|
|
109
|
+
applyBusinessRules(ctx.state, "Create", created);
|
|
110
|
+
const started = startWorkflowDraft(ctx.state, {
|
|
111
|
+
workflowCode: body.workflowCode,
|
|
112
|
+
schemaCode: String(bizObject.schemaCode ?? ""),
|
|
113
|
+
bizObjectId: id,
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
objectId: id,
|
|
117
|
+
bizObjectId: id,
|
|
118
|
+
workflowInstanceId: started.workflowInstanceId,
|
|
119
|
+
// Live shape (TID 88a47a7c): getWorkItem reads inside the
|
|
120
|
+
// still-uncommitted request TX, so FormSaveVO carries NO workItem;
|
|
121
|
+
// the todo is only visible to a committed follow-up read.
|
|
122
|
+
...(ctx.omitDraftSaveWorkItem
|
|
123
|
+
? {}
|
|
124
|
+
: {
|
|
125
|
+
workItem: {
|
|
126
|
+
id: started.workItemId,
|
|
127
|
+
instanceId: started.workflowInstanceId,
|
|
128
|
+
activityCode: "fill",
|
|
129
|
+
activityName: "填写申请单",
|
|
130
|
+
state: "INPROGRESS",
|
|
131
|
+
workflowCode: body.workflowCode,
|
|
132
|
+
schemaCode: String(bizObject.schemaCode ?? ""),
|
|
133
|
+
bizObjectId: id,
|
|
134
|
+
},
|
|
135
|
+
}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// Live shape: the platform reads the target id from data["id"]; a
|
|
139
|
+
// top-level bizObject.id is ignored (kept here as a fallback only).
|
|
140
|
+
const suppliedId = [data.id, bizObject.id].find((value) => typeof value === "string" && value);
|
|
141
|
+
const existing = suppliedId ? ctx.state.dataRows.get(suppliedId) : undefined;
|
|
142
|
+
if (existing) {
|
|
143
|
+
const merged = { ...existing, ...data, bizObjectId: existing.bizObjectId, schemaCode: existing.schemaCode };
|
|
144
|
+
ctx.state.dataRows.set(String(existing.bizObjectId), merged);
|
|
145
|
+
applyBusinessRules(ctx.state, "Update", merged);
|
|
146
|
+
return { objectId: existing.bizObjectId, bizObjectId: existing.bizObjectId };
|
|
147
|
+
}
|
|
148
|
+
// Live quirk: a non-empty id absent from the store does NOT update — the
|
|
149
|
+
// facade silently creates under that id. The tool guards against this.
|
|
150
|
+
const id = suppliedId ?? `row-${ctx.state.dataRows.size + 1}`;
|
|
151
|
+
const created = { bizObjectId: id, schemaCode: bizObject.schemaCode, ...data };
|
|
152
|
+
ctx.state.dataRows.set(id, created);
|
|
153
|
+
applyBusinessRules(ctx.state, "Create", created);
|
|
154
|
+
return { bizObjectId: id, objectId: id };
|
|
155
|
+
}
|
|
156
|
+
case "/api/api/runtime/form/load": {
|
|
157
|
+
const row = ctx.state.dataRows.get(q.bizObjectId ?? q.objectId);
|
|
158
|
+
if (!row)
|
|
159
|
+
return null;
|
|
160
|
+
// The live face nests bizObject alongside the flat map; keep both so
|
|
161
|
+
// flat-field consumers and the submit chain's bizObject.data reader
|
|
162
|
+
// both work.
|
|
163
|
+
return {
|
|
164
|
+
...row,
|
|
165
|
+
bizObject: {
|
|
166
|
+
schemaCode: row.schemaCode,
|
|
167
|
+
sheetCode: row.schemaCode,
|
|
168
|
+
id: row.bizObjectId,
|
|
169
|
+
data: { ...row },
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
case "/api/api/runtime/form/delete": {
|
|
174
|
+
// Ticket 22 source evidence (FormRuntimeController:1233): @RequestParam
|
|
175
|
+
// schemaCode + objectId; validateNotEmpty(schemaCode) -> PortalException
|
|
176
|
+
// 10005 非法的参数 (ResultEnum.ILLEGAL_PARAMETER_ERR, BaseController:887);
|
|
177
|
+
// getBizObject miss -> BIZ_OBJECT_NOT_EXIST 304001 业务对象不存在; success
|
|
178
|
+
// -> ResponseResult<Boolean> data true. The old fake deleted
|
|
179
|
+
// q.bizObjectId (a key the tool never sends) unconditionally.
|
|
180
|
+
const deleteSchemaCode = String(q.schemaCode ?? "");
|
|
181
|
+
const deleteObjectId = String(q.objectId ?? "");
|
|
182
|
+
if (!deleteSchemaCode)
|
|
183
|
+
return businessRefusal(10005, "非法的参数");
|
|
184
|
+
if (!ctx.state.dataRows.has(deleteObjectId))
|
|
185
|
+
return businessRefusal(RUNTIME_DATA_WIRE.absenceErrcode, "业务对象不存在");
|
|
186
|
+
ctx.state.dataRows.delete(deleteObjectId);
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
case "/api/api/runtime/form/getReplayToken": {
|
|
190
|
+
// disposeReplay registers the token before it is returned
|
|
191
|
+
// (AbstractFormRuntimeController.replayToken -> putFormId).
|
|
192
|
+
const token = `REPLAY${Math.random().toString(36).slice(2).padEnd(32, "0")}`;
|
|
193
|
+
ctx.state.replayTokens.add(token);
|
|
194
|
+
return token;
|
|
195
|
+
}
|
|
196
|
+
case "/api/api/runtime/form/submit": {
|
|
197
|
+
// Ticket 78: unpublished-app models answer a stable 50000 on runtime submits.
|
|
198
|
+
const unpublished = unpublishedAppRefusal(ctx, body.bizObject?.schemaCode);
|
|
199
|
+
if (unpublished)
|
|
200
|
+
return unpublished;
|
|
201
|
+
// Pin the WorkItemSubmitVO contract documented in the issue 13 evidence:
|
|
202
|
+
// - formType="2" -> submitBizSheet: sets sequenceStatus=COMPLETED,
|
|
203
|
+
// returns { objectId, schemaCode, formCode }. When the payload's
|
|
204
|
+
// bizObject carries an id (the proven save->load->submit chain),
|
|
205
|
+
// the existing draft row is completed in place.
|
|
206
|
+
// - formType="1" + workflowCode -> startWorkflow: returns
|
|
207
|
+
// { workflowInstanceId }
|
|
208
|
+
if (body.formType === RUNTIME_DATA_WIRE.formTypeWorkflow) {
|
|
209
|
+
const approvalWorkItemId = typeof body.workItemId === "string" && body.workItemId ? body.workItemId : "";
|
|
210
|
+
if (approvalWorkItemId) {
|
|
211
|
+
// Ticket 22 source evidence: WorkItemSubmitVO has NO top-level
|
|
212
|
+
// bizObjectId — the engine reads bizObject.id only
|
|
213
|
+
// (initSubmitBizObject never backfills it from the work item). When
|
|
214
|
+
// it is empty, loadBizObject returns null (BizObjectManagerImpl:206)
|
|
215
|
+
// and changeBizObject/saveBizObject NPEs on objectExisted.getData()
|
|
216
|
+
// (DataChangeHandlerImpl:104 / SubmitDataChangeHandlerImpl:214) ->
|
|
217
|
+
// errcode 50000. This gate covers BOTH workItemId faces (the START
|
|
218
|
+
// draft item and the approval todo) and fires before any mutation:
|
|
219
|
+
// the platform NPE aborts the request transaction.
|
|
220
|
+
const approvalBizObject = (body.bizObject ?? {});
|
|
221
|
+
const approvalBizObjectId = typeof approvalBizObject.id === "string" && approvalBizObject.id ? approvalBizObject.id : "";
|
|
222
|
+
if (!approvalBizObjectId) {
|
|
223
|
+
return businessRefusal(RUNTIME_DATA_WIRE.approvalNpeErrcode, "engine NPE: workItem submit without bizObject.id (DataChangeHandlerImpl.objectExisted is null)");
|
|
224
|
+
}
|
|
225
|
+
// Submitting the instance's START work item (the portal draft shape):
|
|
226
|
+
// the start item finishes, the approval activity spawns a pending
|
|
227
|
+
// todo, and the SAME row id moves DRAFT -> PROCESSING. Platform
|
|
228
|
+
// response shape: { result, objectId } (no workflowInstanceId).
|
|
229
|
+
const startItem = ctx.state.workItems.get(approvalWorkItemId);
|
|
230
|
+
if (startItem?.isStart) {
|
|
231
|
+
const completed = completeWorkItem(ctx.state, approvalWorkItemId);
|
|
232
|
+
spawnApprovalWorkItem(ctx.state, {
|
|
233
|
+
instanceId: startItem.instanceId,
|
|
234
|
+
workflowCode: startItem.workflowCode,
|
|
235
|
+
schemaCode: startItem.schemaCode,
|
|
236
|
+
bizObjectId: startItem.bizObjectId,
|
|
237
|
+
});
|
|
238
|
+
const row = ctx.state.dataRows.get(startItem.bizObjectId);
|
|
239
|
+
if (row) {
|
|
240
|
+
mergeSubmittedData(row, body);
|
|
241
|
+
row.sequenceStatus = SUBMIT_STATUS_TRANSITIONS.workflowStart;
|
|
242
|
+
}
|
|
243
|
+
return { result: completed, objectId: startItem.bizObjectId };
|
|
244
|
+
}
|
|
245
|
+
// Approval submit: the addressed pending work item is completed and
|
|
246
|
+
// leaves the pending store (search_workitems no longer returns it).
|
|
247
|
+
const completed = completeWorkItem(ctx.state, approvalWorkItemId);
|
|
248
|
+
if (completed) {
|
|
249
|
+
const row = ctx.state.dataRows.get(approvalBizObjectId);
|
|
250
|
+
if (row) {
|
|
251
|
+
mergeSubmittedData(row, body);
|
|
252
|
+
row.sequenceStatus = body.agree === false ? SUBMIT_STATUS_TRANSITIONS.approvalReject : SUBMIT_STATUS_TRANSITIONS.approvalAgree;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
workflowInstanceId: typeof body.workflowInstanceId === "string" ? body.workflowInstanceId : null,
|
|
257
|
+
objectId: approvalBizObjectId || undefined,
|
|
258
|
+
workItemId: approvalWorkItemId,
|
|
259
|
+
agree: body.agree !== false,
|
|
260
|
+
completed,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// Start submit: the platform creates the in-flight bizObject and spawns
|
|
264
|
+
// the fill (finished) + approval (pending) work items.
|
|
265
|
+
// handleSubmit verifies the replay token BEFORE dispatching
|
|
266
|
+
// submitWorkflowByWorkflowCode (AbstractFormRuntimeController:156) —
|
|
267
|
+
// the gate precedes every side effect below.
|
|
268
|
+
const replayRefusal = verifyReplayToken(ctx.state, body);
|
|
269
|
+
if (replayRefusal)
|
|
270
|
+
return replayRefusal;
|
|
271
|
+
const bizObject = (body.bizObject ?? {});
|
|
272
|
+
// WorkItemSubmitVO carries no top-level bizObjectId (ticket 22): the
|
|
273
|
+
// created id comes from bizObject.id only — a top-level key is an
|
|
274
|
+
// unknown JSON property the platform silently ignores.
|
|
275
|
+
const explicitId = typeof bizObject.id === "string" && bizObject.id ? bizObject.id : "";
|
|
276
|
+
// Ticket 22 source evidence: submitWorkflowByWorkflowCode ->
|
|
277
|
+
// startWorkflowInstance(create) -> BizObjectFacadeImpl.createAndGetBizObject
|
|
278
|
+
// is a pure INSERT (BizObjectInsertModel, no existence check) — an
|
|
279
|
+
// existing id dies on the duplicate primary key (40000, live TID
|
|
280
|
+
// 771f64ee). The old fake silently mutated the row in place.
|
|
281
|
+
if (explicitId && ctx.state.dataRows.has(explicitId)) {
|
|
282
|
+
return businessRefusal(RUNTIME_DATA_WIRE.workflowStartDuplicateErrcode, `Duplicate entry '${explicitId}' for key 'PRIMARY' (create-only workflow start)`);
|
|
283
|
+
}
|
|
284
|
+
const bizObjectId = explicitId || `wfrow-${ctx.state.dataRows.size + 1}`;
|
|
285
|
+
const submittedData = typeof bizObject.data === "object" && bizObject.data ? bizObject.data : {};
|
|
286
|
+
ctx.state.dataRows.set(bizObjectId, {
|
|
287
|
+
bizObjectId,
|
|
288
|
+
sequenceStatus: SUBMIT_STATUS_TRANSITIONS.workflowStart,
|
|
289
|
+
schemaCode: String(bizObject.schemaCode ?? ""),
|
|
290
|
+
formCode: String(bizObject.sheetCode ?? bizObject.schemaCode ?? ""),
|
|
291
|
+
...submittedData,
|
|
292
|
+
});
|
|
293
|
+
const started = startWorkflowInstance(ctx.state, {
|
|
294
|
+
workflowCode: String(body.workflowCode ?? ""),
|
|
295
|
+
schemaCode: String(bizObject.schemaCode ?? ""),
|
|
296
|
+
bizObjectId,
|
|
297
|
+
});
|
|
298
|
+
return { workflowInstanceId: started.workflowInstanceId, objectId: bizObjectId, workItemId: started.workItemId };
|
|
299
|
+
}
|
|
300
|
+
if (ctx.forceSubmitMissingObjectId) {
|
|
301
|
+
// Simulate a transport / protocol deviation: submitBizSheet
|
|
302
|
+
// returns no objectId. The tool must surface this rather than
|
|
303
|
+
// fabricate sequenceStatus:COMPLETED.
|
|
304
|
+
const noIdBizObject = (body.bizObject ?? {});
|
|
305
|
+
return { schemaCode: String(noIdBizObject.schemaCode ?? "") };
|
|
306
|
+
}
|
|
307
|
+
const bizObject = (body.bizObject ?? {});
|
|
308
|
+
const existing = typeof bizObject.id === "string" ? ctx.state.dataRows.get(bizObject.id) : undefined;
|
|
309
|
+
if (existing) {
|
|
310
|
+
// submitBizSheet -> saveBizObject: the submitted data persists
|
|
311
|
+
// (updateBizObject mergeObject patch) and the status moves COMPLETED.
|
|
312
|
+
mergeSubmittedData(existing, body);
|
|
313
|
+
existing.sequenceStatus = SUBMIT_STATUS_TRANSITIONS.inPlaceSubmit;
|
|
314
|
+
return { objectId: ctx.forceSubmitMismatchedObjectId ? "unexpected-submit-id" : bizObject.id, schemaCode: String(bizObject.schemaCode ?? ""), formCode: String(bizObject.sheetCode ?? bizObject.schemaCode ?? "") };
|
|
315
|
+
}
|
|
316
|
+
const id = `sub-${ctx.state.dataRows.size + 1}`;
|
|
317
|
+
ctx.state.dataRows.set(id, {
|
|
318
|
+
bizObjectId: id,
|
|
319
|
+
sequenceStatus: SUBMIT_STATUS_TRANSITIONS.inPlaceSubmit,
|
|
320
|
+
schemaCode: String(bizObject.schemaCode ?? ""),
|
|
321
|
+
formCode: String(bizObject.sheetCode ?? bizObject.schemaCode ?? ""),
|
|
322
|
+
...(typeof bizObject.data === "object" && bizObject.data ? bizObject.data : {}),
|
|
323
|
+
});
|
|
324
|
+
return { objectId: id, schemaCode: String(bizObject.schemaCode ?? ""), formCode: String(bizObject.sheetCode ?? bizObject.schemaCode ?? "") };
|
|
325
|
+
}
|
|
326
|
+
default:
|
|
327
|
+
return NOT_HANDLED;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
/**
|
|
331
|
+
* The submit face persists bizObject.data itself (submitBizSheet ->
|
|
332
|
+
* saveBizObject; SubmitDataChangeHandlerImpl.updateBizObject): the submitted
|
|
333
|
+
* fields merge patch-style onto the stored row. Engine-owned keys the tool
|
|
334
|
+
* strips are the contract constant (ticket 20): a raw caller echoing them
|
|
335
|
+
* must not pin the row's status.
|
|
336
|
+
*/
|
|
337
|
+
function mergeSubmittedData(row, body) {
|
|
338
|
+
const bizObject = (body.bizObject ?? {});
|
|
339
|
+
if (!bizObject.data || typeof bizObject.data !== "object")
|
|
340
|
+
return;
|
|
341
|
+
for (const [key, value] of Object.entries(bizObject.data)) {
|
|
342
|
+
if (ENGINE_OWNED_KEYS.includes(key))
|
|
343
|
+
continue;
|
|
344
|
+
row[key] = value;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Leaf evaluation goes through the contract operator table (ticket 20) —
|
|
349
|
+
* the same table the DSL compiler encodes through, so every queryFilterType
|
|
350
|
+
* the compiler can emit is evaluated here (before, Gte/Lte/NotBetween fell
|
|
351
|
+
* through to an unconditional true).
|
|
352
|
+
*/
|
|
353
|
+
function matchesCondition(condition, row) {
|
|
354
|
+
if (!condition || typeof condition !== "object")
|
|
355
|
+
return true;
|
|
356
|
+
const value = condition;
|
|
357
|
+
if (value.op === "and" || value.op === "or") {
|
|
358
|
+
const items = Array.isArray(value.items) ? value.items : [];
|
|
359
|
+
const results = items.map((item) => matchesCondition(item, row));
|
|
360
|
+
return value.op === "and" ? results.every(Boolean) : results.some(Boolean);
|
|
361
|
+
}
|
|
362
|
+
const operator = RUNTIME_QUERY_OPERATORS[String(value.queryFilterType ?? "")];
|
|
363
|
+
if (!operator)
|
|
364
|
+
return true;
|
|
365
|
+
return operator.matches(row[String(value.propertyCode ?? "")], String(value.propertyValue ?? ""));
|
|
366
|
+
}
|
|
367
|
+
//# sourceMappingURL=runtime-data.js.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared in-memory store for the fake CloudPivot 8.6. The per-lane route
|
|
3
|
+
* modules read and mutate this one store; `FakeCloudPivot` owns an instance
|
|
4
|
+
* and exposes it as `state` for tests. This mirrors the single shared
|
|
5
|
+
* governance spine that the `src/lanes/*` adapters stand on.
|
|
6
|
+
*/
|
|
7
|
+
export const FAKE_RSA_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4qB3KApdqvS/Civj0U5oX6nLgRLlLEvsUJpbNZAhT3Jq9+OGDv+th8TzUVSUzb68lKs4L6mDz1sZEJVTw6OKHjhf8pPvMuSPmEb3s6oEwh5kKRbG0qwE5xdaPgkIPSOOYYcPD6WLS9wQfhwh3kWfWttM6DKAOJB65RLfEdufYI52vJ5tbWqLumj/GDeOIub4e7PVD/6pGCJJNLjdg+vzYPZrSuSc+3/W23PqVIpnINjw+QNASi9U8KHDNvXapQa1vSEzaIrBhdDpdqnDiIrnYbVB6tQ3MotCAL9lse8VAEKoxaPwmgD6k8CdwjgZDNfnHD+xC+9qPUpmXpfLvRUHIwIDAQAB";
|
|
8
|
+
/** Sentinel returned by a lane route when the path is outside its lane. */
|
|
9
|
+
export const NOT_HANDLED = Symbol("omc-fake-not-handled");
|
|
10
|
+
export function businessRefusal(errcode, errmsg) {
|
|
11
|
+
return { fakeBusinessRefusal: true, errcode, errmsg };
|
|
12
|
+
}
|
|
13
|
+
export function isBusinessRefusal(value) {
|
|
14
|
+
return typeof value === "object" && value !== null && value.fakeBusinessRefusal === true;
|
|
15
|
+
}
|
|
16
|
+
export function malformedEnvelope(payload) {
|
|
17
|
+
return { fakeMalformedEnvelope: true, payload };
|
|
18
|
+
}
|
|
19
|
+
export function isMalformedEnvelope(value) {
|
|
20
|
+
return typeof value === "object" && value !== null && value.fakeMalformedEnvelope === true;
|
|
21
|
+
}
|
|
22
|
+
export function rawResponse(payload) {
|
|
23
|
+
return { fakeRawResponse: true, payload };
|
|
24
|
+
}
|
|
25
|
+
export function isRawResponse(value) {
|
|
26
|
+
return typeof value === "object" && value !== null && value.fakeRawResponse === true;
|
|
27
|
+
}
|
|
28
|
+
export function createFakeState(options = {}) {
|
|
29
|
+
const defaultAppCode = options.applicationCode ?? "test";
|
|
30
|
+
return {
|
|
31
|
+
fields: new Map(),
|
|
32
|
+
dictionaries: new Map(),
|
|
33
|
+
dictionaryClasses: new Map(),
|
|
34
|
+
sheets: new Map(),
|
|
35
|
+
rules: new Map(),
|
|
36
|
+
ruleHistory: [],
|
|
37
|
+
ruleSeq: 0,
|
|
38
|
+
dataRules: new Map(),
|
|
39
|
+
dataRuleSeq: 0,
|
|
40
|
+
services: new Map(),
|
|
41
|
+
ieUploads: new Map(),
|
|
42
|
+
ieUploadSeq: 0,
|
|
43
|
+
categories: [{ id: "fakecat_seed", name: "插件集成服务" }],
|
|
44
|
+
dbConnPools: new Map([
|
|
45
|
+
// Pre-existing environment pool (ticket 92+ fixtures reference it as
|
|
46
|
+
// their DATA_SQL/DATA_TABLE binding target).
|
|
47
|
+
["bpm", { id: "fakepool_bpm", code: "bpm", name: "bpm", databaseType: "MySQL", datasourceType: "DATABASE", jdbcUrl: "jdbc:mysql://127.0.0.1:3306/bpm?useSSL=false", username: "bpm", password: "fake-ciphertext" }],
|
|
48
|
+
]),
|
|
49
|
+
onlineJs: new Map(),
|
|
50
|
+
listViews: new Map(),
|
|
51
|
+
reportPages: new Map(),
|
|
52
|
+
biDashboards: new Map(),
|
|
53
|
+
workflows: new Map(),
|
|
54
|
+
workItems: new Map(),
|
|
55
|
+
dataRows: new Map(),
|
|
56
|
+
replayTokens: new Set(),
|
|
57
|
+
unconfiguredListViews: new Set(),
|
|
58
|
+
/** Ticket 79 live-fidelity: remaining transient 不存在列 publish refusals per schema (self-healing column-sync failure). */
|
|
59
|
+
transientPublishFailures: new Map(),
|
|
60
|
+
fieldDetailDrift: new Map(),
|
|
61
|
+
models: new Map(),
|
|
62
|
+
apps: new Map([[defaultAppCode, { id: "app-1", code: defaultAppCode, name: "Test App", groupId: "group-1" }]]),
|
|
63
|
+
appGroups: new Map([["group-1", { id: "group-1", code: "default", name: "默认分组" }]]),
|
|
64
|
+
callLog: [],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=state.js.map
|