omc-core 0.4.36 → 0.4.37

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.
@@ -1,1014 +0,0 @@
1
- import { createHash, randomUUID } from "node:crypto";
2
- import { canonicalCloudPivotBaselineFingerprint, canonicalCloudPivotEnvironmentFingerprint } from "./cloudpivot-fingerprints.js";
3
- import { canonicalFormalDiscoveryFingerprint } from "./formal-capability-discovery.js";
4
- import { CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT, decodeDraftControl } from "./cloudpivot-low-code.js";
5
- import { compileNativeOperations, runNativeConfigurationPackage } from "./cloudpivot-native-configuration.js";
6
- import { buildKnowledgePack } from "./knowledge-pack-builder.js";
7
- import { KnowledgePackRuntime } from "./knowledge-pack-runtime.js";
8
- import { createDeliveryPlanApproval, deliveryPlanApprovalHash, proposeDeliveryPlan } from "./two-stage-change-package.js";
9
- import { validateConfigurationIntent } from "./configuration-intent.js";
10
- import { SENSITIVE_SECRET_VALUE } from "./delivery-graph.js";
11
- import { appendApprovalHumanBlock } from "./approval-frame-readability.js";
12
- import { isTicket08TargetSchema } from "./ticket08-test-model-mutation.js";
13
- import { activeFieldFamily, fieldFamilyAlwaysReadonlyDemoMember, fieldFamilyMemberNamesProse, fieldFamilyMembers, fieldFamilyRuleDemoMember } from "./families/registry.js";
14
- import { isTicket09TargetField, TICKET09_FIELD_FAMILY } from "./ticket09-test-field-mutation.js";
15
- import { TICKET10_FIELD_PUBLISH_ROUTE } from "./ticket10-test-field-publish.js";
16
- /**
17
- * Ticket 11 (usable-model ladder, rung 3) promotes exactly one independently
18
- * bounded CloudPivot `test` form mutation: the default-form draft save that
19
- * binds the run's own published fixture field into the form layout and
20
- * configures its form-control options (readonly, visibility,
21
- * required-per-form). The composite chain runs the executable Ticket 08
22
- * model-create, Ticket 09 field-create, and Ticket 10 field-publish
23
- * capabilities first; the form-draft package consumes the completed,
24
- * behavior-validated field-publish execution report plus fresh authoritative
25
- * readbacks (published field present with the published flag decoded; form
26
- * pre-state present with the control absent and published=false). The package
27
- * is independently immutable and independently dual-approved. No evidence is
28
- * inferred from the field rungs; form publish, app publish, Environment
29
- * release, production, staging, tenant administration, Redis, restart,
30
- * deployment, and runtime CRUD stay rejected.
31
- *
32
- * Authoritative 8.6.26 contract (Stage 0 read-only live probe + decompiled
33
- * engine, research note .scratch/omc-usable-model-ladder/research/
34
- * form-schema-contract.md):
35
- * - read: GET /api/api/app/bizsheet/get?schemaCode&sheetCode returns the
36
- * BizFormModel with `published`, `existDraft`, and the draft/published JSON
37
- * sides (BizFormController.java:174-230); an unconfigured default form has
38
- * draftViewJson/draftAttributesJson = null, published=false, existDraft=true
39
- * (live probe of customer/project); the configured reference form
40
- * (agent_test_customer) carries the layout in draftViewJson (JSON string:
41
- * array of rows of control keys) and the control map in draftAttributesJson
42
- * (JSON string: key -> {key, type, options}); a bound data field's control
43
- * key IS the field code, control type 1 = ShortText for TEXT fields;
44
- * - draft save: POST /api/api/app/bizsheet/update with the full echoed
45
- * BizSheetVO, @RequireAdmin, schema lock, sheetType required
46
- * (BizFormController.java:276-330, BizSheetVO.java,
47
- * ModelManagementFacadeImpl.java:712-760); the server persists the entity as
48
- * submitted, so the DTO echoes the authoritative GET with only the draft
49
- * members changed — published stays false and published*Json stays null.
50
- * A draft save is never a form publish: /publish (EDIT_AND_PUBLISH_FORM,
51
- * BizFormController.java:824-970) is the separate ticket-04 rung;
52
- * - per-form control options (ShortText/schema.ts, AgentBizSchema.java:77;
53
- * Ticket 20 amendment per type-matrix §9): options.visible (boolean) plus
54
- * options.readonlyFormula (boolean — the ALWAYS-readonly key, the one
55
- * rule-family key legitimately written directly on options). The
56
- * condition strings (requiredFormula/readonlyCondition/displayFormula)
57
- * are NEVER written on the draft options: the panel wipes them on load
58
- * (resetConditionRules) and re-hydrates them from the bizdatarule rows,
59
- * so required-per-form is authored as a dataRuleType 4 rule row on the
60
- * schema (the Ticket 20 rung), distinct from the model-field
61
- * propertyEmpty constraint;
62
- * - designer-standard scaffolding (research §13, amendment for ticket 06):
63
- * when the target default form's draft layout is empty (the auto-created
64
- * default form), the draft save composes the scaffolding the designer would
65
- * insert — Title control (type 203, named after the form/model display
66
- * name, useDataTitle: true, styleInfo), the system row [creater(101),
67
- * createdTime(104, format YYYY-MM-DD HH:mm:ss), sequenceNo(100, UID
68
- * seqNoSetting — a composer constant, not a scheme reference)], one group
69
- * (type 200, 业务标题) — then the business field rows. Title/Group keys share
70
- * one timestamp per composition (Title<ts>/Group<ts>) derived from the
71
- * evidence capture clock, mirroring the designer's insert pattern; the
72
- * server persists keys as submitted and never rewrites them. A non-empty
73
- * draft layout keeps the append-row composition — the scaffolding is never
74
- * injected under or rewriting an existing team layout;
75
- * - cleanup: no separate form-draft delete is promoted; the form draft state
76
- * is design-time state on the schema's default form and dies with the
77
- * separately approved staged cleanup (Ticket 10 published-field cleanup,
78
- * then the Ticket 08 model physical delete). Form-level absence is then
79
- * confirmed by bizsheet/get returning a non-success envelope with the exact
80
- * errcode recorded (candidate: 302024 BIZ_SHEET_NOT_EXIST, ErrorCode.java:
81
- * 256 — pinned exactly by the live run, never guessed). Uncertain outcomes
82
- * stop, retain, and report; the orphan backstop reuses the Ticket 08 path.
83
- */
84
- export const TICKET11_FORM_DRAFT_ROUTE = "ticket11-test-form-draft";
85
- export const TICKET11_FORM_DRAFT_CAPABILITY_ID = "cloudpivot.test.form-draft-8.6.26";
86
- export const TICKET11_FORM_CLEANUP_CAPABILITY_ID = "cloudpivot.test.form-draft-cascade-absence-8.6.26";
87
- export const TICKET11_FORM_DRAFT_KNOWLEDGE_PACK_ID = "cloudpivot-8.6-ticket11-test-form-draft";
88
- export const TICKET11_TEST_ENVIRONMENT_NAME = "test";
89
- export const TICKET11_TEST_APPLICATION_CODE = "test";
90
- export const TICKET11_REFERENCE_EVIDENCE_VERSION = "8.6.26";
91
- export const TICKET11_FORM_DRAFT_ENDPOINT = "/api/api/app/bizsheet/update";
92
- export const TICKET11_FORM_READBACK_ENDPOINT = "/api/api/app/bizsheet/get";
93
- export const TICKET11_FORM_PUBLISH_ENDPOINT = "/api/api/app/bizsheet/publish";
94
- export const TICKET11_PROHIBITED_MUTATION_TERMS = Object.freeze([
95
- "app-publish", "form-publish", "bizsheet/publish", "deploy", "restart", "release", "redis", "tenant-admin", "runtime-crud",
96
- "model:", "view", "folder-create", "folder-delete",
97
- ]);
98
- /**
99
- * Designer-standard scaffolding contract (research §13, pinned live
100
- * 2026-09-04 on agent_test_customer + decompiled sources + the
101
- * product-family consultant composer): when the target default form's draft
102
- * layout is empty, the draft save composes [Title<ts>] (type 203,
103
- * useDataTitle), the system row [creater(101), createdTime(104),
104
- * sequenceNo(100)], [Group<ts>] (type 200), then the business field rows.
105
- */
106
- export const TICKET11_SCAFFOLD_SYSTEM_ROW = Object.freeze(["creater", "createdTime", "sequenceNo"]);
107
- export const TICKET11_SCAFFOLD_SYSTEM_CONTROL_TYPES = Object.freeze({ creater: 101, createdTime: 104, sequenceNo: 100 });
108
- /** The UID rule value is a designer/composer constant, not a per-model scheme reference (research §13.2). */
109
- export const TICKET11_SCAFFOLD_SEQNO_UID_VALUE = "aa860e64ff2e4f86851a661d663ae358";
110
- export const TICKET11_SCAFFOLD_GROUP_NAME = "业务标题";
111
- export const TICKET11_SCAFFOLD_DATE_FORMAT = "YYYY-MM-DD HH:mm:ss";
112
- /** One shared timestamp per composition mirrors the designer's Title<ts>/Group<ts> pattern (research §13.4). */
113
- export function ticket11ScaffoldControlKeys(capturedAt) {
114
- const epochMs = Date.parse(capturedAt);
115
- if (!Number.isFinite(epochMs) || epochMs <= 0)
116
- throw new Error("Ticket 11 scaffold composition requires the evidence capture clock; nothing ships on guessed layout keys.");
117
- return Object.freeze({ titleKey: `Title${epochMs}`, groupKey: `Group${epochMs}` });
118
- }
119
- function deepFrozen(value) {
120
- if (Array.isArray(value))
121
- return Object.freeze(value.map(deepFrozen));
122
- if (value && typeof value === "object")
123
- return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, deepFrozen(item)])));
124
- return value;
125
- }
126
- /** The designer-standard scaffolding control map entries, mirroring the live agent_test_customer sample member-for-member (research §13.1). Deep-frozen: the composition is an immutable contract value. */
127
- export function buildTicket11ScaffoldControls(input) {
128
- return deepFrozen({
129
- [input.titleKey]: {
130
- key: input.titleKey,
131
- type: 203,
132
- options: {
133
- name: input.formName,
134
- name_i18n: { en: input.formName, zh: input.formName },
135
- visible: true,
136
- labelVisible: true,
137
- span: 24,
138
- labelAlign: "",
139
- style: "",
140
- tips: "",
141
- dataItemName: "",
142
- useDataTitle: true,
143
- styleInfo: { height: 1, bgColor: "#ffffff", bgImg: "", textAlign: "center", fontSize: 28, textColor: "#111218", blod: true, italic: false, textCss: "", textCssSwitch: false },
144
- },
145
- },
146
- creater: {
147
- key: "creater",
148
- type: TICKET11_SCAFFOLD_SYSTEM_CONTROL_TYPES.creater,
149
- options: { name: "创建人", name_i18n: "", visible: true, labelVisible: true, span: 8, labelAlign: "", style: "", tips: "", dataItemName: "", widgetType: "", dataItemType: "", mappings: "", verifyFormula: "", defaultValue: "" },
150
- },
151
- createdTime: {
152
- key: "createdTime",
153
- type: TICKET11_SCAFFOLD_SYSTEM_CONTROL_TYPES.createdTime,
154
- options: { name: "创建时间", name_i18n: "", visible: true, labelVisible: true, span: 8, labelAlign: "", style: "", tips: "", dataItemName: "", widgetType: "", dataItemType: "", format1: TICKET11_SCAFFOLD_DATE_FORMAT, format: TICKET11_SCAFFOLD_DATE_FORMAT, syncFormate: true, verifyFormula: "", defaultValue: "" },
155
- },
156
- sequenceNo: {
157
- key: "sequenceNo",
158
- type: TICKET11_SCAFFOLD_SYSTEM_CONTROL_TYPES.sequenceNo,
159
- options: { name: "单据号", name_i18n: "", visible: true, labelVisible: true, span: 8, labelAlign: "", style: "", tips: "", dataItemName: "", widgetType: "", dataItemType: "", seqNoSetting: [{ type: "UID", value: TICKET11_SCAFFOLD_SEQNO_UID_VALUE }], formPropertyType: 100, verifyFormula: "", displayFormula: "", defaultValue: "" },
160
- },
161
- [input.groupKey]: {
162
- key: input.groupKey,
163
- type: 200,
164
- options: { name: TICKET11_SCAFFOLD_GROUP_NAME, tips: "", expand: true, align: "", style: "", styleInfo: { fontSize: 18, textColor: "#111218", blod: true, italic: false, textCss: "", textCssSwitch: false }, span: 24 },
165
- },
166
- });
167
- }
168
- /** An auto-created default form decodes with an empty draft layout (research §2): draftViewJson null/[] and draftAttributesJson null/{}. */
169
- export function ticket11DraftLayoutIsEmpty(form) {
170
- let view = null;
171
- let attributes = null;
172
- try {
173
- view = form.draftViewJson === null ? [] : JSON.parse(form.draftViewJson);
174
- attributes = form.draftAttributesJson === null ? {} : JSON.parse(form.draftAttributesJson);
175
- }
176
- catch {
177
- return false;
178
- }
179
- return Array.isArray(view) && view.length === 0 && typeof attributes === "object" && attributes !== null && !Array.isArray(attributes) && Object.keys(attributes).length === 0;
180
- }
181
- export function decodeTicket11ScaffoldPresence(form) {
182
- if (form.draftViewJson === null || form.draftAttributesJson === null)
183
- return { decodable: false, rowOrderMatchesContract: false };
184
- let view;
185
- let attributes;
186
- try {
187
- view = JSON.parse(form.draftViewJson);
188
- attributes = JSON.parse(form.draftAttributesJson);
189
- }
190
- catch {
191
- return { decodable: false, rowOrderMatchesContract: false };
192
- }
193
- if (!Array.isArray(view) || view.some((row) => !Array.isArray(row) || row.some((key) => typeof key !== "string")) || !attributes || typeof attributes !== "object" || Array.isArray(attributes))
194
- return { decodable: false, rowOrderMatchesContract: false };
195
- const rows = view;
196
- const map = attributes;
197
- const entry = (key) => {
198
- const value = map[key];
199
- return value && typeof value === "object" && !Array.isArray(value) && typeof value.type === "number" && value.options && typeof value.options === "object"
200
- ? value
201
- : undefined;
202
- };
203
- // The row contract is positional (research §13.5): row 0 is the single-key
204
- // Title row, row 1 is exactly the system row, row 2 is the single-key Group
205
- // row. Extra or duplicated scaffold rows decode as non-contract.
206
- const isTitleKey = (key) => /^Title\d+$/.test(key);
207
- const isGroupKey = (key) => /^Group\d+$/.test(key);
208
- const titleShaped = rows.length >= 3 && rows[0].length === 1 && isTitleKey(rows[0][0]) && entry(rows[0][0]) !== undefined;
209
- const systemShaped = rows.length >= 3 && rows[1].length === 3 && rows[1][0] === "creater" && rows[1][1] === "createdTime" && rows[1][2] === "sequenceNo";
210
- const groupShaped = rows.length >= 3 && rows[2].length === 1 && isGroupKey(rows[2][0]) && entry(rows[2][0]) !== undefined;
211
- if (!titleShaped || !systemShaped || !groupShaped)
212
- return { decodable: false, rowOrderMatchesContract: false };
213
- const title = entry(rows[0][0]);
214
- const group = entry(rows[2][0]);
215
- const systemTypes = rows[1].map((key) => entry(key)?.type ?? -1);
216
- const scaffoldRowsOnly = rows.slice(0, 3).every((row, index) => index === 0 ? row.length === 1 && isTitleKey(row[0] ?? "") : index === 1 ? row.length === 3 : row.length === 1 && isGroupKey(row[0] ?? "")) && rows.slice(3).every((row) => row.length === 0 || (!isTitleKey(row[0] ?? "") && !isGroupKey(row[0] ?? "") && row[0] !== "creater" && row[0] !== "createdTime" && row[0] !== "sequenceNo"));
217
- return Object.freeze({
218
- decodable: true,
219
- title: Object.freeze({ key: rows[0][0], type: title.type, useDataTitle: title.options.useDataTitle === true, name: title.options.name }),
220
- systemRow: Object.freeze({ keys: Object.freeze([...rows[1]]), types: Object.freeze(systemTypes) }),
221
- group: Object.freeze({ key: rows[2][0], type: group.type }),
222
- rowOrder: Object.freeze(rows.map((row) => row.join("+"))),
223
- rowOrderMatchesContract: scaffoldRowsOnly,
224
- });
225
- }
226
- const TICKET11_UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
227
- const SHA256 = /^[a-f0-9]{64}$/i;
228
- /** The named `test` environment and its dedicated test application are hard preconditions. */
229
- export function assertTicket11TestFormDraftEnvironment(settings) {
230
- if (settings.environmentName !== "test")
231
- throw new Error("Ticket 11 form draft is restricted to the named test environment; production and staging are rejected.");
232
- if (!settings.baseUrl.trim() || !/^https?:\/\/\S+$/i.test(settings.baseUrl.trim()))
233
- throw new Error("Ticket 11 form draft requires an absolute configured test environment endpoint.");
234
- if (settings.testApplicationCode !== "test")
235
- throw new Error("Ticket 11 form draft requires the dedicated test application to be test.");
236
- }
237
- export function ticket11DeliveryId(testRunId) { return `ticket11-form-draft-${testRunId}`; }
238
- export function ticket11FormDraftScope(targetSchemaCode, targetSheetCode) { return `cloudpivot:form:${targetSchemaCode}/${targetSheetCode}`; }
239
- /** Design-time designer direct URL; the fixture form draft stays inspectable here without any form publish. */
240
- export function ticket11DesignerDirectUrl(targetSchemaCode, targetSheetCode) { return `/admin/#/apps/model/${TICKET11_TEST_APPLICATION_CODE}/${targetSchemaCode}/form-design/${targetSheetCode}`; }
241
- export function buildTicket11FormCleanupContract(testRunId, targetSchemaCode, targetSheetCode, targetFieldCodes) {
242
- if (!TICKET11_UUID_V4.test(testRunId) || !isTicket08TargetSchema(targetSchemaCode) || targetSheetCode !== targetSchemaCode || targetFieldCodes.length !== TICKET09_FIELD_FAMILY.length || targetFieldCodes.some((code) => !isTicket09TargetField(code)) || new Set(targetFieldCodes).size !== TICKET09_FIELD_FAMILY.length)
243
- throw new Error("Ticket 11 form cleanup contract requires the exact host-owned testRunId, fixture schema, default sheet, and one distinct family field code per active family member.");
244
- return Object.freeze({
245
- capabilityId: TICKET11_FORM_CLEANUP_CAPABILITY_ID,
246
- testRunId,
247
- targetSchemaCode,
248
- targetSheetCode,
249
- targetFieldCodes,
250
- semantics: "form-draft-state-is-design-time-state-on-the-default-form;no-separate-form-delete-is-promoted;the-separately-approved-staged-cleanup(ticket10-published-field-cleanup-then-ticket08-model-physical-delete)cascades-the-form-draft-away",
251
- absenceReadback: { method: "GET", endpoint: TICKET11_FORM_READBACK_ENDPOINT, expected: "non-success-envelope-recorded-errcode" },
252
- ownership: "schema-sheet-and-field-bound-to-testRunId",
253
- replayPolicy: "absent-is-no-op",
254
- uncertainPolicy: "stop-retain-report-no-retry",
255
- });
256
- }
257
- /** Deterministic, run-independent contract identities pinned by the Knowledge Pack promotion. */
258
- export function ticket11FormDraftContractHash() { return digest(ticket11FormDraftContract()); }
259
- export function ticket11FormCleanupContractHash() { return digest(ticket11FormCleanupContractTemplate()); }
260
- function ticket11FormDraftContract() {
261
- return {
262
- capabilityId: TICKET11_FORM_DRAFT_CAPABILITY_ID,
263
- operationKind: "form-draft-save",
264
- method: "POST",
265
- endpoint: CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT.endpoint,
266
- readbackEndpoint: CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT.readbackEndpoint,
267
- preflight: CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT.preflight,
268
- dto: "BizSheetVO:echoed-bizsheet-get-with-only-draft-members-changed",
269
- scaffoldingComposition: {
270
- when: "target-default-form-draft-layout-empty(draftViewJson null-or-[] and draftAttributesJson null-or-{})",
271
- rows: [["Title<shared-ts>"], [...TICKET11_SCAFFOLD_SYSTEM_ROW], ["Group<shared-ts>"], ["<numericFieldCode>"], ["<logicalFieldCode>"]],
272
- titleControl: { type: 203, useDataTitle: true, name: "form-display-name" },
273
- systemControlTypes: { ...TICKET11_SCAFFOLD_SYSTEM_CONTROL_TYPES },
274
- groupControl: { type: 200, name: TICKET11_SCAFFOLD_GROUP_NAME },
275
- sequenceNoRule: { type: "UID", value: TICKET11_SCAFFOLD_SEQNO_UID_VALUE },
276
- keyNaming: "Title<ts>/Group<ts> share one timestamp per composition from the evidence capture clock (research §13.4)",
277
- nonEmptyLayout: "append-row composition unchanged; scaffolding never overwrites an existing layout",
278
- },
279
- controlBinding: "control-key===published-family-field-code-per-field",
280
- boundControls: CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT.boundControls.map((binding) => ({ propertyTypeName: binding.propertyTypeName, controlType: binding.controlType, options: binding.options })),
281
- optionRepresentation: `${fieldFamilyMembers().map((member) => {
282
- const alwaysReadonly = member.familyPosition === activeFieldFamily().alwaysReadonlyDemoPosition ? "/readonlyFormula(always-readonly boolean)" : "";
283
- const ruleTarget = member.familyPosition === activeFieldFamily().ruleDemoTargetPosition ? "/no-required-readonly-keys(required-rule target stays editable)" : "";
284
- const multiPart = "multi" in member.control.options ? `/multi=${member.cardinality === "multi"}` : "";
285
- return `${member.propertyTypeName}(${member.propertyType}):visible/labelVisible/span${multiPart}${alwaysReadonly}${ruleTarget}`;
286
- }).join(";")};NEVER-any-condition-strings(requiredFormula/readonlyCondition/displayFormula)-type-matrix-§9-those-live-on-bizdatarule-rows`,
287
- publishedStateInvariant: "published-flag-and-published-json-unchanged;draft-save-only-never-a-form-publish-app-publish-or-Environment-release",
288
- scope: "default-form-draft-save",
289
- permission: "@RequireAdmin",
290
- transportMode: "serialized-server-form-draft-8.6.26",
291
- capability: CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT,
292
- evidence: [...CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT.evidence],
293
- requiredPermissions: ["configuration-write"],
294
- prohibited: [...TICKET11_PROHIBITED_MUTATION_TERMS],
295
- };
296
- }
297
- function ticket11FormCleanupContractTemplate() {
298
- return {
299
- capabilityId: TICKET11_FORM_CLEANUP_CAPABILITY_ID,
300
- operationKind: "form-draft-cascade-absence-confirmation",
301
- semantics: "form-draft-state-dies-with-the-model-physical-delete;no-separate-form-delete",
302
- absenceReadback: `${TICKET11_FORM_READBACK_ENDPOINT}:non-success-envelope-recorded-errcode`,
303
- ownership: "schema-sheet-and-field-bound-to-testRunId",
304
- replayPolicy: "absent-is-no-op",
305
- uncertainPolicy: "stop-retain-report-no-retry",
306
- };
307
- }
308
- /** Package operations must be exactly one bounded default-form draft-save step binding BOTH family controls for the bound target. */
309
- export function assertTicket11OperationsBounded(input) {
310
- if (input.operations.length !== 1)
311
- throw new Error("Ticket 11 packages must contain exactly one bounded operation.");
312
- const operation = input.operations[0];
313
- const identity = `${operation.id} ${operation.target} ${operation.path}`.toLowerCase();
314
- if (TICKET11_PROHIBITED_MUTATION_TERMS.some((term) => identity.includes(term)) || !operation.id.startsWith("form-draft:"))
315
- throw new Error(`Ticket 11 operation ${operation.id} is outside the bounded operation set; prohibited families (form publish, app publish, deploy, restart, release, redis, tenant administration, runtime CRUD, model/view/folder writes) remain rejected.`);
316
- const expected = { id: `form-draft:${input.targetSchemaCode}/${input.targetSheetCode}/${input.targetFieldCodes.join("+")}`, target: `form:${input.targetSchemaCode}/${input.targetSheetCode}`, path: TICKET11_FORM_DRAFT_ENDPOINT, transportMode: "serialized-server-form-draft-8.6.26" };
317
- if (operation.id !== expected.id || operation.path !== expected.path || operation.transportMode !== expected.transportMode || operation.target !== expected.target)
318
- throw new Error(`Ticket 11 operation ${operation.id} does not match the exact target binding for ${expected.target}.`);
319
- }
320
- /** Builds the signed, version-bound pack promoting exactly the single test form-draft capability. */
321
- export function buildTicket11FormDraftKnowledgePack(input) {
322
- const compiler = { id: "src/ticket11-test-form-draft.ts:createTicket11FormDraftPlanApproval", sha256: digest("omc-ticket11-compiler-v1") };
323
- const decoder = { id: "src/dsh-host-plugin.ts:createCloudPivotWriteTransport", sha256: digest("omc-ticket11-decoder-v1") };
324
- return buildKnowledgePack({
325
- packId: TICKET11_FORM_DRAFT_KNOWLEDGE_PACK_ID,
326
- packVersion: "1.0.0",
327
- sourceVersion: TICKET11_REFERENCE_EVIDENCE_VERSION,
328
- sourceArtifacts: [{ id: "cloudpivot-8.6.26-serialized-form-draft-capability", sha256: digest(CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT) }],
329
- references: [
330
- { id: "omc-form-schema-contract-research", sha256: digest("omc-form-schema-contract-research") },
331
- { id: TICKET11_FORM_DRAFT_ENDPOINT, sha256: digest(TICKET11_FORM_DRAFT_ENDPOINT) },
332
- { id: TICKET11_FORM_READBACK_ENDPOINT, sha256: digest(TICKET11_FORM_READBACK_ENDPOINT) },
333
- ],
334
- compilers: [compiler],
335
- decoders: [decoder],
336
- capabilities: [{
337
- id: TICKET11_FORM_DRAFT_CAPABILITY_ID,
338
- status: "runtime-verified",
339
- promote: true,
340
- compilerRef: compiler.id,
341
- decoderRef: decoder.id,
342
- sourceEvidence: [...CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT.evidence, TICKET11_FORM_DRAFT_ENDPOINT, TICKET11_FORM_READBACK_ENDPOINT, "BizFormController.java:174-230", "BizFormController.java:276-330", "ErrorCode.java:256"],
343
- runtimeEvidence: ["omc-form-schema-contract-research", "cloudpivot.form-draft.serialized-server-8.6.26"],
344
- operationPromotions: [{
345
- id: "form-draft-save",
346
- status: "promoted",
347
- operationContractHash: ticket11FormDraftContractHash(),
348
- sourceRefs: [...CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT.evidence, TICKET11_FORM_DRAFT_ENDPOINT, TICKET11_FORM_READBACK_ENDPOINT],
349
- versionFamily: "8.6",
350
- referenceVersion: TICKET11_REFERENCE_EVIDENCE_VERSION,
351
- requiredEvidence: ["cleanup", "discovery", "permission", "readback", "test", "write"],
352
- evidence: ["cleanup", "discovery", "permission", "readback", "test", "write"],
353
- blockedReasons: [],
354
- }],
355
- }],
356
- signerKeyId: input.signerKeyId,
357
- signingKey: input.signingKey,
358
- createdAt: input.createdAt,
359
- });
360
- }
361
- /** Loads the pack against CloudPivot 8.6.26 and pins the single executable capability and contract hash. */
362
- export function loadTicket11KnowledgePack(artifact, trustedKeys) {
363
- const runtime = new KnowledgePackRuntime(trustedKeys);
364
- const load = runtime.load(artifact, TICKET11_REFERENCE_EVIDENCE_VERSION);
365
- if (!load.accepted)
366
- throw new Error(`Ticket 11 Knowledge Pack is not authoritative: ${load.blockingReasons.join(", ")}.`);
367
- const active = runtime.active();
368
- const capabilities = active.capabilities;
369
- if (capabilities.length !== 1 || capabilities[0].id !== TICKET11_FORM_DRAFT_CAPABILITY_ID)
370
- throw new Error("Ticket 11 Knowledge Pack must promote exactly the single test form-draft capability; evidence inference from the field rungs or to any other capability (form publish included) is rejected.");
371
- const hashes = capabilities[0].operationContractHashes;
372
- if (hashes.length !== 1 || hashes[0] !== ticket11FormDraftContractHash())
373
- throw new Error("Ticket 11 Knowledge Pack operation contract hash does not match the canonical form-draft contract.");
374
- return { runtime, packId: active.manifest.packId, contentDigest: active.manifest.contentSha256, executableCapabilities: capabilities.map(({ id }) => id) };
375
- }
376
- /**
377
- * Decodes the authoritative bizsheet/get envelope. A successful decode
378
- * (errcode=0 with a record payload bound to the queried schema/sheet) is
379
- * "present"; any non-success business envelope is "absent" with the exact
380
- * errcode recorded (the form-cascade absence contract; the exact absence code
381
- * — e.g. 302024 BIZ_SHEET_NOT_EXIST — is pinned by the live run).
382
- */
383
- export function decodeTicket11FormStateEvidence(input) {
384
- const url = `${TICKET11_FORM_READBACK_ENDPOINT}?schemaCode=${encodeURIComponent(input.schemaCode)}&sheetCode=${encodeURIComponent(input.sheetCode)}`;
385
- const query = { schemaCode: input.schemaCode, sheetCode: input.sheetCode };
386
- const httpStatus = input.httpStatus ?? 200;
387
- const envelope = input.body;
388
- if (!envelope || typeof envelope !== "object" || Array.isArray(envelope))
389
- return Object.freeze({ classification: "blocked", reason: "malformed-response", query, url, httpStatus });
390
- if (httpStatus < 200 || httpStatus >= 300)
391
- return Object.freeze({ classification: "blocked", reason: "http-error", query, url, httpStatus });
392
- if (envelope.errcode !== 0) {
393
- const errcode = typeof envelope.errcode === "number" ? envelope.errcode : undefined;
394
- return Object.freeze({ classification: "absent", reason: "form-absence", query, url, httpStatus, ...(errcode !== undefined ? { errcode } : {}) });
395
- }
396
- const data = envelope.data;
397
- if (!data || typeof data !== "object" || Array.isArray(data))
398
- return Object.freeze({ classification: "blocked", reason: "malformed-response", query, url, httpStatus });
399
- const record = data;
400
- if (record.code !== input.sheetCode || record.schemaCode !== input.schemaCode || typeof record.name !== "string")
401
- return Object.freeze({ classification: "blocked", reason: "malformed-response", query, url, httpStatus });
402
- const form = Object.freeze({
403
- ...(typeof record.id === "string" && record.id ? { id: record.id } : {}),
404
- code: input.sheetCode,
405
- schemaCode: input.schemaCode,
406
- name: record.name,
407
- published: record.published === true,
408
- existDraft: record.existDraft === true,
409
- sheetType: typeof record.sheetType === "number" ? record.sheetType : null,
410
- draftViewJson: typeof record.draftViewJson === "string" ? record.draftViewJson : null,
411
- draftAttributesJson: typeof record.draftAttributesJson === "string" ? record.draftAttributesJson : null,
412
- draftActionsJson: typeof record.draftActionsJson === "string" ? record.draftActionsJson : null,
413
- draftHtmlJson: typeof record.draftHtmlJson === "string" ? record.draftHtmlJson : null,
414
- publishedViewJson: typeof record.publishedViewJson === "string" ? record.publishedViewJson : null,
415
- publishedAttributesJson: typeof record.publishedAttributesJson === "string" ? record.publishedAttributesJson : null,
416
- publishedActionsJson: typeof record.publishedActionsJson === "string" ? record.publishedActionsJson : null,
417
- publishedHtmlJson: typeof record.publishedHtmlJson === "string" ? record.publishedHtmlJson : null,
418
- });
419
- return Object.freeze({ classification: "present", reason: "exact-presence", query, url, httpStatus, form });
420
- }
421
- /** Loose draft-control presence decode for preflight/evidence checks (never throws on absent draft JSON). */
422
- export function decodeTicket11DraftControlPresence(form, fieldCode) {
423
- if (form.draftViewJson === null || form.draftAttributesJson === null)
424
- return { decodable: true, layoutOccurrences: 0 };
425
- let view;
426
- let attributes;
427
- try {
428
- view = JSON.parse(form.draftViewJson);
429
- attributes = JSON.parse(form.draftAttributesJson);
430
- }
431
- catch {
432
- return { decodable: false, layoutOccurrences: 0 };
433
- }
434
- if (!Array.isArray(view) || view.some((row) => !Array.isArray(row) || row.some((key) => typeof key !== "string")) || !attributes || typeof attributes !== "object" || Array.isArray(attributes))
435
- return { decodable: false, layoutOccurrences: 0 };
436
- const layoutOccurrences = view.flat().filter((key) => key === fieldCode).length;
437
- const entry = attributes[fieldCode];
438
- if (layoutOccurrences === 0 && entry === undefined)
439
- return { decodable: true, layoutOccurrences: 0 };
440
- const control = decodeDraftControl(form.draftViewJson, form.draftAttributesJson, fieldCode);
441
- return { decodable: true, layoutOccurrences, ...(control ? { control } : {}) };
442
- }
443
- /** Host-owned composite-chain evidence capture: consumes the completed family field-publish handoff and proves BOTH published family fields are present and their controls are still absent with the published state pinned. */
444
- export function captureTicket11FormDraftEvidence(input) {
445
- assertTicket11TestFormDraftEnvironment(input.settings);
446
- if (!TICKET11_UUID_V4.test(input.testRunId))
447
- throw new Error("Ticket 11 evidence capture requires the run's host-owned UUID v4 testRunId.");
448
- const handoff = input.handoff;
449
- const report = handoff.fieldPublishExecutionReport;
450
- const publishEvidence = handoff.fieldPublishEvidence;
451
- if (!handoff.fieldPublishPackageId || !SHA256.test(handoff.fieldPublishPackageHash) || !/[1-9a-f]/i.test(handoff.fieldPublishPackageHash) || report.route !== TICKET10_FIELD_PUBLISH_ROUTE || report.status !== "completed" || report.mutationExecuted !== true || report.behavior?.status !== "validated")
452
- throw new Error("Ticket 11 form draft requires the completed, behavior-validated field-publish handoff; serial writes never assume state.");
453
- if (report.testRunId !== input.testRunId || publishEvidence.testRunId !== input.testRunId)
454
- throw new Error("Ticket 11 composite chain requires the single run testRunId across the model, field, publish, and form packages.");
455
- const targetSchemaCode = publishEvidence.targetSchemaCode;
456
- const targetFieldCodes = publishEvidence.targetFieldCodes;
457
- const targetSheetCode = targetSchemaCode;
458
- if (!isTicket08TargetSchema(targetSchemaCode) || report.targetSchemaCode !== targetSchemaCode || report.targetFieldCodes.length !== targetFieldCodes.length || report.targetFieldCodes.some((fieldCode, index) => fieldCode !== targetFieldCodes[index]))
459
- throw new Error("Ticket 11 fixture family identity diverges from the completed field-publish handoff.");
460
- const modelPresence = input.modelPresence;
461
- if (modelPresence.classification !== "present" || modelPresence.query.schemaCode !== targetSchemaCode)
462
- throw new Error("Ticket 11 form draft requires the fixture model to be present in fresh authoritative readback.");
463
- targetFieldCodes.forEach((targetFieldCode, index) => {
464
- const member = TICKET09_FIELD_FAMILY[index];
465
- const publishedFieldPresence = input.publishedFieldPresence[index];
466
- if (publishedFieldPresence.classification !== "present" || publishedFieldPresence.reason !== "exact-presence" || publishedFieldPresence.query.schemaCode !== targetSchemaCode || publishedFieldPresence.query.propertyCode !== targetFieldCode || publishedFieldPresence.query.isPublish !== "true")
467
- throw new Error(`Ticket 11 evidence capture requires exact authenticated published-state presence for the ${member.propertyTypeName} handoff field; form work only ever consumes published configuration.`);
468
- if (!publishedFieldPresence.field || publishedFieldPresence.field.code !== targetFieldCode || publishedFieldPresence.field.schemaCode !== targetSchemaCode || publishedFieldPresence.field.published !== true)
469
- throw new Error(`Ticket 11 evidence capture requires the ${member.propertyTypeName} field's published flag to decode from the authoritative published readback.`);
470
- });
471
- const formPreState = input.formPreState;
472
- if (formPreState.classification !== "present" || formPreState.reason !== "exact-presence" || formPreState.query.schemaCode !== targetSchemaCode || formPreState.query.sheetCode !== targetSheetCode || !formPreState.form)
473
- throw new Error("Ticket 11 evidence capture requires the default form to be present in fresh authoritative readback.");
474
- if (formPreState.form.published !== false || formPreState.form.publishedViewJson !== null || formPreState.form.publishedAttributesJson !== null || formPreState.form.publishedActionsJson !== null || formPreState.form.publishedHtmlJson !== null)
475
- throw new Error("Ticket 11 evidence capture requires the fixture form to be unpublished (published=false, every published JSON side null); this rung never touches a published form.");
476
- for (const targetFieldCode of targetFieldCodes) {
477
- const presence = decodeTicket11DraftControlPresence(formPreState.form, targetFieldCode);
478
- if (!presence.decodable)
479
- throw new Error("Ticket 11 evidence capture could not decode the form draft JSON; nothing ships on guessed schema JSON.");
480
- if (presence.layoutOccurrences !== 0 || presence.control)
481
- throw new Error(`Ticket 11 evidence capture requires exact control absence for the handoff family field ${targetFieldCode}; duplicate form binding is rejected.`);
482
- }
483
- const authoritativeOrigin = input.discovery.authoritativeOrigin;
484
- const enterpriseId = input.discovery.enterpriseId;
485
- if (!authoritativeOrigin || !enterpriseId)
486
- throw new Error("Ticket 11 evidence capture requires authenticated environment fingerprint evidence.");
487
- const environmentFingerprint = canonicalCloudPivotEnvironmentFingerprint({ environment: TICKET11_TEST_ENVIRONMENT_NAME, authoritativeOrigin, applicationCode: TICKET11_TEST_APPLICATION_CODE, enterpriseId });
488
- if (environmentFingerprint !== publishEvidence.environmentFingerprint)
489
- throw new Error("Ticket 11 form draft is outside the field-publish handoff environment fingerprint.");
490
- const evidence = Object.freeze({
491
- route: TICKET11_FORM_DRAFT_ROUTE,
492
- capabilityId: TICKET11_FORM_DRAFT_CAPABILITY_ID,
493
- environmentName: TICKET11_TEST_ENVIRONMENT_NAME,
494
- applicationCode: TICKET11_TEST_APPLICATION_CODE,
495
- folder: publishEvidence.folder,
496
- targetSchemaCode,
497
- targetSheetCode,
498
- targetFieldCodes,
499
- testRunId: input.testRunId,
500
- environmentFingerprint,
501
- baselineFingerprint: canonicalCloudPivotBaselineFingerprint({ environmentFingerprint, applicationCode: TICKET11_TEST_APPLICATION_CODE, discovery: input.discovery }),
502
- discoveryFingerprint: canonicalFormalDiscoveryFingerprint(input.discovery),
503
- handoff,
504
- modelPresence,
505
- publishedFieldPresence: Object.freeze([...input.publishedFieldPresence]),
506
- formPreState,
507
- boundControls: CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT.boundControls.map((binding) => ({ propertyTypeName: binding.propertyTypeName, controlType: binding.controlType, options: binding.options })),
508
- cleanupContract: buildTicket11FormCleanupContract(input.testRunId, targetSchemaCode, targetSheetCode, targetFieldCodes),
509
- designerDirectUrl: ticket11DesignerDirectUrl(targetSchemaCode, targetSheetCode),
510
- capturedAt: new Date((input.now ?? Date.now)()).toISOString(),
511
- });
512
- if ([evidence.environmentFingerprint, evidence.baselineFingerprint, evidence.discoveryFingerprint].some((value) => !SHA256.test(value)))
513
- throw new Error("Ticket 11 evidence fingerprints are not canonical.");
514
- if (SENSITIVE_SECRET_VALUE.test(stable(evidence)))
515
- throw new Error("Ticket 11 evidence contains prohibited sensitive data.");
516
- return evidence;
517
- }
518
- export const TICKET11_EVIDENCE_DEFAULT_TTL_MS = 25 * 60_000;
519
- export const TICKET11_EVIDENCE_MAX_TTL_MS = 30 * 60_000;
520
- /** Single-use, TTL-bounded evidence store; consumed exactly once by plan creation. */
521
- export class Ticket11EvidenceStore {
522
- records = new Map();
523
- now;
524
- ttlMs;
525
- lastNow;
526
- constructor(options = {}) {
527
- this.now = options.now ?? Date.now;
528
- this.ttlMs = options.ttlMs ?? TICKET11_EVIDENCE_DEFAULT_TTL_MS;
529
- if (!Number.isFinite(this.ttlMs) || this.ttlMs < 1 || this.ttlMs > TICKET11_EVIDENCE_MAX_TTL_MS)
530
- throw new Error("Ticket 11 evidence TTL must be between 1ms and its safety maximum.");
531
- }
532
- capture(evidence) {
533
- validateTicket11Evidence(evidence);
534
- const evidenceId = `ticket11-evidence-${randomUUID()}`;
535
- const expiresAt = this.readClock() + this.ttlMs;
536
- const stored = Object.freeze({ ...evidence, evidenceArtifactId: evidenceId });
537
- this.records.set(evidenceId, { evidence: stored, expiresAt, consumed: false });
538
- return { evidenceId, expiresAt, evidence: stored };
539
- }
540
- consume(evidenceId) {
541
- const record = this.records.get(evidenceId);
542
- if (!record)
543
- throw new Error("Ticket 11 evidence artifact is missing or divergent.");
544
- if (record.consumed)
545
- throw new Error("Ticket 11 evidence has already been consumed.");
546
- if (record.expiresAt <= this.readClock())
547
- throw new Error("Ticket 11 evidence is stale or expired.");
548
- record.consumed = true;
549
- return record.evidence;
550
- }
551
- resolve(evidenceId) {
552
- const record = this.records.get(evidenceId);
553
- if (!record || !record.consumed)
554
- throw new Error("Ticket 11 evidence artifact is missing or has not been consumed by planning.");
555
- if (record.expiresAt <= this.readClock())
556
- throw new Error("Ticket 11 evidence artifact is stale or expired.");
557
- return record.evidence;
558
- }
559
- readClock() {
560
- const currentTime = this.now();
561
- if (!Number.isFinite(currentTime))
562
- throw new Error("Ticket 11 evidence clock is invalid; fail closed.");
563
- if (this.lastNow !== undefined && currentTime < this.lastNow)
564
- throw new Error("Ticket 11 evidence clock moved backwards; fail closed.");
565
- this.lastNow = currentTime;
566
- return currentTime;
567
- }
568
- }
569
- /**
570
- * Composes the exact evidenced BizSheetVO draft-save DTO: the authoritative
571
- * pre-state form echoed with only the draft members changed. When the target
572
- * form's draft layout is EMPTY (the auto-created default form), the draft
573
- * side is the designer-standard scaffolding — Title control (203, named after
574
- * the form/model, useDataTitle), the system row (creater 101 / createdTime
575
- * 104 / sequenceNo 100), one group (200) — followed by the published field's
576
- * business row with the pinned control options (research §13.5). A non-empty
577
- * layout keeps the append-row composition; the scaffolding is never injected
578
- * under or rewriting an existing team layout. The published side stays
579
- * false/null and no publish-only BizSheetVO member is set.
580
- */
581
- export function buildTicket11FormDraftPayload(evidence) {
582
- validateTicket11Evidence(evidence);
583
- const pre = evidence.formPreState.form;
584
- const fieldCreateEvidence = evidence.handoff.fieldPublishEvidence.handoff.fieldCreateEvidence;
585
- let view = [];
586
- let attributes = {};
587
- if (pre.draftViewJson !== null || pre.draftAttributesJson !== null) {
588
- try {
589
- view = pre.draftViewJson === null ? [] : JSON.parse(pre.draftViewJson);
590
- attributes = pre.draftAttributesJson === null ? {} : JSON.parse(pre.draftAttributesJson);
591
- }
592
- catch {
593
- throw new Error("Ticket 11 payload composition could not decode the authoritative form draft JSON.");
594
- }
595
- if (!Array.isArray(view) || view.some((row) => !Array.isArray(row) || row.some((key) => typeof key !== "string")) || !attributes || typeof attributes !== "object" || Array.isArray(attributes))
596
- throw new Error("Ticket 11 payload composition requires the authoritative form draft JSON structure; nothing ships on guessed schema JSON.");
597
- }
598
- // The two typed controls, in family contract order: Date(3) then Time(28),
599
- // each keyed by its published family field's code with the per-type pinned
600
- // options (the visible family key plus the always-readonly boolean on the
601
- // Date control — the required-rule demo target Time control carries
602
- // neither — and the type-specific keys; NEVER the condition strings —
603
- // type-matrix §9).
604
- const controls = evidence.targetFieldCodes.map((fieldCode, index) => {
605
- const binding = CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT.boundControls[index];
606
- const member = fieldFamilyMembers()[index];
607
- const fieldName = fieldCreateEvidence.targetFields[index].fieldName;
608
- if (view.flat().includes(fieldCode) || attributes[fieldCode] !== undefined)
609
- throw new Error(`Ticket 11 payload composition requires exact control absence for the bound ${binding.propertyTypeName} family field.`);
610
- const pinnedOptions = member.control.resolveControlOptions
611
- ? member.control.resolveControlOptions({ schemaCode: evidence.targetSchemaCode, memberFieldCodes: evidence.targetFieldCodes, testRunId: evidence.testRunId })
612
- : binding.options;
613
- return { key: fieldCode, type: binding.controlType, options: { name: fieldName, name_i18n: { en: fieldName, zh: fieldName }, ...pinnedOptions } };
614
- });
615
- if (view.length === 0 && Object.keys(attributes).length === 0) {
616
- const { titleKey, groupKey } = ticket11ScaffoldControlKeys(evidence.capturedAt);
617
- const scaffold = buildTicket11ScaffoldControls({ titleKey, groupKey, formName: pre.name });
618
- return {
619
- ...pre,
620
- schemaCode: evidence.targetSchemaCode,
621
- code: evidence.targetSheetCode,
622
- name: pre.name,
623
- sheetType: pre.sheetType ?? 0,
624
- published: false,
625
- publishedViewJson: null,
626
- publishedAttributesJson: null,
627
- publishedActionsJson: null,
628
- publishedHtmlJson: null,
629
- draftViewJson: JSON.stringify([[titleKey], [...TICKET11_SCAFFOLD_SYSTEM_ROW], [groupKey], ...evidence.targetFieldCodes.map((fieldCode) => [fieldCode])]),
630
- draftAttributesJson: JSON.stringify({ ...scaffold, ...Object.fromEntries(controls.map((control) => [control.key, control])) }),
631
- };
632
- }
633
- return {
634
- ...pre,
635
- schemaCode: evidence.targetSchemaCode,
636
- code: evidence.targetSheetCode,
637
- name: pre.name,
638
- sheetType: pre.sheetType ?? 0,
639
- published: false,
640
- publishedViewJson: null,
641
- publishedAttributesJson: null,
642
- publishedActionsJson: null,
643
- publishedHtmlJson: null,
644
- draftViewJson: JSON.stringify([...view, ...evidence.targetFieldCodes.map((fieldCode) => [fieldCode])]),
645
- draftAttributesJson: JSON.stringify({ ...attributes, ...Object.fromEntries(controls.map((control) => [control.key, control])) }),
646
- };
647
- }
648
- export function buildTicket11ConfigurationIntent(evidence) {
649
- validateTicket11Evidence(evidence);
650
- const target = `${evidence.targetSchemaCode}/${evidence.targetSheetCode}`;
651
- return {
652
- intentVersion: 1,
653
- targetFamily: "8.6",
654
- environmentBinding: { application: evidence.applicationCode, model: evidence.targetSchemaCode, baselineFingerprint: evidence.baselineFingerprint },
655
- sourceRequirement: `Ticket 11 maintainer test (${activeFieldFamily().familyId} family rung): bind the run's ${fieldFamilyMembers().length} published fixture family fields (${fieldFamilyMemberNamesProse()}) into the default form layout as their registered selector controls — NEVER any condition strings (requiredFormula/readonlyCondition live on bizdatarule rows per type-matrix §9) — and save the form as a draft, composing the designer-standard scaffolding (Title 203 useDataTitle, system row 101/104/100, group 200) when the form's draft layout is empty with the business rows in family order — draft save only, never a form publish, app publish, or Environment release — with authoritative readback, per-type control options decode, behavior validation, and the stated cascade cleanup contract.`,
656
- resources: [
657
- { id: target, kind: "form", state: "update", properties: { schemaCode: evidence.targetSchemaCode, sheetCode: evidence.targetSheetCode, boundFamilyFields: [...evidence.targetFieldCodes], draftSaveOnly: true, boundControls: evidence.boundControls.map((binding) => ({ propertyTypeName: binding.propertyTypeName, controlType: binding.controlType })) } },
658
- ...evidence.targetFieldCodes.map((fieldCode) => ({ id: `${evidence.targetSchemaCode}/${fieldCode}`, kind: "field", state: "existing", properties: { schemaCode: evidence.targetSchemaCode, published: true } })),
659
- ],
660
- relationships: evidence.targetFieldCodes.map((fieldCode) => ({ from: target, to: `${evidence.targetSchemaCode}/${fieldCode}`, kind: "bindsTo" })),
661
- lifecycle: [{ resourceId: target, transitions: ["draft-save"], order: 1, idempotent: false, dependencyChecks: ["completed-family-field-publish-handoff", "published-family-field-presence-and-control-absence-preflight"], recoveryBoundary: `form:${target}` }],
662
- behavior: [{ id: "form-draft-family-control-binding", kind: "structure", requiredCapabilityIds: [TICKET11_FORM_DRAFT_CAPABILITY_ID], resourceIds: [target], obligations: [`each published family field's typed control is present in the draft layout and attribute map exactly once`, `each registered control decodes its pinned options; ${fieldFamilyAlwaysReadonlyDemoMember().propertyTypeName} carries the always-readonly boolean and ${fieldFamilyRuleDemoMember().propertyTypeName} stays free of required/readonly keys`, "no control options carry the condition strings requiredFormula/readonlyCondition/displayFormula — the designer panel wipes them on load and re-hydrates them from the bizdatarule rows (type-matrix §9); required-per-form is authored by the Ticket 20 rule row, never here", "when the pre-state draft layout was empty, the designer-standard scaffolding decodes (Title 203 useDataTitle form-named, system row 101/104/100, group 200) with the contracted row order Title, system row, group, then every registered business row in family order", "the published flag and published JSON are unchanged by the draft save", "designer direct URL stays inspectable"] }],
663
- acceptance: [{ id: "ticket11-form-draft-readback", scenario: "family-form-draft-binding-and-published-invariance", actor: "omc-maintainer-opencode", assertions: [`the draft readback decodes all ${fieldFamilyMembers().length} registered selector controls with their per-member options`, "the published state readback is unchanged before and after the draft save", "fixture remains inspectable at the designer direct URL"], readbackInvariants: [`${TICKET11_FORM_READBACK_ENDPOINT}?schemaCode=<model>&sheetCode=<sheet> decodes every draft control`, "published flag and published*Json are byte-identical before and after"], outcome: "validated" }],
664
- cleanup: [{ id: "ticket11-form-cascade-cleanup", boundary: `ticket11:${evidence.testRunId}:form-draft-cascade-absence`, dependencyChecks: ["staged family field cleanup", "model physical delete", "form absence readback"], resources: [`form:${target}`] }],
665
- implementationConstraints: { nativePreferred: true, backendRequired: false, prohibitedPaths: ["form.online-js", "business-rule", "integration", "backend.extension"], approvalExceptions: [] },
666
- evidenceRefs: [TICKET11_FORM_DRAFT_CAPABILITY_ID, TICKET11_FORM_DRAFT_ENDPOINT, TICKET11_FORM_READBACK_ENDPOINT, ...CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT.evidence],
667
- unresolved: [],
668
- };
669
- }
670
- function ticket11FormDraftOperation(evidence) {
671
- const target = `form:${evidence.targetSchemaCode}/${evidence.targetSheetCode}`;
672
- return {
673
- id: `form-draft:${evidence.targetSchemaCode}/${evidence.targetSheetCode}/${evidence.targetFieldCodes.join("+")}`,
674
- method: "POST",
675
- path: TICKET11_FORM_DRAFT_ENDPOINT,
676
- target,
677
- payload: buildTicket11FormDraftPayload(evidence),
678
- expectedReadback: `${target}:draft`,
679
- evidence: [TICKET11_FORM_DRAFT_CAPABILITY_ID, ...CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT.evidence, TICKET11_FORM_READBACK_ENDPOINT],
680
- transportMode: "serialized-server-form-draft-8.6.26",
681
- capability: CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT,
682
- };
683
- }
684
- /** Creates and proposes the immutable Ticket 11 Delivery Plan Approval for the single default-form draft-save step. */
685
- export function createTicket11FormDraftPlanApproval(input) {
686
- const evidence = validateTicket11Evidence(input.evidence);
687
- assertDigest(input.knowledgePackDigest, "knowledge pack digest");
688
- const operation = ticket11FormDraftOperation(evidence);
689
- assertTicket11OperationsBounded({ route: TICKET11_FORM_DRAFT_ROUTE, operations: [operation], targetSchemaCode: evidence.targetSchemaCode, targetSheetCode: evidence.targetSheetCode, targetFieldCodes: evidence.targetFieldCodes });
690
- const intent = buildTicket11ConfigurationIntent(evidence);
691
- if (validateConfigurationIntent(intent).length)
692
- throw new Error("Ticket 11 Configuration Intent is invalid; planning is blocked.");
693
- const binding = {
694
- deliveryId: ticket11DeliveryId(evidence.testRunId),
695
- environment: TICKET11_TEST_ENVIRONMENT_NAME,
696
- baselineFingerprint: evidence.baselineFingerprint,
697
- environmentFingerprint: evidence.environmentFingerprint,
698
- discoveryFingerprint: evidence.discoveryFingerprint,
699
- ...(evidence.evidenceArtifactId ? { evidenceArtifactId: evidence.evidenceArtifactId } : {}),
700
- };
701
- const planHash = digest({ route: TICKET11_FORM_DRAFT_ROUTE, capabilityId: TICKET11_FORM_DRAFT_CAPABILITY_ID, target: `${evidence.targetSchemaCode}/${evidence.targetSheetCode}/${evidence.targetFieldCodes.join("+")}`, testRunId: evidence.testRunId, operation: { id: operation.id, readback: operation.expectedReadback }, familyFieldCodes: [...evidence.targetFieldCodes], publishHandoff: { packageId: evidence.handoff.fieldPublishPackageId, packageHash: evidence.handoff.fieldPublishPackageHash }, folder: evidence.folder, fingerprints: { baseline: evidence.baselineFingerprint, environment: evidence.environmentFingerprint, discovery: evidence.discoveryFingerprint }, cleanupContractHash: ticket11FormCleanupContractHash(), knowledgePackDigest: input.knowledgePackDigest, prohibited: [...TICKET11_PROHIBITED_MUTATION_TERMS] });
702
- const base = createDeliveryPlanApproval({
703
- binding,
704
- requirementHash: digest({ target: `${evidence.targetSchemaCode}/${evidence.targetSheetCode}`, formDraft: true }),
705
- clarificationHash: digest("ticket11-test-form-draft"),
706
- intentHash: digest(intent),
707
- decisionHash: digest(TICKET11_FORM_DRAFT_ROUTE),
708
- graphHash: digest([operation.id]),
709
- riskHash: digest(TICKET11_RISKS),
710
- validationPlanHash: digest("authoritative-form-draft-readback-control-decode-and-published-state-invariance"),
711
- cleanupPlanHash: ticket11FormCleanupContractHash(),
712
- allowedOperationScope: [ticket11FormDraftScope(evidence.targetSchemaCode, evidence.targetSheetCode)],
713
- planHash,
714
- applicationCode: TICKET11_TEST_APPLICATION_CODE,
715
- folderId: evidence.folder.id,
716
- folderName: evidence.folder.name,
717
- targets: [`${evidence.targetSchemaCode}/${evidence.targetSheetCode}`],
718
- });
719
- const approval = {
720
- ...base,
721
- route: TICKET11_FORM_DRAFT_ROUTE,
722
- testRunId: evidence.testRunId,
723
- targetSchemaCode: evidence.targetSchemaCode,
724
- targetSheetCode: evidence.targetSheetCode,
725
- targetFieldCodes: evidence.targetFieldCodes,
726
- capabilityId: TICKET11_FORM_DRAFT_CAPABILITY_ID,
727
- knowledgePackDigest: input.knowledgePackDigest,
728
- cleanupContractHash: ticket11FormCleanupContractHash(),
729
- environmentName: TICKET11_TEST_ENVIRONMENT_NAME,
730
- };
731
- const proposal = { ...approval, approvalHash: deliveryPlanApprovalHash(approval) };
732
- proposeDeliveryPlan(input.ledger, proposal);
733
- return { approval: proposal, operation, cleanupContract: evidence.cleanupContract, intent, binding };
734
- }
735
- const TICKET11_RISKS = [
736
- "Exactly one default-form draft save binding the run's own published fixture field is in scope — draft save only, never a form publish, app publish, or Environment release; model, view, runtime, folder, deployment, restart, release, Redis, and tenant administration remain rejected.",
737
- "The form draft state dies with the separately approved staged cleanup (Ticket 10 published-field cleanup, then the Ticket 08 model physical delete); form-level absence is confirmed by the authoritative bizsheet/get readback and uncertain outcomes stop, retain, and report.",
738
- ];
739
- /** Compiles the immutable one-step form-draft package bound to the approved plan, pack digest, and pinned contract hash. */
740
- export function prepareTicket11FormDraftPackage(input) {
741
- const evidence = validateTicket11Evidence(input.evidence);
742
- const approval = requireTicket11Approval(input.approval);
743
- const persisted = requirePersistedApprovedPlan(input.ledger, approval);
744
- assertTicket11BindingMatchesEvidence(persisted.binding, evidence);
745
- const scope = ticket11FormDraftScope(evidence.targetSchemaCode, evidence.targetSheetCode);
746
- if (!persisted.allowedOperationScope.includes(scope))
747
- throw new Error(`Ticket 11 package step is outside the approved operation scope: ${scope}.`);
748
- if (input.pinnedFormDraftContractHash !== ticket11FormDraftContractHash())
749
- throw new Error("Ticket 11 pinned form-draft contract hash does not match the canonical 8.6.26 capability contract.");
750
- assertDigest(input.knowledgePackDigest, "knowledge pack digest");
751
- if (input.knowledgePackDigest !== approval.knowledgePackDigest)
752
- throw new Error("Ticket 11 knowledge pack digest diverges from the approved plan binding.");
753
- const operation = ticket11FormDraftOperation(evidence);
754
- assertTicket11OperationsBounded({ route: TICKET11_FORM_DRAFT_ROUTE, operations: [operation], targetSchemaCode: evidence.targetSchemaCode, targetSheetCode: evidence.targetSheetCode, targetFieldCodes: evidence.targetFieldCodes });
755
- const snapshotBase = { id: `snapshot:${operation.id}`, target: operation.target, identity: operation.target, recoveryBoundary: operation.target, state: "absent", baselineFingerprint: persisted.binding.baselineFingerprint };
756
- const snapshot = { ...snapshotBase, snapshotHash: digest(snapshotBase) };
757
- const pkg = compileNativeOperations({
758
- operations: [operation],
759
- binding: persisted.binding,
760
- planApproval: persisted,
761
- snapshotManifest: [snapshot],
762
- releasePlanHash: digest("ticket11-no-release"),
763
- recoveryPolicyHash: digest("ticket11-form-draft-step-only"),
764
- knowledgePack: { id: TICKET11_FORM_DRAFT_KNOWLEDGE_PACK_ID, digest: input.knowledgePackDigest },
765
- capabilityEvidence: [TICKET11_FORM_DRAFT_CAPABILITY_ID, "cloudpivot.form-draft.serialized-server-8.6.26", TICKET11_FORM_READBACK_ENDPOINT, ...evidence.targetFieldCodes.map((fieldCode) => `familyField:${fieldCode}`), `publishHandoff:${evidence.handoff.fieldPublishPackageId}`, `folder:${evidence.folder.id}`, `testRunId:${evidence.testRunId}`, `baselineFingerprint:${persisted.binding.baselineFingerprint}`],
766
- requiredPermissions: ["configuration-write"],
767
- packageId: `ticket11-form-draft-${digest(persisted.approvalId).slice(0, 32)}`,
768
- ledger: input.ledger,
769
- });
770
- input.ledger.createPackage(pkg);
771
- return { package: pkg, operations: [operation], planApproval: persisted, dryRun: ticket11DryRun(evidence, [operation]), evidence };
772
- }
773
- /** Read-only dry-run projection; never a CloudPivot write and never a form/app publish operation. */
774
- function ticket11DryRun(evidence, operations) {
775
- const readbackOperations = [
776
- { id: `readback:form:${evidence.targetSchemaCode}:${evidence.targetSheetCode}`, method: "GET", path: TICKET11_FORM_READBACK_ENDPOINT, target: `form:${evidence.targetSchemaCode}/${evidence.targetSheetCode}`, payload: { schemaCode: evidence.targetSchemaCode, sheetCode: evidence.targetSheetCode }, expectedReadback: `form:${evidence.targetSchemaCode}/${evidence.targetSheetCode}:read`, evidence: ["BizFormController.java:174-230"] },
777
- ];
778
- const body = JSON.stringify({ execution: "read-only-dry-run", operations, readbackOperations, blocked: [], prohibitedActions: ["cloudpivot-write"] }, null, 2) + "\n";
779
- return { execution: "read-only-dry-run", operations, readbackOperations, blocked: [], prohibitedActions: ["cloudpivot-write"], artifact: { name: "cloudpivot-ticket11-form-draft-dry-run.json", body, sha256: digest(body), mimeType: "application/json" } };
780
- }
781
- export function formatTicket11DeliveryPlanApprovalFrame(approval) {
782
- const value = requireTicket11Approval(approval);
783
- return appendApprovalHumanBlock(`Approve Ticket 11 form draft Delivery Plan Approval ${value.approvalId} hash ${value.approvalHash}; plan hash ${value.planHash}; capability ${value.capabilityId}; environment test; application test; environment fingerprint ${value.binding.environmentFingerprint}; baseline fingerprint ${value.binding.baselineFingerprint}; folder ${value.folderId} (${value.folderName}); fixture model ${value.targetSchemaCode}; default sheet ${value.targetSheetCode}; bound published family fields ${value.targetFieldCodes.join(" + ")} (${fieldFamilyMemberNamesProse()}); testRunId ${value.testRunId}; exact scope ${value.allowedOperationScope[0]}; one default-form draft save step (POST ${TICKET11_FORM_DRAFT_ENDPOINT} with the echoed BizSheetVO: when the draft layout is empty the designer-standard scaffolding — Title 203 useDataTitle, system row 101/104/100, group 200 — is composed before the business rows; layout binding of all registered selector controls with per-member options and no condition strings anywhere — required is authored as a bizdatarule row by the Ticket 20 rung) — draft save only, never a form publish, app publish, or Environment release — with authoritative readback, per-type behavior validation, and the stated cascade cleanup contract.`, { capability: "ticket11-form-draft", kind: "delivery-plan", identities: [value.targetSchemaCode, value.targetSheetCode, ...value.targetFieldCodes], scope: [value.allowedOperationScope[0]] });
784
- }
785
- export function formatTicket11EnvironmentChangeApprovalFrame(input) {
786
- const value = requireTicket11Approval(input.approval);
787
- return appendApprovalHumanBlock(`Approve Ticket 11 form draft Environment Change Approval for package ${input.packageId} hash ${input.packageHash}; plan approval ${value.approvalId} hash ${value.approvalHash}; environment test; folder ${value.folderId} (${value.folderName}); fixture model ${value.targetSchemaCode}; default sheet ${value.targetSheetCode}; bound published family fields ${value.targetFieldCodes.join(" + ")}; testRunId ${value.testRunId}; exact scope ${value.allowedOperationScope[0]}; exactly one default-form draft save step binding all registered selector controls (when the draft layout is empty the designer-standard scaffolding is composed with the business rows; draft save only, never a form publish, app publish, or Environment release) executed by the single Forge configuration writer lease with authoritative readback and the published state proven unchanged.`, { capability: "ticket11-form-draft", kind: "environment-change", identities: [value.targetSchemaCode, value.targetSheetCode, ...value.targetFieldCodes], scope: [value.allowedOperationScope[0]] });
788
- }
789
- /** Validates real form-draft behavior beyond HTTP 2xx: the control binding and options decode from the authoritative readback, the published state is byte-unchanged, and — when the pre-state layout was empty — the designer-standard scaffolding decodes with the contracted row order (research §13.6). */
790
- export function validateTicket11FormDraftBehavior(input) {
791
- const evidence = validateTicket11Evidence(input.evidence);
792
- const checks = [];
793
- const add = (id, ok, detail) => checks.push({ id, status: ok ? "validated" : "blocked", detail });
794
- const before = input.before;
795
- const beforeOk = before.classification === "present" && before.query.schemaCode === evidence.targetSchemaCode && before.query.sheetCode === evidence.targetSheetCode && Boolean(before.form);
796
- add("pre-state-present", beforeOk, beforeOk ? "the pre-save authoritative form readback decoded the default form." : "the pre-save authoritative form readback did not decode the default form.");
797
- const after = input.after;
798
- const afterOk = after.classification === "present" && after.query.schemaCode === evidence.targetSchemaCode && after.query.sheetCode === evidence.targetSheetCode && Boolean(after.form);
799
- evidence.targetFieldCodes.forEach((fieldCode, fieldIndex) => {
800
- const binding = CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT.boundControls[fieldIndex];
801
- // Ticket 03b: the per-type decode descriptors (display name, extra
802
- // readback guards) are the ACTIVE family's registered member data.
803
- const member = fieldFamilyMembers()[fieldIndex];
804
- const suffix = binding.propertyTypeName.toLowerCase();
805
- const control = afterOk ? decodeTicket11DraftControlPresence(after.form, fieldCode) : { decodable: false, layoutOccurrences: 0 };
806
- const bound = afterOk && control.decodable && control.layoutOccurrences === 1 && control.control?.key === fieldCode && control.control.type === binding.controlType;
807
- add(`published-field-control-bound-${suffix}`, bound, bound ? `the draft layout carries the published ${binding.propertyTypeName} field's control exactly once with the ${member.control.controlDisplayName} binding.` : `the authoritative draft readback did not decode the published ${binding.propertyTypeName} field's control binding after the draft save returned success.`);
808
- const options = control.control?.options;
809
- // Per-type decode: the pinned family keys (visible + the always-readonly
810
- // boolean for Date; visible and the format keys for Time — the Time
811
- // control deliberately carries no required/readonly keys) plus the
812
- // type-specific keys, via the shared capability seam so the contract
813
- // cannot drift from the transport gates. Runtime-resolved control
814
- // options (relevance schemaCode/queryCode) win over the static map.
815
- const expectedOptions = member.control.resolveControlOptions
816
- ? member.control.resolveControlOptions({ schemaCode: evidence.targetSchemaCode, memberFieldCodes: evidence.targetFieldCodes, testRunId: evidence.testRunId })
817
- : binding.options;
818
- if (options) {
819
- for (const [key, value] of Object.entries(expectedOptions)) {
820
- add(`control-${suffix}-${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`, JSON.stringify(options[key]) === JSON.stringify(value), `the ${binding.propertyTypeName} control's pinned option ${key} decodes as ${JSON.stringify(options[key])} (expected ${JSON.stringify(value)}) from the authoritative draft readback.`);
821
- }
822
- for (const guard of member.control.readbackGuards) {
823
- add(`control-${suffix}-${guard.id}`, guard.passes(options), guard.detail);
824
- }
825
- add(`control-${suffix}-no-condition-strings`, !("requiredFormula" in options) && !("readonlyCondition" in options) && !("displayFormula" in options), `the ${binding.propertyTypeName} control options carry no condition strings (requiredFormula/readonlyCondition/displayFormula): the panel wipes and re-hydrates these from the bizdatarule rows on load (type-matrix §9), so they are authored as rule rows — never as control options.`);
826
- }
827
- else {
828
- add(`control-${suffix}-options`, false, `the ${binding.propertyTypeName} control options did not decode from the authoritative draft readback.`);
829
- }
830
- });
831
- if (ticket11DraftLayoutIsEmpty(evidence.formPreState.form)) {
832
- const scaffold = afterOk ? decodeTicket11ScaffoldPresence(after.form) : { decodable: false, rowOrderMatchesContract: false };
833
- const formName = evidence.formPreState.form.name;
834
- const titleOk = scaffold.decodable && scaffold.title?.type === 203 && scaffold.title.useDataTitle === true && scaffold.title.name === formName;
835
- add("scaffolding-title-control", titleOk, titleOk ? `the draft layout decodes the Title control (type 203, useDataTitle) named after the form (${JSON.stringify(formName)}).` : "the authoritative draft readback did not decode the designer-standard Title control (type 203, useDataTitle, form-named) for the empty default form.");
836
- const systemOk = scaffold.decodable && scaffold.systemRow !== undefined && scaffold.systemRow.keys.join(",") === TICKET11_SCAFFOLD_SYSTEM_ROW.join(",") && scaffold.systemRow.types.join(",") === [TICKET11_SCAFFOLD_SYSTEM_CONTROL_TYPES.creater, TICKET11_SCAFFOLD_SYSTEM_CONTROL_TYPES.createdTime, TICKET11_SCAFFOLD_SYSTEM_CONTROL_TYPES.sequenceNo].join(",");
837
- add("scaffolding-system-row", systemOk, systemOk ? "the draft layout decodes the system row [creater(101), createdTime(104), sequenceNo(100)] exactly as contracted." : "the authoritative draft readback did not decode the designer-standard system row (creater 101 / createdTime 104 / sequenceNo 100) in order.");
838
- const groupOk = scaffold.decodable && scaffold.group?.type === 200;
839
- add("scaffolding-group-control", groupOk, groupOk ? "the draft layout decodes the group control (type 200) after the system row." : "the authoritative draft readback did not decode the designer-standard group control (type 200).");
840
- let viewRows;
841
- if (afterOk && typeof after.form?.draftViewJson === "string") {
842
- try {
843
- const parsed = JSON.parse(after.form.draftViewJson);
844
- if (Array.isArray(parsed))
845
- viewRows = parsed;
846
- }
847
- catch {
848
- viewRows = undefined;
849
- }
850
- }
851
- const rowOrderOk = scaffold.rowOrderMatchesContract && viewRows !== undefined && viewRows.length === 3 + evidence.targetFieldCodes.length && evidence.targetFieldCodes.every((fieldCode, i) => viewRows[3 + i].length === 1 && viewRows[3 + i][0] === fieldCode);
852
- add("scaffolded-layout-row-order", rowOrderOk, rowOrderOk ? `the layout row order decodes as exactly Title, system row, group, then the ${evidence.targetFieldCodes.length} bound business field rows in family order — matching the contract sample.` : `the authoritative draft readback did not decode the contracted layout shape (Title, system row, group, then exactly the ${evidence.targetFieldCodes.length} bound fields' business rows in family order).`);
853
- }
854
- const publishedUnchanged = Boolean(before.form && after.form) && before.form.published === evidence.formPreState.form.published && after.form.published === before.form.published && after.form.publishedViewJson === before.form.publishedViewJson && after.form.publishedAttributesJson === before.form.publishedAttributesJson && after.form.publishedActionsJson === before.form.publishedActionsJson && after.form.publishedHtmlJson === before.form.publishedHtmlJson;
855
- add("published-state-unchanged", publishedUnchanged, `the published flag decodes as ${String(after.form?.published)} (pre-save ${String(before.form?.published)}) with the published JSON unchanged; a draft save never publishes.`);
856
- add("exist-draft", after.form?.existDraft === true, `the existDraft flag decodes as ${String(after.form?.existDraft)} after the draft save.`);
857
- add("designer-direct-url", evidence.designerDirectUrl === ticket11DesignerDirectUrl(evidence.targetSchemaCode, evidence.targetSheetCode), `designer direct URL ${evidence.designerDirectUrl} stays inspectable.`);
858
- const blocked = checks.find((check) => check.status === "blocked");
859
- return blocked ? { status: "blocked", reason: `Ticket 11 behavior validation failed: ${blocked.detail}`, checks } : { status: "validated", checks };
860
- }
861
- /** Execution boundary: named test environment, route-bound package steps, active approvals, no replay of terminal or uncertain packages. */
862
- export function assertTicket11ExecutionBoundary(input) {
863
- if (input.environmentName !== "test")
864
- throw new Error("Ticket 11 execution is restricted to the named test environment; production and staging are rejected.");
865
- const { projection } = input;
866
- if (input.steps.length !== 1 || !input.steps[0].id.startsWith("form-draft:"))
867
- throw new Error(`Ticket 11 execution boundary rejected package ${input.packageId}: its steps are not the single form-draft: operation bound to the ${input.route} route.`);
868
- if (projection.status === "approved" && projection.approvals.plan === "approved" && projection.approvals.environment === "approved")
869
- return;
870
- if (projection.status === "completed")
871
- throw new Error(`Ticket 11 package ${input.packageId} is already terminal completed; replay is rejected.`);
872
- if (projection.status === "uncertain")
873
- throw new Error(`Ticket 11 package ${input.packageId} is uncertain; reconcile by authoritative readback before any replay.`);
874
- if (projection.status === "in_progress")
875
- throw new Error(`Ticket 11 package ${input.packageId} is already running; concurrent execution is rejected.`);
876
- const pending = [...projection.blockers];
877
- if (projection.approvals.plan !== "approved")
878
- pending.push("Delivery Plan Approval is required before package execution.");
879
- if (projection.approvals.environment !== "approved")
880
- pending.push("Environment Change Approval is required before mutation.");
881
- throw new Error(`Ticket 11 execution boundary rejected package ${input.packageId}: status is ${projection.status} (blocked); ${[...new Set(pending)].join("; ")}.`);
882
- }
883
- /** Executes the approved one-step default-form draft-save package through the single OMC Host write transport, then validates the control binding and the published-state invariance. */
884
- export async function executeTicket11FormDraftPackage(input) {
885
- assertTicket11TestFormDraftEnvironment(input.settings);
886
- if (typeof input.transport.serializedServerFormDraftSave !== "function")
887
- throw new Error("Ticket 11 execution requires the OMC Host serialized form-draft transport; bypass transports are rejected.");
888
- const projection = input.ledger.project(input.prepared.package.packageId);
889
- assertTicket11ExecutionBoundary({ projection, environmentName: input.settings.environmentName, packageId: input.prepared.package.packageId, route: TICKET11_FORM_DRAFT_ROUTE, steps: input.prepared.package.steps });
890
- if (input.environmentApproval.packageId !== input.prepared.package.packageId || input.environmentApproval.packageHash !== input.prepared.package.packageHash)
891
- throw new Error("Ticket 11 Environment Change Approval does not match the prepared package.");
892
- const evidence = input.prepared.evidence;
893
- const preflight = await input.readFormState();
894
- if (preflight.classification !== "present" || !preflight.form || preflight.form.published !== evidence.formPreState.form.published || preflight.form.publishedViewJson !== evidence.formPreState.form.publishedViewJson || preflight.form.publishedAttributesJson !== evidence.formPreState.form.publishedAttributesJson || preflight.form.publishedActionsJson !== evidence.formPreState.form.publishedActionsJson || preflight.form.publishedHtmlJson !== evidence.formPreState.form.publishedHtmlJson)
895
- throw new Error(`Ticket 11 execution requires the form pre-state to match the approved evidence; ${input.prepared.package.steps[0].target} diverges from the approved handoff.`);
896
- for (const fieldCode of evidence.targetFieldCodes) {
897
- const preflightPresence = decodeTicket11DraftControlPresence(preflight.form, fieldCode);
898
- if (!preflightPresence.decodable || preflightPresence.layoutOccurrences !== 0 || preflightPresence.control)
899
- throw new Error(`Ticket 11 execution requires exact control absence before the draft save; ${fieldCode} is already bound or undecodable.`);
900
- }
901
- const result = await runNativeConfigurationPackage({
902
- package: input.prepared.package,
903
- gate: {
904
- mode: "approved",
905
- planApproval: { status: "approved", approvalHash: input.prepared.planApproval.approvalHash },
906
- environmentApproval: { status: "approved", packageHash: input.environmentApproval.packageHash },
907
- lease: { owner: "configuration-agent", environment: TICKET11_TEST_ENVIRONMENT_NAME },
908
- baselineFingerprint: input.prepared.package.binding.baselineFingerprint,
909
- snapshots: input.prepared.package.snapshotManifest,
910
- package: input.prepared.package,
911
- },
912
- operations: input.prepared.operations,
913
- ledger: input.ledger,
914
- planApproval: input.prepared.planApproval,
915
- transport: input.transport,
916
- credentials: input.credentials,
917
- authoritativeOrigin: input.authoritativeOrigin,
918
- testApplicationCode: TICKET11_TEST_APPLICATION_CODE,
919
- }, input.signal);
920
- const mutationExecuted = result.steps.some((step) => step.status === "committed" || step.status === "uncertain");
921
- const blockers = [...result.blockers];
922
- let status = ticket11ExecutionStatus(result.status);
923
- let nextAction = result.nextAction;
924
- let behavior;
925
- if (result.status === "completed") {
926
- behavior = validateTicket11FormDraftBehavior({ evidence, before: preflight, after: await input.readFormState() });
927
- if (behavior.status !== "validated") {
928
- status = "blocked";
929
- blockers.push(behavior.reason ?? "Ticket 11 behavior validation failed.");
930
- nextAction = "reconcile-behavior-retain-and-report-resource";
931
- }
932
- }
933
- return { reportType: "omc.ticket11.test-form-draft-execution", schemaVersion: 1, packageId: result.packageId, packageHash: result.packageHash, route: TICKET11_FORM_DRAFT_ROUTE, testRunId: evidence.testRunId, targetSchemaCode: evidence.targetSchemaCode, targetSheetCode: evidence.targetSheetCode, targetFieldCodes: evidence.targetFieldCodes, status, mutationExecuted, steps: result.steps, blockers, nextAction, ...(behavior ? { behavior } : {}) };
934
- }
935
- /**
936
- * Closes the stated form cascade cleanup contract: after the separately
937
- * approved staged cleanup (published-field cleanup, then the model physical
938
- * delete), the authoritative bizsheet/get readback must be a non-success
939
- * envelope for the fixture (exact errcode recorded). Anything else stops,
940
- * retains, and reports.
941
- */
942
- export function confirmTicket11FormCascadeAbsence(input) {
943
- const evidence = validateTicket11Evidence(input.evidence);
944
- const readback = input.formReadback;
945
- if (readback.classification !== "absent" || readback.reason !== "form-absence" || readback.query.schemaCode !== evidence.targetSchemaCode || readback.query.sheetCode !== evidence.targetSheetCode)
946
- throw new Error(`Ticket 11 form cascade cleanup contract is not closed: the bizsheet/get readback does not confirm form absence after the model physical delete (classification=${readback.classification}, errcode=${String(readback.errcode ?? "n/a")}); stop, retain, and report.`);
947
- }
948
- function ticket11ExecutionStatus(status) {
949
- if (status === "completed" || status === "uncertain" || status === "partial")
950
- return status;
951
- return "blocked";
952
- }
953
- function requireTicket11Approval(approval) {
954
- const value = approval;
955
- if (value.route !== TICKET11_FORM_DRAFT_ROUTE || value.capabilityId !== TICKET11_FORM_DRAFT_CAPABILITY_ID || value.environmentName !== "test" || value.applicationCode !== "test" || value.targets?.length !== 1 || !value.folderId || !value.folderName || !value.testRunId || !value.targetSchemaCode || !value.targetSheetCode || !Array.isArray(value.targetFieldCodes) || value.targetFieldCodes.length !== TICKET09_FIELD_FAMILY.length || value.targetFieldCodes.some((code) => !isTicket09TargetField(code)) || new Set(value.targetFieldCodes).size !== TICKET09_FIELD_FAMILY.length)
956
- throw new Error(`Ticket 11 approval frame is not bound to the exact ${TICKET11_FORM_DRAFT_ROUTE} route, family targets, folder, and testRunId.`);
957
- return value;
958
- }
959
- function requirePersistedApprovedPlan(ledger, approval) {
960
- if (approval.status !== "approved")
961
- throw new Error("Ticket 11 package preparation requires the approved exact Ticket 11 Delivery Plan Approval.");
962
- const persisted = ledger.findPlanApproval(approval.approvalId);
963
- if (!persisted || persisted.status !== "approved" || persisted.approvalHash !== approval.approvalHash)
964
- throw new Error("Ticket 11 package preparation requires the exact authoritative approved Delivery Plan Approval.");
965
- return persisted;
966
- }
967
- function assertTicket11BindingMatchesEvidence(binding, evidence) {
968
- const expected = {
969
- deliveryId: ticket11DeliveryId(evidence.testRunId),
970
- environment: TICKET11_TEST_ENVIRONMENT_NAME,
971
- baselineFingerprint: evidence.baselineFingerprint,
972
- environmentFingerprint: evidence.environmentFingerprint,
973
- discoveryFingerprint: evidence.discoveryFingerprint,
974
- ...(evidence.evidenceArtifactId ? { evidenceArtifactId: evidence.evidenceArtifactId } : {}),
975
- };
976
- if (stable(binding) !== stable(expected))
977
- throw new Error("Ticket 11 plan approval binding is outside the evidence baseline, environment, discovery, or delivery binding.");
978
- }
979
- function validateTicket11Evidence(evidence) {
980
- if (!evidence || evidence.route !== TICKET11_FORM_DRAFT_ROUTE || evidence.capabilityId !== TICKET11_FORM_DRAFT_CAPABILITY_ID)
981
- throw new Error("Ticket 11 evidence is not the host-owned form draft route.");
982
- if (evidence.environmentName !== "test" || evidence.applicationCode !== "test")
983
- throw new Error("Ticket 11 evidence is outside the named test environment and dedicated test application.");
984
- if (!isTicket08TargetSchema(evidence.targetSchemaCode))
985
- throw new Error("Ticket 11 evidence fixture model is not the host-owned Ticket 08 schema.");
986
- if (evidence.targetSheetCode !== evidence.targetSchemaCode)
987
- throw new Error("Ticket 11 evidence default sheet is not bound to the fixture schema.");
988
- if (!Array.isArray(evidence.targetFieldCodes) || evidence.targetFieldCodes.length !== TICKET09_FIELD_FAMILY.length || evidence.targetFieldCodes.some((code) => !isTicket09TargetField(code)) || new Set(evidence.targetFieldCodes).size !== TICKET09_FIELD_FAMILY.length)
989
- throw new Error("Ticket 11 evidence family field codes are not the distinct host-owned Ticket 09 field codes (one per active family member).");
990
- if ([evidence.environmentFingerprint, evidence.baselineFingerprint, evidence.discoveryFingerprint].some((value) => !SHA256.test(value)))
991
- throw new Error("Ticket 11 evidence fingerprints are not canonical.");
992
- if (evidence.modelPresence.classification !== "present" || evidence.modelPresence.query.schemaCode !== evidence.targetSchemaCode)
993
- throw new Error("Ticket 11 evidence does not carry the authoritative fixture model presence handoff.");
994
- evidence.targetFieldCodes.forEach((fieldCode, index) => {
995
- const presence = evidence.publishedFieldPresence[index];
996
- if (presence?.classification !== "present" || presence.query.schemaCode !== evidence.targetSchemaCode || presence.query.propertyCode !== fieldCode || presence.query.isPublish !== "true" || presence.field?.published !== true)
997
- throw new Error("Ticket 11 evidence does not carry exact authenticated published family field presence.");
998
- });
999
- if (evidence.formPreState.classification !== "present" || evidence.formPreState.query.schemaCode !== evidence.targetSchemaCode || evidence.formPreState.query.sheetCode !== evidence.targetSheetCode || !evidence.formPreState.form || evidence.formPreState.form.published !== false)
1000
- throw new Error("Ticket 11 evidence does not carry the exact authenticated unpublished form pre-state.");
1001
- if (!evidence.folder.id || !evidence.folder.name)
1002
- throw new Error("Ticket 11 evidence folder binding is missing.");
1003
- return evidence;
1004
- }
1005
- function assertDigest(value, label) {
1006
- if (typeof value !== "string" || !SHA256.test(value) || !/[1-9a-f]/i.test(value))
1007
- throw new Error(`Ticket 11 ${label} is invalid.`);
1008
- }
1009
- function digest(value) { return createHash("sha256").update(stable(value)).digest("hex"); }
1010
- function stable(value) { return JSON.stringify(sort(value)); }
1011
- function sort(value) { if (Array.isArray(value))
1012
- return value.map(sort); if (value && typeof value === "object")
1013
- return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, sort(item)])); return value; }
1014
- //# sourceMappingURL=ticket11-test-form-draft.js.map