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.
- package/dist/childtable-sheet201-contract.js +1 -1
- package/dist/field-probe-runner-contract.js +1 -1
- package/dist/field-probe-schema.js +19 -0
- package/dist/form-scaffold-contract.js +126 -0
- package/dist/formula-controls-contract.js +1 -1
- package/package.json +1 -1
- package/dist/master-plan-approval.js +0 -443
- package/dist/ticket08-test-model-mutation.js +0 -901
- package/dist/ticket09-test-field-mutation.js +0 -1026
- package/dist/ticket10-test-field-publish.js +0 -886
- package/dist/ticket11-test-form-draft.js +0 -1014
- package/dist/ticket12-test-form-publish.js +0 -917
- package/dist/ticket20-test-data-rule.js +0 -780
|
@@ -1,780 +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 { assertFamilyControlOptions, CLOUDPIVOT_8_6_26_SERIALIZED_DATA_RULE_CREATE, decodeRequiredDataRuleRow, decodeDraftControl, CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT } 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, fieldFamilyMemberNamesProse, fieldFamilyRuleDemoMember } from "./families/registry.js";
|
|
14
|
-
import { isTicket09TargetField, TICKET09_FIELD_FAMILY } from "./ticket09-test-field-mutation.js";
|
|
15
|
-
import { TICKET11_FORM_DRAFT_ROUTE, decodeTicket11DraftControlPresence } from "./ticket11-test-form-draft.js";
|
|
16
|
-
/**
|
|
17
|
-
* Ticket 20 (field-type ladder issue 02a — the designer-native rule surface)
|
|
18
|
-
* promotes exactly one independently bounded CloudPivot `test` data-rule
|
|
19
|
-
* mutation: the bizdatarule row that makes the run's own published rule-demo
|
|
20
|
-
* fixture field REQUIRED through the surface the designer itself authors —
|
|
21
|
-
* dataRuleType 4 (DataRuleType.OTHER_RULE 必填规则) with the unconditional
|
|
22
|
-
* options {"requiredFormula":"true","functionType:"true"} bound through
|
|
23
|
-
* propertyCodes. The composite chain runs the executable Ticket 08/09/10/11
|
|
24
|
-
* capabilities first; the data-rule package consumes the completed,
|
|
25
|
-
* behavior-validated form-draft execution report plus fresh authoritative
|
|
26
|
-
* readbacks (published fields present; the draft-configured form carrying
|
|
27
|
-
* every registered selector control with the amended options — visible plus
|
|
28
|
-
* the always-readonly boolean on STAFF_SELECTOR, NO condition strings
|
|
29
|
-
* anywhere; no dataRuleType 4 row bound to the field and no dataRuleType 5
|
|
30
|
-
* rows at all). The package is independently immutable and independently
|
|
31
|
-
* dual-approved. No evidence is inferred from the prior rungs; form publish,
|
|
32
|
-
* app publish, Environment release, production, staging, tenant
|
|
33
|
-
* administration, Redis, restart, deployment, and runtime CRUD stay
|
|
34
|
-
* rejected, and conditional rules (dataRuleType 5, display 3) are never
|
|
35
|
-
* authored by this capability.
|
|
36
|
-
*
|
|
37
|
-
* Authoritative 8.6.26 contract (type-matrix §9 + the decompiled engine +
|
|
38
|
-
* the platform's own writer):
|
|
39
|
-
* - the designer's AUTHORING surface for required/readonly/display
|
|
40
|
-
* conditions is the bizdatarule row set on the schema, NOT the control
|
|
41
|
-
* options keys: on panel load resetConditionRules clears
|
|
42
|
-
* options.displayFormula/requiredFormula/readonlyCondition strings, then
|
|
43
|
-
* writeRuleToControls hydrates them from the rule lists. A hand-written
|
|
44
|
-
* options.requiredFormula is therefore wiped on load — the grey/
|
|
45
|
-
* uneditable required and the conditional-readonly-without-condition
|
|
46
|
-
* seen in the numeric-logical walkthrough (and the earlier "re-set
|
|
47
|
-
* required does not persist" mystery on TEXT fixtures);
|
|
48
|
-
* - CRUD (BizDataRuleController.java:86-427, /api/app/bizdatarule → wire
|
|
49
|
-
* /api/api/app/bizdatarule): GET /list?schemaCode&dataRuleType returns
|
|
50
|
-
* the rows with `options` as a serialized JSON string (parseFormula
|
|
51
|
-
* rewrites the expression member for the row's rule family); POST
|
|
52
|
-
* /create @RequireAdmin validates schemaCode + dataRuleType non-null,
|
|
53
|
-
* runs checkDataRuleFormat, then addBizDataRule; POST|PUT /update
|
|
54
|
-
* @RequireAdmin requires id; POST|DELETE /delete?id @RequireAdmin;
|
|
55
|
-
* - the platform's own writer (admin dist chunk 19407, rule modal) sends
|
|
56
|
-
* exactly the four-member create shape {schemaCode, propertyCodes,
|
|
57
|
-
* dataRuleType, options} with options built as
|
|
58
|
-
* JSON.stringify({requiredFormula:<expr>, functionType:"true"}) for the
|
|
59
|
-
* required family, and blocks empty conditions client-side ("this
|
|
60
|
-
* condition is empty!") — this capability's gates reject them with the
|
|
61
|
-
* same parity;
|
|
62
|
-
* - cleanup: rule rows are schema-scoped design-time state — they die with
|
|
63
|
-
* the separately approved staged cleanup (Ticket 10 published-field
|
|
64
|
-
* cleanup, then the Ticket 08 model physical delete cascade). Absence is
|
|
65
|
-
* confirmed by the authoritative bizdatarule/list readback decoding zero
|
|
66
|
-
* rows for the fixture schema. If a live run shows surviving rows, the
|
|
67
|
-
* pinned /delete?id contract is the explicit pre-cascade fallback and
|
|
68
|
-
* the outcome stops, retains, and reports — never a silent replay.
|
|
69
|
-
*/
|
|
70
|
-
export const TICKET20_DATA_RULE_ROUTE = "ticket20-test-data-rule";
|
|
71
|
-
export const TICKET20_DATA_RULE_CAPABILITY_ID = "cloudpivot.test.data-rule-8.6.26";
|
|
72
|
-
export const TICKET20_RULE_CLEANUP_CAPABILITY_ID = "cloudpivot.test.data-rule-cascade-absence-8.6.26";
|
|
73
|
-
export const TICKET20_DATA_RULE_KNOWLEDGE_PACK_ID = "cloudpivot-8.6-ticket20-test-data-rule";
|
|
74
|
-
export const TICKET20_TEST_ENVIRONMENT_NAME = "test";
|
|
75
|
-
export const TICKET20_TEST_APPLICATION_CODE = "test";
|
|
76
|
-
export const TICKET20_REFERENCE_EVIDENCE_VERSION = "8.6.26";
|
|
77
|
-
export const TICKET20_DATA_RULE_ENDPOINT = "/api/api/app/bizdatarule/create";
|
|
78
|
-
export const TICKET20_RULE_READBACK_ENDPOINT = "/api/api/app/bizdatarule/list";
|
|
79
|
-
export const TICKET20_RULE_DELETE_ENDPOINT = "/api/api/app/bizdatarule/delete";
|
|
80
|
-
export const TICKET20_REQUIRED_DATA_RULE_TYPE = 4;
|
|
81
|
-
export const TICKET20_READONLY_CONDITION_DATA_RULE_TYPE = 5;
|
|
82
|
-
export const TICKET20_PROHIBITED_MUTATION_TERMS = Object.freeze([
|
|
83
|
-
"app-publish", "form-publish", "bizsheet/publish", "bizsheet/update", "deploy", "restart", "release", "redis", "tenant-admin", "runtime-crud",
|
|
84
|
-
"model:", "view", "folder-create", "folder-delete", "bizproperty/create", "bizproperty/publish", "bizdatarule/update", "bizdatarule/delete",
|
|
85
|
-
]);
|
|
86
|
-
const TICKET20_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;
|
|
87
|
-
const SHA256 = /^[a-f0-9]{64}$/i;
|
|
88
|
-
/** The named `test` environment and its dedicated test application are hard preconditions. */
|
|
89
|
-
export function assertTicket20TestDataRuleEnvironment(settings) {
|
|
90
|
-
if (settings.environmentName !== "test")
|
|
91
|
-
throw new Error("Ticket 20 data rule is restricted to the named test environment; production and staging are rejected.");
|
|
92
|
-
if (!settings.baseUrl.trim() || !/^https?:\/\/\S+$/i.test(settings.baseUrl.trim()))
|
|
93
|
-
throw new Error("Ticket 20 data rule requires an absolute configured test environment endpoint.");
|
|
94
|
-
if (settings.testApplicationCode !== "test")
|
|
95
|
-
throw new Error("Ticket 20 data rule requires the dedicated test application to be test.");
|
|
96
|
-
}
|
|
97
|
-
export function ticket20DeliveryId(testRunId) { return `ticket20-data-rule-${testRunId}`; }
|
|
98
|
-
/** The data-rule stage's exact operation scope: the schema's TIME field's required-rule row. */
|
|
99
|
-
export function ticket20DataRuleScope(targetSchemaCode, requiredFieldCode) { return `cloudpivot:rule:${targetSchemaCode}/${requiredFieldCode}`; }
|
|
100
|
-
/** Design-time designer direct URL; the fixture rule state stays inspectable here without any form publish. */
|
|
101
|
-
export function ticket20DesignerDirectUrl(targetSchemaCode) { return `/admin/#/apps/model/${TICKET20_TEST_APPLICATION_CODE}/${targetSchemaCode}/field-define`; }
|
|
102
|
-
export function buildTicket20RuleCleanupContract(testRunId, targetSchemaCode, targetFieldCode) {
|
|
103
|
-
if (!TICKET20_UUID_V4.test(testRunId) || !isTicket08TargetSchema(targetSchemaCode) || !isTicket09TargetField(targetFieldCode))
|
|
104
|
-
throw new Error("Ticket 20 rule cleanup contract requires the exact host-owned testRunId, fixture schema, and required field code.");
|
|
105
|
-
return Object.freeze({
|
|
106
|
-
capabilityId: TICKET20_RULE_CLEANUP_CAPABILITY_ID,
|
|
107
|
-
testRunId,
|
|
108
|
-
targetSchemaCode,
|
|
109
|
-
targetFieldCode,
|
|
110
|
-
semantics: "rule-rows-are-schema-scoped-design-time-state;they-die-with-the-staged-cleanup(ticket10-published-field-cleanup-then-ticket08-model-physical-delete-cascade);if-the-live-cascade-leaves-survivors-the-pinned-delete?id-contract-is-the-explicit-pre-cascade-fallback-and-the-outcome-stops-retains-and-reports",
|
|
111
|
-
absenceReadback: { method: "GET", endpoint: TICKET20_RULE_READBACK_ENDPOINT, expected: "zero-rows-decoded-for-the-fixture-schema" },
|
|
112
|
-
ownership: "schema-and-required-field-bound-to-testRunId",
|
|
113
|
-
replayPolicy: "absent-is-no-op",
|
|
114
|
-
uncertainPolicy: "stop-retain-report-no-retry",
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
/** Deterministic, run-independent contract identities pinned by the Knowledge Pack promotion. */
|
|
118
|
-
export function ticket20DataRuleContractHash() { return digest(ticket20DataRuleContract()); }
|
|
119
|
-
export function ticket20RuleCleanupContractHash() { return digest(ticket20RuleCleanupContractTemplate()); }
|
|
120
|
-
function ticket20DataRuleContract() {
|
|
121
|
-
return {
|
|
122
|
-
capabilityId: TICKET20_DATA_RULE_CAPABILITY_ID,
|
|
123
|
-
operationKind: "data-rule-create",
|
|
124
|
-
method: "POST",
|
|
125
|
-
endpoint: CLOUDPIVOT_8_6_26_SERIALIZED_DATA_RULE_CREATE.endpoint,
|
|
126
|
-
readbackEndpoint: CLOUDPIVOT_8_6_26_SERIALIZED_DATA_RULE_CREATE.readbackEndpoint,
|
|
127
|
-
preflight: CLOUDPIVOT_8_6_26_SERIALIZED_DATA_RULE_CREATE.preflight,
|
|
128
|
-
dto: "BizDataRuleModel:the-platform-writer's-exact-four-member-create-shape(schemaCode,propertyCodes,dataRuleType,options)",
|
|
129
|
-
ruleContract: { ...CLOUDPIVOT_8_6_26_SERIALIZED_DATA_RULE_CREATE.ruleContract },
|
|
130
|
-
ruleSurface: "bizdatarule-rows-are-the-designer's-authoring-surface-for-required/readonly/display;control-options-condition-strings-are-runtime-hydration-targets-the-panel-wipes-on-load",
|
|
131
|
-
emptyConditionParity: "empty-or-missing-condition-expressions-are-rejected-by-our-gates-with-designer-client-side-parity",
|
|
132
|
-
conditionalRules: "dataRuleType-5-readonly-and-3-display-rows-are-never-authored-by-this-capability",
|
|
133
|
-
cleanup: "rule-rows-die-with-the-staged-cleanup-cascade;bizdatarule/list-zero-rows-absence-confirmation;delete?id-is-the-explicit-pre-cascade-fallback",
|
|
134
|
-
scope: "schema-scoped-required-rule-row",
|
|
135
|
-
permission: "@RequireAdmin",
|
|
136
|
-
transportMode: "serialized-server-data-rule-create-8.6.26",
|
|
137
|
-
capability: CLOUDPIVOT_8_6_26_SERIALIZED_DATA_RULE_CREATE,
|
|
138
|
-
evidence: [...CLOUDPIVOT_8_6_26_SERIALIZED_DATA_RULE_CREATE.evidence],
|
|
139
|
-
requiredPermissions: ["configuration-write"],
|
|
140
|
-
prohibited: [...TICKET20_PROHIBITED_MUTATION_TERMS],
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
function ticket20RuleCleanupContractTemplate() {
|
|
144
|
-
return {
|
|
145
|
-
capabilityId: TICKET20_RULE_CLEANUP_CAPABILITY_ID,
|
|
146
|
-
operationKind: "data-rule-cascade-absence-confirmation",
|
|
147
|
-
semantics: "rule-rows-die-with-the-model-physical-delete-cascade;no-separate-rule-delete-in-the-standard-chain",
|
|
148
|
-
absenceReadback: `${TICKET20_RULE_READBACK_ENDPOINT}:zero-rows-decoded-for-the-fixture-schema`,
|
|
149
|
-
ownership: "schema-and-required-field-bound-to-testRunId",
|
|
150
|
-
replayPolicy: "absent-is-no-op",
|
|
151
|
-
uncertainPolicy: "stop-retain-report-no-retry",
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
/** Package operations must be exactly one bounded required-rule create step bound to the run's TIME field. */
|
|
155
|
-
export function assertTicket20OperationsBounded(input) {
|
|
156
|
-
if (input.operations.length !== 1)
|
|
157
|
-
throw new Error("Ticket 20 packages must contain exactly one bounded operation.");
|
|
158
|
-
const operation = input.operations[0];
|
|
159
|
-
const identity = `${operation.id} ${operation.target} ${operation.path}`.toLowerCase();
|
|
160
|
-
if (TICKET20_PROHIBITED_MUTATION_TERMS.some((term) => identity.includes(term)) || !operation.id.startsWith("rule-create:"))
|
|
161
|
-
throw new Error(`Ticket 20 operation ${operation.id} is outside the bounded operation set; prohibited families (conditional rules, rule update/delete, form writes, app publish, deploy, restart, release, redis, tenant administration, runtime CRUD) remain rejected.`);
|
|
162
|
-
const expected = { id: `rule-create:${input.targetSchemaCode}/${input.targetFieldCode}`, target: `rule:${input.targetSchemaCode}/${input.targetFieldCode}`, path: TICKET20_DATA_RULE_ENDPOINT, transportMode: "serialized-server-data-rule-create-8.6.26" };
|
|
163
|
-
if (operation.id !== expected.id || operation.path !== expected.path || operation.transportMode !== expected.transportMode || operation.target !== expected.target)
|
|
164
|
-
throw new Error(`Ticket 20 operation ${operation.id} does not match the exact target binding for ${expected.target}.`);
|
|
165
|
-
}
|
|
166
|
-
/** Builds the signed, version-bound pack promoting exactly the single test data-rule capability. */
|
|
167
|
-
export function buildTicket20DataRuleKnowledgePack(input) {
|
|
168
|
-
const compiler = { id: "src/ticket20-test-data-rule.ts:createTicket20DataRulePlanApproval", sha256: digest("omc-ticket20-compiler-v1") };
|
|
169
|
-
const decoder = { id: "src/dsh-host-plugin.ts:createCloudPivotWriteTransport", sha256: digest("omc-ticket20-decoder-v1") };
|
|
170
|
-
return buildKnowledgePack({
|
|
171
|
-
packId: TICKET20_DATA_RULE_KNOWLEDGE_PACK_ID,
|
|
172
|
-
packVersion: "1.0.0",
|
|
173
|
-
sourceVersion: TICKET20_REFERENCE_EVIDENCE_VERSION,
|
|
174
|
-
sourceArtifacts: [{ id: "cloudpivot-8.6.26-serialized-data-rule-create-capability", sha256: digest(CLOUDPIVOT_8_6_26_SERIALIZED_DATA_RULE_CREATE) }],
|
|
175
|
-
references: [
|
|
176
|
-
{ id: "omc-type-matrix-section-9-designer-rule-model", sha256: digest("omc-type-matrix-section-9-designer-rule-model") },
|
|
177
|
-
{ id: TICKET20_DATA_RULE_ENDPOINT, sha256: digest(TICKET20_DATA_RULE_ENDPOINT) },
|
|
178
|
-
{ id: TICKET20_RULE_READBACK_ENDPOINT, sha256: digest(TICKET20_RULE_READBACK_ENDPOINT) },
|
|
179
|
-
],
|
|
180
|
-
compilers: [compiler],
|
|
181
|
-
decoders: [decoder],
|
|
182
|
-
capabilities: [{
|
|
183
|
-
id: TICKET20_DATA_RULE_CAPABILITY_ID,
|
|
184
|
-
status: "runtime-verified",
|
|
185
|
-
promote: true,
|
|
186
|
-
compilerRef: compiler.id,
|
|
187
|
-
decoderRef: decoder.id,
|
|
188
|
-
sourceEvidence: [...CLOUDPIVOT_8_6_26_SERIALIZED_DATA_RULE_CREATE.evidence, TICKET20_DATA_RULE_ENDPOINT, TICKET20_RULE_READBACK_ENDPOINT],
|
|
189
|
-
runtimeEvidence: ["omc-type-matrix-section-9-designer-rule-model", "cloudpivot.data-rule-create.serialized-server-8.6.26"],
|
|
190
|
-
operationPromotions: [{
|
|
191
|
-
id: "data-rule-create",
|
|
192
|
-
status: "promoted",
|
|
193
|
-
operationContractHash: ticket20DataRuleContractHash(),
|
|
194
|
-
sourceRefs: [...CLOUDPIVOT_8_6_26_SERIALIZED_DATA_RULE_CREATE.evidence, TICKET20_DATA_RULE_ENDPOINT, TICKET20_RULE_READBACK_ENDPOINT],
|
|
195
|
-
versionFamily: "8.6",
|
|
196
|
-
referenceVersion: TICKET20_REFERENCE_EVIDENCE_VERSION,
|
|
197
|
-
requiredEvidence: ["cleanup", "discovery", "permission", "readback", "test", "write"],
|
|
198
|
-
evidence: ["cleanup", "discovery", "permission", "readback", "test", "write"],
|
|
199
|
-
blockedReasons: [],
|
|
200
|
-
}],
|
|
201
|
-
}],
|
|
202
|
-
signerKeyId: input.signerKeyId,
|
|
203
|
-
signingKey: input.signingKey,
|
|
204
|
-
createdAt: input.createdAt,
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
/** Loads the pack against CloudPivot 8.6.26 and pins the single executable capability and contract hash. */
|
|
208
|
-
export function loadTicket20KnowledgePack(artifact, trustedKeys) {
|
|
209
|
-
const runtime = new KnowledgePackRuntime(trustedKeys);
|
|
210
|
-
const load = runtime.load(artifact, TICKET20_REFERENCE_EVIDENCE_VERSION);
|
|
211
|
-
if (!load.accepted)
|
|
212
|
-
throw new Error(`Ticket 20 Knowledge Pack is not authoritative: ${load.blockingReasons.join(", ")}.`);
|
|
213
|
-
const active = runtime.active();
|
|
214
|
-
const capabilities = active.capabilities;
|
|
215
|
-
if (capabilities.length !== 1 || capabilities[0].id !== TICKET20_DATA_RULE_CAPABILITY_ID)
|
|
216
|
-
throw new Error("Ticket 20 Knowledge Pack must promote exactly the single test data-rule capability; evidence inference from the form rungs or to any other capability (conditional rules included) is rejected.");
|
|
217
|
-
const hashes = capabilities[0].operationContractHashes;
|
|
218
|
-
if (hashes.length !== 1 || hashes[0] !== ticket20DataRuleContractHash())
|
|
219
|
-
throw new Error("Ticket 20 Knowledge Pack operation contract hash does not match the canonical data-rule contract.");
|
|
220
|
-
return { runtime, packId: active.manifest.packId, contentDigest: active.manifest.contentSha256, executableCapabilities: capabilities.map(({ id }) => id) };
|
|
221
|
-
}
|
|
222
|
-
export function decodeTicket20RuleListEvidence(input) {
|
|
223
|
-
const url = `${TICKET20_RULE_READBACK_ENDPOINT}?schemaCode=${encodeURIComponent(input.schemaCode)}&dataRuleType=${String(input.dataRuleType)}`;
|
|
224
|
-
const query = { schemaCode: input.schemaCode, dataRuleType: input.dataRuleType };
|
|
225
|
-
const httpStatus = input.httpStatus ?? 200;
|
|
226
|
-
const envelope = input.body;
|
|
227
|
-
if (!envelope || typeof envelope !== "object" || Array.isArray(envelope))
|
|
228
|
-
return Object.freeze({ classification: "blocked", reason: "malformed-response", query, url, httpStatus, rows: [] });
|
|
229
|
-
if (httpStatus < 200 || httpStatus >= 300)
|
|
230
|
-
return Object.freeze({ classification: "blocked", reason: "http-error", query, url, httpStatus, rows: [] });
|
|
231
|
-
if (envelope.errcode !== 0) {
|
|
232
|
-
const errcode = typeof envelope.errcode === "number" ? envelope.errcode : undefined;
|
|
233
|
-
// ONLY the schema-absence envelope (the engine's business-model-missing
|
|
234
|
-
// family, errcode 10011 — the rule rows are schema-scoped and the
|
|
235
|
-
// facade resolves the schema first) decodes as rule absence; every
|
|
236
|
-
// other non-success envelope (auth, malformed query, server fault) is
|
|
237
|
-
// BLOCKED, never a falsely-confirmed zero-rows cleanup gate.
|
|
238
|
-
if (errcode === 10011)
|
|
239
|
-
return Object.freeze({ classification: "absent", reason: "schema-rule-absence", query, url, httpStatus, ...(errcode !== undefined ? { errcode } : {}), rows: [] });
|
|
240
|
-
return Object.freeze({ classification: "blocked", reason: "malformed-response", query, url, httpStatus, ...(errcode !== undefined ? { errcode } : {}), rows: [] });
|
|
241
|
-
}
|
|
242
|
-
const data = envelope.data;
|
|
243
|
-
if (!Array.isArray(data) || !data.every((row) => row === null || typeof row === "object"))
|
|
244
|
-
return Object.freeze({ classification: "blocked", reason: "malformed-response", query, url, httpStatus, rows: [] });
|
|
245
|
-
const rows = data.filter((row) => row !== null && typeof row === "object");
|
|
246
|
-
return Object.freeze({ classification: "present", reason: "rows-decoded", query, url, httpStatus, rows });
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Host-owned composite-chain evidence capture: consumes the completed
|
|
250
|
-
* form-draft handoff and proves BOTH published family fields present, BOTH
|
|
251
|
-
* typed controls bound with the amended options (visible + the
|
|
252
|
-
* always-readonly boolean only — NO condition strings), no required-rule row
|
|
253
|
-
* bound to the field, and no conditional rules at all.
|
|
254
|
-
*/
|
|
255
|
-
export function captureTicket20DataRuleEvidence(input) {
|
|
256
|
-
assertTicket20TestDataRuleEnvironment(input.settings);
|
|
257
|
-
if (!TICKET20_UUID_V4.test(input.testRunId))
|
|
258
|
-
throw new Error("Ticket 20 evidence capture requires the run's host-owned UUID v4 testRunId.");
|
|
259
|
-
const handoff = input.handoff;
|
|
260
|
-
const report = handoff.formDraftExecutionReport;
|
|
261
|
-
const draftEvidence = handoff.formDraftEvidence;
|
|
262
|
-
if (!handoff.formDraftPackageId || !SHA256.test(handoff.formDraftPackageHash) || !/[1-9a-f]/i.test(handoff.formDraftPackageHash) || report.route !== TICKET11_FORM_DRAFT_ROUTE || report.status !== "completed" || report.mutationExecuted !== true || report.behavior?.status !== "validated")
|
|
263
|
-
throw new Error("Ticket 20 data rule requires the completed, behavior-validated form-draft handoff; serial writes never assume state.");
|
|
264
|
-
if (report.testRunId !== input.testRunId || draftEvidence.testRunId !== input.testRunId)
|
|
265
|
-
throw new Error("Ticket 20 composite chain requires the single run testRunId across the model, field, publish, form-draft, and data-rule packages.");
|
|
266
|
-
const targetSchemaCode = draftEvidence.targetSchemaCode;
|
|
267
|
-
const targetFieldCodes = draftEvidence.targetFieldCodes;
|
|
268
|
-
// Ticket 03b: the required-rule demo target is the ACTIVE family's
|
|
269
|
-
// registered rule-demo member (position 2 for date-time: the TIME field).
|
|
270
|
-
const targetFieldCode = targetFieldCodes[activeFieldFamily().ruleDemoTargetPosition - 1];
|
|
271
|
-
const targetSheetCode = draftEvidence.targetSheetCode;
|
|
272
|
-
if (!isTicket08TargetSchema(targetSchemaCode) || !isTicket09TargetField(targetFieldCode) || report.targetSchemaCode !== targetSchemaCode || report.targetSheetCode !== targetSheetCode || report.targetFieldCodes.length !== targetFieldCodes.length || report.targetFieldCodes.some((fieldCode, index) => fieldCode !== targetFieldCodes[index]))
|
|
273
|
-
throw new Error("Ticket 20 fixture family identity diverges from the completed form-draft handoff.");
|
|
274
|
-
const modelPresence = input.modelPresence;
|
|
275
|
-
if (modelPresence.classification !== "present" || modelPresence.query.schemaCode !== targetSchemaCode)
|
|
276
|
-
throw new Error("Ticket 20 data rule requires the fixture model to be present in fresh authoritative readback.");
|
|
277
|
-
targetFieldCodes.forEach((targetCode, index) => {
|
|
278
|
-
const member = TICKET09_FIELD_FAMILY[index];
|
|
279
|
-
const publishedFieldPresence = input.publishedFieldPresence[index];
|
|
280
|
-
if (publishedFieldPresence.classification !== "present" || publishedFieldPresence.reason !== "exact-presence" || publishedFieldPresence.query.schemaCode !== targetSchemaCode || publishedFieldPresence.query.propertyCode !== targetCode || publishedFieldPresence.query.isPublish !== "true")
|
|
281
|
-
throw new Error(`Ticket 20 evidence capture requires exact authenticated published-state presence for the ${member.propertyTypeName} handoff field; rule work only ever consumes published configuration.`);
|
|
282
|
-
if (!publishedFieldPresence.field || publishedFieldPresence.field.code !== targetCode || publishedFieldPresence.field.schemaCode !== targetSchemaCode || publishedFieldPresence.field.published !== true)
|
|
283
|
-
throw new Error(`Ticket 20 evidence capture requires the ${member.propertyTypeName} field's published flag to decode from the authoritative published readback.`);
|
|
284
|
-
});
|
|
285
|
-
const formDraftState = input.formDraftState;
|
|
286
|
-
if (formDraftState.classification !== "present" || formDraftState.reason !== "exact-presence" || formDraftState.query.schemaCode !== targetSchemaCode || formDraftState.query.sheetCode !== targetSheetCode || !formDraftState.form)
|
|
287
|
-
throw new Error("Ticket 20 evidence capture requires the draft-configured default form to be present in fresh authoritative readback.");
|
|
288
|
-
if (formDraftState.form.published !== false)
|
|
289
|
-
throw new Error("Ticket 20 evidence capture requires the fixture form to stay unpublished; the data-rule rung never touches a published form.");
|
|
290
|
-
// §9 re-gate on the consumed draft state: every registered control decodes with
|
|
291
|
-
// the amended options — visible family key, the per-type pinned keys, the
|
|
292
|
-
// the always-readonly boolean on STAFF_SELECTOR, and NO condition strings
|
|
293
|
-
// anywhere (the authoring surface for conditions is the rule rows this
|
|
294
|
-
// rung creates, never the control options keys).
|
|
295
|
-
targetFieldCodes.forEach((targetCode, index) => {
|
|
296
|
-
const presence = decodeTicket11DraftControlPresence(formDraftState.form, targetCode);
|
|
297
|
-
if (!presence.decodable || presence.layoutOccurrences !== 1 || !presence.control)
|
|
298
|
-
throw new Error(`Ticket 20 evidence capture requires the ${TICKET09_FIELD_FAMILY[index].propertyTypeName} handoff field's typed control to be bound exactly once in the consumed draft state.`);
|
|
299
|
-
assertFamilyControlOptions(CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT.boundControls, presence.control, CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT.boundControls[index].propertyTypeName, { schemaCode: targetSchemaCode, memberFieldCodes: targetFieldCodes });
|
|
300
|
-
});
|
|
301
|
-
const requiredRulePreState = input.requiredRulePreState;
|
|
302
|
-
if (requiredRulePreState.classification !== "present" || requiredRulePreState.query.schemaCode !== targetSchemaCode || requiredRulePreState.query.dataRuleType !== TICKET20_REQUIRED_DATA_RULE_TYPE)
|
|
303
|
-
throw new Error("Ticket 20 evidence capture requires the authoritative required-rule (dataRuleType=4) list readback to decode before any write.");
|
|
304
|
-
const boundRequiredRow = requiredRulePreState.rows.find((row) => decodeRequiredDataRuleRow(row, targetSchemaCode, targetFieldCode) !== undefined);
|
|
305
|
-
if (boundRequiredRow)
|
|
306
|
-
throw new Error("Ticket 20 evidence capture requires exact required-rule absence for the handoff field; duplicate rule authoring is rejected.");
|
|
307
|
-
const readonlyRulePreState = input.readonlyRulePreState;
|
|
308
|
-
if (readonlyRulePreState.classification !== "present" || readonlyRulePreState.query.schemaCode !== targetSchemaCode || readonlyRulePreState.query.dataRuleType !== TICKET20_READONLY_CONDITION_DATA_RULE_TYPE)
|
|
309
|
-
throw new Error("Ticket 20 evidence capture requires the authoritative conditional-readonly (dataRuleType=5) list readback to decode before any write.");
|
|
310
|
-
if (readonlyRulePreState.rows.length)
|
|
311
|
-
throw new Error("Ticket 20 evidence capture found conditional-readonly rules on the fixture schema; this capability never mixes with conditional rules.");
|
|
312
|
-
const authoritativeOrigin = input.discovery.authoritativeOrigin;
|
|
313
|
-
const enterpriseId = input.discovery.enterpriseId;
|
|
314
|
-
if (!authoritativeOrigin || !enterpriseId)
|
|
315
|
-
throw new Error("Ticket 20 evidence capture requires authenticated environment fingerprint evidence.");
|
|
316
|
-
const environmentFingerprint = canonicalCloudPivotEnvironmentFingerprint({ environment: TICKET20_TEST_ENVIRONMENT_NAME, authoritativeOrigin, applicationCode: TICKET20_TEST_APPLICATION_CODE, enterpriseId });
|
|
317
|
-
if (environmentFingerprint !== draftEvidence.environmentFingerprint)
|
|
318
|
-
throw new Error("Ticket 20 data rule is outside the form-draft handoff environment fingerprint.");
|
|
319
|
-
const evidence = Object.freeze({
|
|
320
|
-
route: TICKET20_DATA_RULE_ROUTE,
|
|
321
|
-
capabilityId: TICKET20_DATA_RULE_CAPABILITY_ID,
|
|
322
|
-
environmentName: TICKET20_TEST_ENVIRONMENT_NAME,
|
|
323
|
-
applicationCode: TICKET20_TEST_APPLICATION_CODE,
|
|
324
|
-
folder: draftEvidence.folder,
|
|
325
|
-
targetSchemaCode,
|
|
326
|
-
targetSheetCode,
|
|
327
|
-
targetFieldCodes,
|
|
328
|
-
targetFieldCode,
|
|
329
|
-
testRunId: input.testRunId,
|
|
330
|
-
environmentFingerprint,
|
|
331
|
-
baselineFingerprint: canonicalCloudPivotBaselineFingerprint({ environmentFingerprint, applicationCode: TICKET20_TEST_APPLICATION_CODE, discovery: input.discovery }),
|
|
332
|
-
discoveryFingerprint: canonicalFormalDiscoveryFingerprint(input.discovery),
|
|
333
|
-
handoff,
|
|
334
|
-
modelPresence,
|
|
335
|
-
publishedFieldPresence: Object.freeze([...input.publishedFieldPresence]),
|
|
336
|
-
formDraftState,
|
|
337
|
-
requiredRulePreState,
|
|
338
|
-
readonlyRulePreState,
|
|
339
|
-
ruleContract: { dataRuleType: 4, options: CLOUDPIVOT_8_6_26_SERIALIZED_DATA_RULE_CREATE.ruleContract.options, propertyTypeName: fieldFamilyRuleDemoMember().propertyTypeName },
|
|
340
|
-
cleanupContract: buildTicket20RuleCleanupContract(input.testRunId, targetSchemaCode, targetFieldCode),
|
|
341
|
-
designerDirectUrl: ticket20DesignerDirectUrl(targetSchemaCode),
|
|
342
|
-
capturedAt: new Date((input.now ?? Date.now)()).toISOString(),
|
|
343
|
-
});
|
|
344
|
-
if ([evidence.environmentFingerprint, evidence.baselineFingerprint, evidence.discoveryFingerprint].some((value) => !SHA256.test(value)))
|
|
345
|
-
throw new Error("Ticket 20 evidence fingerprints are not canonical.");
|
|
346
|
-
if (SENSITIVE_SECRET_VALUE.test(stable(evidence)))
|
|
347
|
-
throw new Error("Ticket 20 evidence contains prohibited sensitive data.");
|
|
348
|
-
return evidence;
|
|
349
|
-
}
|
|
350
|
-
export const TICKET20_EVIDENCE_DEFAULT_TTL_MS = 25 * 60_000;
|
|
351
|
-
export const TICKET20_EVIDENCE_MAX_TTL_MS = 30 * 60_000;
|
|
352
|
-
/** Single-use, TTL-bounded evidence store; consumed exactly once by plan creation. */
|
|
353
|
-
export class Ticket20EvidenceStore {
|
|
354
|
-
records = new Map();
|
|
355
|
-
now;
|
|
356
|
-
ttlMs;
|
|
357
|
-
lastNow;
|
|
358
|
-
constructor(options = {}) {
|
|
359
|
-
this.now = options.now ?? Date.now;
|
|
360
|
-
this.ttlMs = options.ttlMs ?? TICKET20_EVIDENCE_DEFAULT_TTL_MS;
|
|
361
|
-
if (!Number.isFinite(this.ttlMs) || this.ttlMs < 1 || this.ttlMs > TICKET20_EVIDENCE_MAX_TTL_MS)
|
|
362
|
-
throw new Error("Ticket 20 evidence TTL must be between 1ms and its safety maximum.");
|
|
363
|
-
}
|
|
364
|
-
capture(evidence) {
|
|
365
|
-
validateTicket20Evidence(evidence);
|
|
366
|
-
const evidenceId = `ticket20-evidence-${randomUUID()}`;
|
|
367
|
-
const expiresAt = this.readClock() + this.ttlMs;
|
|
368
|
-
const stored = Object.freeze({ ...evidence, evidenceArtifactId: evidenceId });
|
|
369
|
-
this.records.set(evidenceId, { evidence: stored, expiresAt, consumed: false });
|
|
370
|
-
return { evidenceId, expiresAt, evidence: stored };
|
|
371
|
-
}
|
|
372
|
-
consume(evidenceId) {
|
|
373
|
-
const record = this.records.get(evidenceId);
|
|
374
|
-
if (!record)
|
|
375
|
-
throw new Error("Ticket 20 evidence artifact is missing or divergent.");
|
|
376
|
-
if (record.consumed)
|
|
377
|
-
throw new Error("Ticket 20 evidence has already been consumed.");
|
|
378
|
-
if (record.expiresAt <= this.readClock())
|
|
379
|
-
throw new Error("Ticket 20 evidence is stale or expired.");
|
|
380
|
-
record.consumed = true;
|
|
381
|
-
return record.evidence;
|
|
382
|
-
}
|
|
383
|
-
resolve(evidenceId) {
|
|
384
|
-
const record = this.records.get(evidenceId);
|
|
385
|
-
if (!record || !record.consumed)
|
|
386
|
-
throw new Error("Ticket 20 evidence artifact is missing or has not been consumed by planning.");
|
|
387
|
-
if (record.expiresAt <= this.readClock())
|
|
388
|
-
throw new Error("Ticket 20 evidence artifact is stale or expired.");
|
|
389
|
-
return record.evidence;
|
|
390
|
-
}
|
|
391
|
-
readClock() {
|
|
392
|
-
const currentTime = this.now();
|
|
393
|
-
if (!Number.isFinite(currentTime))
|
|
394
|
-
throw new Error("Ticket 20 evidence clock is invalid; fail closed.");
|
|
395
|
-
if (this.lastNow !== undefined && currentTime < this.lastNow)
|
|
396
|
-
throw new Error("Ticket 20 evidence clock moved backwards; fail closed.");
|
|
397
|
-
this.lastNow = currentTime;
|
|
398
|
-
return currentTime;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
/**
|
|
402
|
-
* Composes the exact evidenced BizDataRuleModel create DTO — the platform
|
|
403
|
-
* writer's own four-member shape: schemaCode, propertyCodes (exactly the
|
|
404
|
-
* active rule-demo field code), dataRuleType 4, and the serialized options JSON
|
|
405
|
-
* string {"requiredFormula":"true","functionType":"true"}. Empty-condition
|
|
406
|
-
* parity is enforced by the capability gate; nothing else ships.
|
|
407
|
-
*/
|
|
408
|
-
export function buildTicket20DataRulePayload(evidence) {
|
|
409
|
-
validateTicket20Evidence(evidence);
|
|
410
|
-
const rule = CLOUDPIVOT_8_6_26_SERIALIZED_DATA_RULE_CREATE.ruleContract;
|
|
411
|
-
return {
|
|
412
|
-
schemaCode: evidence.targetSchemaCode,
|
|
413
|
-
propertyCodes: [evidence.targetFieldCode],
|
|
414
|
-
dataRuleType: rule.dataRuleType,
|
|
415
|
-
options: rule.options,
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
export function buildTicket20ConfigurationIntent(evidence) {
|
|
419
|
-
validateTicket20Evidence(evidence);
|
|
420
|
-
const target = `${evidence.targetSchemaCode}/${evidence.targetFieldCode}`;
|
|
421
|
-
const ruleResourceId = `rule:${target}`;
|
|
422
|
-
return {
|
|
423
|
-
intentVersion: 1,
|
|
424
|
-
targetFamily: "8.6",
|
|
425
|
-
environmentBinding: { application: evidence.applicationCode, model: evidence.targetSchemaCode, baselineFingerprint: evidence.baselineFingerprint },
|
|
426
|
-
sourceRequirement: `Ticket 20 maintainer test (field-type ladder issue 02a, designer-native rule surface): create exactly one unconditional required rule row on the fixture schema — dataRuleType 4 with options {"requiredFormula":"true","functionType":"true"} bound to the published ${fieldFamilyRuleDemoMember().propertyTypeName} family field through propertyCodes — so the designer panel hydrates required checked AND editable from the rule row (type-matrix §9: the condition strings on control options are runtime hydration targets the panel wipes on load, never authoring keys). Never a conditional rule (dataRuleType 5/3), never a rule update/delete in the standard chain, never a form write, app publish, or Environment release — with authoritative rule-list readback, behavior validation, and the stated cascade cleanup contract.`,
|
|
427
|
-
resources: [
|
|
428
|
-
{ id: ruleResourceId, kind: "dataRule", state: "update", properties: { schemaCode: evidence.targetSchemaCode, requiredFieldCode: evidence.targetFieldCode, dataRuleType: 4, options: evidence.ruleContract.options, propertyTypeName: fieldFamilyRuleDemoMember().propertyTypeName } },
|
|
429
|
-
{ id: target, kind: "field", state: "existing", properties: { schemaCode: evidence.targetSchemaCode, published: true } },
|
|
430
|
-
],
|
|
431
|
-
relationships: [{ from: ruleResourceId, to: target, kind: "bindsTo" }],
|
|
432
|
-
lifecycle: [{ resourceId: ruleResourceId, transitions: ["rule-create"], order: 1, idempotent: false, dependencyChecks: ["completed-form-draft-handoff", "required-rule-exact-absence-and-no-conditional-rules-preflight"], recoveryBoundary: ruleResourceId }],
|
|
433
|
-
behavior: [{ id: "data-rule-required-row", kind: "structure", requiredCapabilityIds: [TICKET20_DATA_RULE_CAPABILITY_ID], resourceIds: [ruleResourceId], obligations: [`the authoritative bizdatarule/list?schemaCode&dataRuleType=4 readback decodes the run's required-rule row bound to the ${fieldFamilyRuleDemoMember().propertyTypeName} field through propertyCodes`, "the row's options decode as the pinned serialized JSON {requiredFormula:\"true\", functionType:\"true\"}", "no dataRuleType 5 (conditional-readonly) rows exist on the fixture schema", "the draft selector control options decode with visible=true and the registered cardinality/multi flag, with no condition strings"] }],
|
|
434
|
-
acceptance: [{ id: "ticket20-data-rule-readback", scenario: "designer-native-required-rule-authoring", actor: "omc-maintainer-opencode", assertions: ["bizdatarule/list?schemaCode&dataRuleType=4 decodes our rule row bound to the field", "bizdatarule/list?schemaCode&dataRuleType=5 decodes zero rows", "the consumed draft control options carry no condition strings"], readbackInvariants: [`${TICKET20_RULE_READBACK_ENDPOINT}?schemaCode=<model>&dataRuleType=4 decodes the bound rule row`, `${TICKET20_RULE_READBACK_ENDPOINT}?schemaCode=<model>&dataRuleType=5 decodes zero rows`], outcome: "validated" }],
|
|
435
|
-
cleanup: [{ id: "ticket20-rule-cascade-cleanup", boundary: `ticket20:${evidence.testRunId}:data-rule-cascade-absence`, dependencyChecks: ["staged family field cleanup", "model physical delete", "rule list zero-rows readback"], resources: [`rule:${target}`] }],
|
|
436
|
-
implementationConstraints: { nativePreferred: true, backendRequired: false, prohibitedPaths: ["form.online-js", "business-rule", "integration", "backend.extension"], approvalExceptions: [] },
|
|
437
|
-
evidenceRefs: [TICKET20_DATA_RULE_CAPABILITY_ID, TICKET20_DATA_RULE_ENDPOINT, TICKET20_RULE_READBACK_ENDPOINT, ...CLOUDPIVOT_8_6_26_SERIALIZED_DATA_RULE_CREATE.evidence],
|
|
438
|
-
unresolved: [],
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
function ticket20DataRuleOperation(evidence) {
|
|
442
|
-
const target = `rule:${evidence.targetSchemaCode}/${evidence.targetFieldCode}`;
|
|
443
|
-
return {
|
|
444
|
-
id: `rule-create:${evidence.targetSchemaCode}/${evidence.targetFieldCode}`,
|
|
445
|
-
method: "POST",
|
|
446
|
-
path: TICKET20_DATA_RULE_ENDPOINT,
|
|
447
|
-
target,
|
|
448
|
-
payload: buildTicket20DataRulePayload(evidence),
|
|
449
|
-
expectedReadback: `${target}:created`,
|
|
450
|
-
evidence: [TICKET20_DATA_RULE_CAPABILITY_ID, ...CLOUDPIVOT_8_6_26_SERIALIZED_DATA_RULE_CREATE.evidence, TICKET20_RULE_READBACK_ENDPOINT],
|
|
451
|
-
transportMode: "serialized-server-data-rule-create-8.6.26",
|
|
452
|
-
capability: CLOUDPIVOT_8_6_26_SERIALIZED_DATA_RULE_CREATE,
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
/** Creates and proposes the immutable Ticket 20 Delivery Plan Approval for the single required-rule create step. */
|
|
456
|
-
export function createTicket20DataRulePlanApproval(input) {
|
|
457
|
-
const evidence = validateTicket20Evidence(input.evidence);
|
|
458
|
-
assertDigest(input.knowledgePackDigest, "knowledge pack digest");
|
|
459
|
-
const operation = ticket20DataRuleOperation(evidence);
|
|
460
|
-
assertTicket20OperationsBounded({ route: TICKET20_DATA_RULE_ROUTE, operations: [operation], targetSchemaCode: evidence.targetSchemaCode, targetFieldCode: evidence.targetFieldCode });
|
|
461
|
-
const intent = buildTicket20ConfigurationIntent(evidence);
|
|
462
|
-
if (validateConfigurationIntent(intent).length)
|
|
463
|
-
throw new Error("Ticket 20 Configuration Intent is invalid; planning is blocked.");
|
|
464
|
-
const binding = {
|
|
465
|
-
deliveryId: ticket20DeliveryId(evidence.testRunId),
|
|
466
|
-
environment: TICKET20_TEST_ENVIRONMENT_NAME,
|
|
467
|
-
baselineFingerprint: evidence.baselineFingerprint,
|
|
468
|
-
environmentFingerprint: evidence.environmentFingerprint,
|
|
469
|
-
discoveryFingerprint: evidence.discoveryFingerprint,
|
|
470
|
-
...(evidence.evidenceArtifactId ? { evidenceArtifactId: evidence.evidenceArtifactId } : {}),
|
|
471
|
-
};
|
|
472
|
-
const planHash = digest({ route: TICKET20_DATA_RULE_ROUTE, capabilityId: TICKET20_DATA_RULE_CAPABILITY_ID, target: `${evidence.targetSchemaCode}/${evidence.targetFieldCode}`, testRunId: evidence.testRunId, operation: { id: operation.id, readback: operation.expectedReadback }, requiredFieldCode: evidence.targetFieldCode, familyFieldCodes: [...evidence.targetFieldCodes], formDraftHandoff: { packageId: evidence.handoff.formDraftPackageId, packageHash: evidence.handoff.formDraftPackageHash }, folder: evidence.folder, fingerprints: { baseline: evidence.baselineFingerprint, environment: evidence.environmentFingerprint, discovery: evidence.discoveryFingerprint }, cleanupContractHash: ticket20RuleCleanupContractHash(), knowledgePackDigest: input.knowledgePackDigest, prohibited: [...TICKET20_PROHIBITED_MUTATION_TERMS] });
|
|
473
|
-
const base = createDeliveryPlanApproval({
|
|
474
|
-
binding,
|
|
475
|
-
requirementHash: digest({ target: `${evidence.targetSchemaCode}/${evidence.targetFieldCode}`, dataRule: true, dataRuleType: 4 }),
|
|
476
|
-
clarificationHash: digest("ticket20-test-data-rule"),
|
|
477
|
-
intentHash: digest(intent),
|
|
478
|
-
decisionHash: digest(TICKET20_DATA_RULE_ROUTE),
|
|
479
|
-
graphHash: digest([operation.id]),
|
|
480
|
-
riskHash: digest(TICKET20_RISKS),
|
|
481
|
-
validationPlanHash: digest("authoritative-rule-list-readback-row-decode-and-no-conditional-rows"),
|
|
482
|
-
cleanupPlanHash: ticket20RuleCleanupContractHash(),
|
|
483
|
-
allowedOperationScope: [ticket20DataRuleScope(evidence.targetSchemaCode, evidence.targetFieldCode)],
|
|
484
|
-
planHash,
|
|
485
|
-
applicationCode: TICKET20_TEST_APPLICATION_CODE,
|
|
486
|
-
folderId: evidence.folder.id,
|
|
487
|
-
folderName: evidence.folder.name,
|
|
488
|
-
targets: [`${evidence.targetSchemaCode}/${evidence.targetFieldCode}`],
|
|
489
|
-
});
|
|
490
|
-
const approval = {
|
|
491
|
-
...base,
|
|
492
|
-
route: TICKET20_DATA_RULE_ROUTE,
|
|
493
|
-
testRunId: evidence.testRunId,
|
|
494
|
-
targetSchemaCode: evidence.targetSchemaCode,
|
|
495
|
-
targetSheetCode: evidence.targetSheetCode,
|
|
496
|
-
targetFieldCodes: evidence.targetFieldCodes,
|
|
497
|
-
targetFieldCode: evidence.targetFieldCode,
|
|
498
|
-
capabilityId: TICKET20_DATA_RULE_CAPABILITY_ID,
|
|
499
|
-
knowledgePackDigest: input.knowledgePackDigest,
|
|
500
|
-
cleanupContractHash: ticket20RuleCleanupContractHash(),
|
|
501
|
-
environmentName: TICKET20_TEST_ENVIRONMENT_NAME,
|
|
502
|
-
};
|
|
503
|
-
const proposal = { ...approval, approvalHash: deliveryPlanApprovalHash(approval) };
|
|
504
|
-
proposeDeliveryPlan(input.ledger, proposal);
|
|
505
|
-
return { approval: proposal, operation, cleanupContract: evidence.cleanupContract, intent, binding };
|
|
506
|
-
}
|
|
507
|
-
const TICKET20_RISKS = Object.freeze([
|
|
508
|
-
"Exactly one unconditional required rule row bound to the run's own published fixture field is in scope — dataRuleType 4 with options {requiredFormula:\"true\",functionType:\"true\"}; conditional rules (dataRuleType 5/3), rule updates, form writes, app publish, and Environment release remain rejected, and empty conditions are rejected with the designer's client-side parity.",
|
|
509
|
-
"Rule rows are schema-scoped design-time state that die with the separately approved staged cleanup (published-field cleanup, then the model physical delete cascade); rule-level absence is confirmed by the authoritative bizdatarule/list zero-rows readback, and if the live cascade leaves survivors the pinned /delete?id contract is the explicit pre-cascade fallback — the outcome stops, retains, and reports.",
|
|
510
|
-
]);
|
|
511
|
-
/** Compiles the immutable one-step data-rule package bound to the approved plan, pack digest, and pinned contract hash. */
|
|
512
|
-
export function prepareTicket20DataRulePackage(input) {
|
|
513
|
-
const evidence = validateTicket20Evidence(input.evidence);
|
|
514
|
-
const approval = requireTicket20Approval(input.approval);
|
|
515
|
-
const persisted = requirePersistedApprovedPlan(input.ledger, approval);
|
|
516
|
-
assertTicket20BindingMatchesEvidence(persisted.binding, evidence);
|
|
517
|
-
const scope = ticket20DataRuleScope(evidence.targetSchemaCode, evidence.targetFieldCode);
|
|
518
|
-
if (!persisted.allowedOperationScope.includes(scope))
|
|
519
|
-
throw new Error(`Ticket 20 package step is outside the approved operation scope: ${scope}.`);
|
|
520
|
-
if (input.pinnedDataRuleContractHash !== ticket20DataRuleContractHash())
|
|
521
|
-
throw new Error("Ticket 20 pinned data-rule contract hash does not match the canonical 8.6.26 capability contract.");
|
|
522
|
-
assertDigest(input.knowledgePackDigest, "knowledge pack digest");
|
|
523
|
-
if (input.knowledgePackDigest !== approval.knowledgePackDigest)
|
|
524
|
-
throw new Error("Ticket 20 knowledge pack digest diverges from the approved plan binding.");
|
|
525
|
-
const operation = ticket20DataRuleOperation(evidence);
|
|
526
|
-
assertTicket20OperationsBounded({ route: TICKET20_DATA_RULE_ROUTE, operations: [operation], targetSchemaCode: evidence.targetSchemaCode, targetFieldCode: evidence.targetFieldCode });
|
|
527
|
-
const snapshotBase = { id: `snapshot:${operation.id}`, target: operation.target, identity: operation.target, recoveryBoundary: operation.target, state: "absent", baselineFingerprint: persisted.binding.baselineFingerprint };
|
|
528
|
-
const snapshot = { ...snapshotBase, snapshotHash: digest(snapshotBase) };
|
|
529
|
-
const pkg = compileNativeOperations({
|
|
530
|
-
operations: [operation],
|
|
531
|
-
binding: persisted.binding,
|
|
532
|
-
planApproval: persisted,
|
|
533
|
-
snapshotManifest: [snapshot],
|
|
534
|
-
releasePlanHash: digest("ticket20-no-release"),
|
|
535
|
-
recoveryPolicyHash: digest("ticket20-data-rule-step-only"),
|
|
536
|
-
knowledgePack: { id: TICKET20_DATA_RULE_KNOWLEDGE_PACK_ID, digest: input.knowledgePackDigest },
|
|
537
|
-
capabilityEvidence: [TICKET20_DATA_RULE_CAPABILITY_ID, "cloudpivot.data-rule-create.serialized-server-8.6.26", TICKET20_RULE_READBACK_ENDPOINT, `requiredField:${evidence.targetFieldCode}`, ...evidence.targetFieldCodes.map((fieldCode) => `familyField:${fieldCode}`), `formDraftHandoff:${evidence.handoff.formDraftPackageId}`, `folder:${evidence.folder.id}`, `testRunId:${evidence.testRunId}`, `baselineFingerprint:${persisted.binding.baselineFingerprint}`],
|
|
538
|
-
requiredPermissions: ["configuration-write"],
|
|
539
|
-
packageId: `ticket20-data-rule-${digest(persisted.approvalId).slice(0, 32)}`,
|
|
540
|
-
ledger: input.ledger,
|
|
541
|
-
});
|
|
542
|
-
input.ledger.createPackage(pkg);
|
|
543
|
-
return { package: pkg, operations: [operation], planApproval: persisted, dryRun: ticket20DryRun(evidence, [operation]), evidence };
|
|
544
|
-
}
|
|
545
|
-
/** Read-only dry-run projection; never a CloudPivot write and never a conditional-rule or rule-update operation. */
|
|
546
|
-
function ticket20DryRun(evidence, operations) {
|
|
547
|
-
const readbackOperations = [
|
|
548
|
-
{ id: `readback:rule:${evidence.targetSchemaCode}:4`, method: "GET", path: TICKET20_RULE_READBACK_ENDPOINT, target: `rule:${evidence.targetSchemaCode}/${evidence.targetFieldCode}`, payload: { schemaCode: evidence.targetSchemaCode, dataRuleType: 4 }, expectedReadback: `rule:${evidence.targetSchemaCode}/${evidence.targetFieldCode}:read`, evidence: ["BizDataRuleController.java:86-427"] },
|
|
549
|
-
{ id: `readback:rule:${evidence.targetSchemaCode}:5`, method: "GET", path: TICKET20_RULE_READBACK_ENDPOINT, target: `rule:${evidence.targetSchemaCode}/${evidence.targetFieldCode}`, payload: { schemaCode: evidence.targetSchemaCode, dataRuleType: 5 }, expectedReadback: `rule:${evidence.targetSchemaCode}/${evidence.targetFieldCode}:read`, evidence: ["BizDataRuleController.java:86-427"] },
|
|
550
|
-
];
|
|
551
|
-
const body = JSON.stringify({ execution: "read-only-dry-run", operations, readbackOperations, blocked: [], prohibitedActions: ["cloudpivot-write"] }, null, 2) + "\n";
|
|
552
|
-
return { execution: "read-only-dry-run", operations, readbackOperations, blocked: [], prohibitedActions: ["cloudpivot-write"], artifact: { name: "cloudpivot-ticket20-data-rule-dry-run.json", body, sha256: digest(body), mimeType: "application/json" } };
|
|
553
|
-
}
|
|
554
|
-
export function formatTicket20DeliveryPlanApprovalFrame(approval) {
|
|
555
|
-
const value = requireTicket20Approval(approval);
|
|
556
|
-
return appendApprovalHumanBlock(`Approve Ticket 20 data rule 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}; required field ${value.targetFieldCode} (the ${fieldFamilyRuleDemoMember().propertyTypeName} member of ${fieldFamilyMemberNamesProse()}); testRunId ${value.testRunId}; exact scope ${value.allowedOperationScope[0]}; one designer-native required-rule create step (POST ${TICKET20_DATA_RULE_ENDPOINT} with the platform writer's exact four-member BizDataRuleModel: schemaCode, propertyCodes [${value.targetFieldCode}], dataRuleType 4, options {"requiredFormula":"true","functionType":"true"}) — the authoring surface the designer panel itself hydrates required/readonly conditions from (type-matrix §9); never a conditional rule, rule update, form write, app publish, or Environment release — with authoritative rule-list readback, empty-condition parity gates, behavior validation, and the stated cascade cleanup contract.`, { capability: "ticket20-data-rule", kind: "delivery-plan", identities: [value.targetSchemaCode, value.targetFieldCode], scope: [value.allowedOperationScope[0]] });
|
|
557
|
-
}
|
|
558
|
-
export function formatTicket20EnvironmentChangeApprovalFrame(input) {
|
|
559
|
-
const value = requireTicket20Approval(input.approval);
|
|
560
|
-
return appendApprovalHumanBlock(`Approve Ticket 20 data rule 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}; required field ${value.targetFieldCode}; testRunId ${value.testRunId}; exact scope ${value.allowedOperationScope[0]}; exactly one designer-native required-rule create step (dataRuleType 4, options {"requiredFormula":"true","functionType":"true"}, propertyCodes [${value.targetFieldCode}]) executed by the single Forge configuration writer lease with the authoritative rule-list readback and the no-conditional-rows proof.`, { capability: "ticket20-data-rule", kind: "environment-change", identities: [value.targetSchemaCode, value.targetFieldCode], scope: [value.allowedOperationScope[0]] });
|
|
561
|
-
}
|
|
562
|
-
/**
|
|
563
|
-
* Validates real data-rule behavior beyond HTTP 2xx: the authoritative
|
|
564
|
-
* bizdatarule/list?schemaCode&dataRuleType=4 readback decodes our rule row
|
|
565
|
-
* bound to the field with the pinned options; NO dataRuleType 5 rows exist;
|
|
566
|
-
* and the draft control options still decode with visible + the
|
|
567
|
-
* always-readonly boolean and WITHOUT any condition strings (§9: the rule
|
|
568
|
-
* rows are the authoring surface; the options strings are hydration
|
|
569
|
-
* targets).
|
|
570
|
-
*/
|
|
571
|
-
export function validateTicket20DataRuleBehavior(input) {
|
|
572
|
-
const evidence = validateTicket20Evidence(input.evidence);
|
|
573
|
-
const checks = [];
|
|
574
|
-
const add = (id, ok, detail) => checks.push({ id, status: ok ? "validated" : "blocked", detail });
|
|
575
|
-
const requiredRuleList = input.requiredRuleList;
|
|
576
|
-
const listOk = requiredRuleList.classification === "present" && requiredRuleList.query.schemaCode === evidence.targetSchemaCode && requiredRuleList.query.dataRuleType === 4;
|
|
577
|
-
add("required-rule-list-decoded", listOk, listOk ? `the authoritative rule-list readback decoded ${String(requiredRuleList.rows.length)} dataRuleType=4 row(s) for the fixture schema.` : "the authoritative dataRuleType=4 rule-list readback did not decode after the create returned success.");
|
|
578
|
-
const boundRow = listOk ? requiredRuleList.rows.find((row) => decodeRequiredDataRuleRow(row, evidence.targetSchemaCode, evidence.targetFieldCode) !== undefined) : undefined;
|
|
579
|
-
add("required-rule-row-bound", Boolean(boundRow), boundRow ? `the required-rule row decodes bound to the TIME field ${evidence.targetFieldCode} through propertyCodes.` : `the authoritative dataRuleType=4 rule list does not carry our rule row bound to the field ${evidence.targetFieldCode}.`);
|
|
580
|
-
const decodedRow = boundRow ? decodeRequiredDataRuleRow(boundRow, evidence.targetSchemaCode, evidence.targetFieldCode) : undefined;
|
|
581
|
-
const optionsOk = decodedRow !== undefined && decodedRow.options.requiredFormula === "true" && decodedRow.options.functionType === "true";
|
|
582
|
-
add("required-rule-options-decoded", optionsOk, optionsOk ? 'the row options decode as the pinned unconditional contract {requiredFormula:"true", functionType:"true"}.' : `the row options did not decode as the pinned unconditional contract (got ${boundRow ? JSON.stringify(boundRow.options) : "no row"}).`);
|
|
583
|
-
const readonlyRuleList = input.readonlyRuleList;
|
|
584
|
-
const noConditional = readonlyRuleList.classification === "present" && readonlyRuleList.query.dataRuleType === 5 && readonlyRuleList.rows.length === 0;
|
|
585
|
-
add("no-conditional-readonly-rules", noConditional, noConditional ? "the authoritative dataRuleType=5 rule list decodes zero rows — nothing conditional was authored." : "the authoritative dataRuleType=5 rule list did not decode as zero rows; conditional rules are outside this capability.");
|
|
586
|
-
// §9 re-gate on the post-create draft state: the controls still carry
|
|
587
|
-
// visible + the always-readonly boolean and NO condition strings.
|
|
588
|
-
evidence.targetFieldCodes.forEach((fieldCode, index) => {
|
|
589
|
-
const binding = CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT.boundControls[index];
|
|
590
|
-
const suffix = binding.propertyTypeName.toLowerCase();
|
|
591
|
-
const presence = decodeTicket11DraftControlPresence(input.formDraftState.form ?? { draftViewJson: null, draftAttributesJson: null }, fieldCode);
|
|
592
|
-
const control = presence.control;
|
|
593
|
-
let optionsOkForControl = false;
|
|
594
|
-
if (control) {
|
|
595
|
-
try {
|
|
596
|
-
assertFamilyControlOptions(CLOUDPIVOT_8_6_26_SERIALIZED_FORM_DRAFT.boundControls, control, binding.propertyTypeName, { schemaCode: evidence.targetSchemaCode, memberFieldCodes: evidence.targetFieldCodes });
|
|
597
|
-
optionsOkForControl = true;
|
|
598
|
-
}
|
|
599
|
-
catch {
|
|
600
|
-
optionsOkForControl = false;
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
add(`draft-control-options-${suffix}-no-condition-strings`, optionsOkForControl, optionsOkForControl
|
|
604
|
-
? `the ${binding.propertyTypeName} control options still decode with the pinned keys and WITHOUT requiredFormula/readonlyCondition condition strings (the rule rows are the authoring surface).`
|
|
605
|
-
: `the ${binding.propertyTypeName} control options failed the §9 re-gate on the post-create draft readback.`);
|
|
606
|
-
});
|
|
607
|
-
// Ticket 03b: the always-readonly demo check (id + wording) is the ACTIVE
|
|
608
|
-
// family's registered descriptor, and its member position selects the
|
|
609
|
-
// re-gated control selected by the ACTIVE family's registered position.
|
|
610
|
-
const alwaysReadonlyFieldCode = evidence.targetFieldCodes[activeFieldFamily().alwaysReadonlyDemoPosition - 1];
|
|
611
|
-
const visibleReadonlyOk = Boolean(input.formDraftState.form) && (() => {
|
|
612
|
-
const control = input.formDraftState.form ? decodeDraftControl(input.formDraftState.form.draftViewJson, input.formDraftState.form.draftAttributesJson, alwaysReadonlyFieldCode) : undefined;
|
|
613
|
-
return control?.options?.visible === true && control.options.readonlyFormula === true;
|
|
614
|
-
})();
|
|
615
|
-
add(activeFieldFamily().alwaysReadonlyDemoCheck.id, visibleReadonlyOk, visibleReadonlyOk ? activeFieldFamily().alwaysReadonlyDemoCheck.passDetail : activeFieldFamily().alwaysReadonlyDemoCheck.failDetail);
|
|
616
|
-
const blocked = checks.find((check) => check.status === "blocked");
|
|
617
|
-
return blocked ? { status: "blocked", reason: `Ticket 20 behavior validation failed: ${blocked.detail}`, checks } : { status: "validated", checks };
|
|
618
|
-
}
|
|
619
|
-
/** Execution boundary: named test environment, route-bound package steps, active approvals, no replay of terminal or uncertain packages. */
|
|
620
|
-
export function assertTicket20ExecutionBoundary(input) {
|
|
621
|
-
if (input.environmentName !== "test")
|
|
622
|
-
throw new Error("Ticket 20 execution is restricted to the named test environment; production and staging are rejected.");
|
|
623
|
-
const { projection } = input;
|
|
624
|
-
if (input.steps.length !== 1 || !input.steps[0].id.startsWith("rule-create:"))
|
|
625
|
-
throw new Error(`Ticket 20 execution boundary rejected package ${input.packageId}: its steps are not the single rule-create: operation bound to the ${input.route} route.`);
|
|
626
|
-
if (projection.status === "approved" && projection.approvals.plan === "approved" && projection.approvals.environment === "approved")
|
|
627
|
-
return;
|
|
628
|
-
if (projection.status === "completed")
|
|
629
|
-
throw new Error(`Ticket 20 package ${input.packageId} is already terminal completed; replay is rejected.`);
|
|
630
|
-
if (projection.status === "uncertain")
|
|
631
|
-
throw new Error(`Ticket 20 package ${input.packageId} is uncertain; reconcile by authoritative readback before any replay.`);
|
|
632
|
-
if (projection.status === "in_progress")
|
|
633
|
-
throw new Error(`Ticket 20 package ${input.packageId} is already running; concurrent execution is rejected.`);
|
|
634
|
-
const pending = [...projection.blockers];
|
|
635
|
-
if (projection.approvals.plan !== "approved")
|
|
636
|
-
pending.push("Delivery Plan Approval is required before package execution.");
|
|
637
|
-
if (projection.approvals.environment !== "approved")
|
|
638
|
-
pending.push("Environment Change Approval is required before mutation.");
|
|
639
|
-
throw new Error(`Ticket 20 execution boundary rejected package ${input.packageId}: status is ${projection.status} (blocked); ${[...new Set(pending)].join("; ")}.`);
|
|
640
|
-
}
|
|
641
|
-
/** Executes the approved one-step required-rule create package through the single OMC Host write transport, then validates the rule row and the no-conditional proofs. */
|
|
642
|
-
export async function executeTicket20DataRulePackage(input) {
|
|
643
|
-
assertTicket20TestDataRuleEnvironment(input.settings);
|
|
644
|
-
if (typeof input.transport.serializedServerDataRuleCreate !== "function")
|
|
645
|
-
throw new Error("Ticket 20 execution requires the OMC Host serialized data-rule transport; bypass transports are rejected.");
|
|
646
|
-
const projection = input.ledger.project(input.prepared.package.packageId);
|
|
647
|
-
assertTicket20ExecutionBoundary({ projection, environmentName: input.settings.environmentName, packageId: input.prepared.package.packageId, route: TICKET20_DATA_RULE_ROUTE, steps: input.prepared.package.steps });
|
|
648
|
-
if (input.environmentApproval.packageId !== input.prepared.package.packageId || input.environmentApproval.packageHash !== input.prepared.package.packageHash)
|
|
649
|
-
throw new Error("Ticket 20 Environment Change Approval does not match the prepared package.");
|
|
650
|
-
const evidence = input.prepared.evidence;
|
|
651
|
-
const preflightRequired = await input.readRequiredRuleList();
|
|
652
|
-
if (preflightRequired.classification !== "present")
|
|
653
|
-
throw new Error("Ticket 20 execution requires the authoritative rule-list readback to decode before the write.");
|
|
654
|
-
if (preflightRequired.rows.some((row) => decodeRequiredDataRuleRow(row, evidence.targetSchemaCode, evidence.targetFieldCode) !== undefined))
|
|
655
|
-
throw new Error(`Ticket 20 execution requires exact required-rule absence before the create; ${evidence.targetFieldCode} already carries a bound rule row.`);
|
|
656
|
-
const preflightReadonly = await input.readonlyRuleList();
|
|
657
|
-
if (preflightReadonly.classification !== "present" || preflightReadonly.rows.length)
|
|
658
|
-
throw new Error("Ticket 20 execution requires zero conditional-readonly rules on the fixture schema before the write.");
|
|
659
|
-
const result = await runNativeConfigurationPackage({
|
|
660
|
-
package: input.prepared.package,
|
|
661
|
-
gate: {
|
|
662
|
-
mode: "approved",
|
|
663
|
-
planApproval: { status: "approved", approvalHash: input.prepared.planApproval.approvalHash },
|
|
664
|
-
environmentApproval: { status: "approved", packageHash: input.environmentApproval.packageHash },
|
|
665
|
-
lease: { owner: "configuration-agent", environment: TICKET20_TEST_ENVIRONMENT_NAME },
|
|
666
|
-
baselineFingerprint: input.prepared.package.binding.baselineFingerprint,
|
|
667
|
-
snapshots: input.prepared.package.snapshotManifest,
|
|
668
|
-
package: input.prepared.package,
|
|
669
|
-
},
|
|
670
|
-
operations: input.prepared.operations,
|
|
671
|
-
ledger: input.ledger,
|
|
672
|
-
planApproval: input.prepared.planApproval,
|
|
673
|
-
transport: input.transport,
|
|
674
|
-
credentials: input.credentials,
|
|
675
|
-
authoritativeOrigin: input.authoritativeOrigin,
|
|
676
|
-
testApplicationCode: TICKET20_TEST_APPLICATION_CODE,
|
|
677
|
-
}, input.signal);
|
|
678
|
-
const mutationExecuted = result.steps.some((step) => step.status === "committed" || step.status === "uncertain");
|
|
679
|
-
const blockers = [...result.blockers];
|
|
680
|
-
let status = ticket20ExecutionStatus(result.status);
|
|
681
|
-
let nextAction = result.nextAction;
|
|
682
|
-
let behavior;
|
|
683
|
-
if (result.status === "completed") {
|
|
684
|
-
behavior = validateTicket20DataRuleBehavior({ evidence, requiredRuleList: await input.readRequiredRuleList(), readonlyRuleList: await input.readonlyRuleList(), formDraftState: await input.readFormDraftState() });
|
|
685
|
-
if (behavior.status !== "validated") {
|
|
686
|
-
status = "blocked";
|
|
687
|
-
blockers.push(behavior.reason ?? "Ticket 20 behavior validation failed.");
|
|
688
|
-
nextAction = "reconcile-behavior-retain-and-report-resource";
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
return { reportType: "omc.ticket20.test-data-rule-execution", schemaVersion: 1, packageId: result.packageId, packageHash: result.packageHash, route: TICKET20_DATA_RULE_ROUTE, testRunId: evidence.testRunId, targetSchemaCode: evidence.targetSchemaCode, targetSheetCode: evidence.targetSheetCode, targetFieldCodes: evidence.targetFieldCodes, targetFieldCode: evidence.targetFieldCode, status, mutationExecuted, steps: result.steps, blockers, nextAction, ...(behavior ? { behavior } : {}) };
|
|
692
|
-
}
|
|
693
|
-
/**
|
|
694
|
-
* Closes the stated rule cascade cleanup contract: after the separately
|
|
695
|
-
* approved staged cleanup (published-field cleanup, then the model physical
|
|
696
|
-
* delete), the authoritative bizdatarule/list readback must decode zero
|
|
697
|
-
* rows for the fixture schema (or the recorded schema-absence envelope).
|
|
698
|
-
* Anything else stops, retains, and reports.
|
|
699
|
-
*/
|
|
700
|
-
export function confirmTicket20RuleCascadeAbsence(input) {
|
|
701
|
-
const evidence = validateTicket20Evidence(input.evidence);
|
|
702
|
-
const readback = input.ruleListReadback;
|
|
703
|
-
if (readback.query.schemaCode !== evidence.targetSchemaCode || readback.query.dataRuleType !== 4)
|
|
704
|
-
throw new Error("Ticket 20 rule cascade confirmation requires the dataRuleType=4 list readback for the fixture schema.");
|
|
705
|
-
if (readback.classification === "absent")
|
|
706
|
-
return;
|
|
707
|
-
if (readback.classification !== "present" || readback.rows.length !== 0)
|
|
708
|
-
throw new Error(`Ticket 20 rule cascade cleanup contract is not closed: the bizdatarule/list readback still decodes ${String(readback.rows.length)} row(s) after the model physical delete (classification=${readback.classification}); if the live cascade leaves survivors, execute the pinned /delete?id fallback explicitly and stop, retain, and report.`);
|
|
709
|
-
}
|
|
710
|
-
function ticket20ExecutionStatus(status) {
|
|
711
|
-
if (status === "completed" || status === "uncertain" || status === "partial")
|
|
712
|
-
return status;
|
|
713
|
-
return "blocked";
|
|
714
|
-
}
|
|
715
|
-
function requireTicket20Approval(approval) {
|
|
716
|
-
const value = approval;
|
|
717
|
-
if (value.route !== TICKET20_DATA_RULE_ROUTE || value.capabilityId !== TICKET20_DATA_RULE_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 || !isTicket09TargetField(value.targetFieldCode) || value.targetFieldCode !== value.targetFieldCodes[activeFieldFamily().ruleDemoTargetPosition - 1])
|
|
718
|
-
throw new Error(`Ticket 20 approval frame is not bound to the exact ${TICKET20_DATA_RULE_ROUTE} route, family targets, required field, folder, and testRunId.`);
|
|
719
|
-
return value;
|
|
720
|
-
}
|
|
721
|
-
function requirePersistedApprovedPlan(ledger, approval) {
|
|
722
|
-
if (approval.status !== "approved")
|
|
723
|
-
throw new Error("Ticket 20 package preparation requires the approved exact Ticket 20 Delivery Plan Approval.");
|
|
724
|
-
const persisted = ledger.findPlanApproval(approval.approvalId);
|
|
725
|
-
if (!persisted || persisted.status !== "approved" || persisted.approvalHash !== approval.approvalHash)
|
|
726
|
-
throw new Error("Ticket 20 package preparation requires the exact authoritative approved Delivery Plan Approval.");
|
|
727
|
-
return persisted;
|
|
728
|
-
}
|
|
729
|
-
function assertTicket20BindingMatchesEvidence(binding, evidence) {
|
|
730
|
-
const expected = {
|
|
731
|
-
deliveryId: ticket20DeliveryId(evidence.testRunId),
|
|
732
|
-
environment: TICKET20_TEST_ENVIRONMENT_NAME,
|
|
733
|
-
baselineFingerprint: evidence.baselineFingerprint,
|
|
734
|
-
environmentFingerprint: evidence.environmentFingerprint,
|
|
735
|
-
discoveryFingerprint: evidence.discoveryFingerprint,
|
|
736
|
-
...(evidence.evidenceArtifactId ? { evidenceArtifactId: evidence.evidenceArtifactId } : {}),
|
|
737
|
-
};
|
|
738
|
-
if (stable(binding) !== stable(expected))
|
|
739
|
-
throw new Error("Ticket 20 plan approval binding is outside the evidence baseline, environment, discovery, or delivery binding.");
|
|
740
|
-
}
|
|
741
|
-
function validateTicket20Evidence(evidence) {
|
|
742
|
-
if (!evidence || evidence.route !== TICKET20_DATA_RULE_ROUTE || evidence.capabilityId !== TICKET20_DATA_RULE_CAPABILITY_ID)
|
|
743
|
-
throw new Error("Ticket 20 evidence is not the host-owned data-rule route.");
|
|
744
|
-
if (evidence.environmentName !== "test" || evidence.applicationCode !== "test")
|
|
745
|
-
throw new Error("Ticket 20 evidence is outside the named test environment and dedicated test application.");
|
|
746
|
-
if (!isTicket08TargetSchema(evidence.targetSchemaCode))
|
|
747
|
-
throw new Error("Ticket 20 evidence fixture model is not the host-owned Ticket 08 schema.");
|
|
748
|
-
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)
|
|
749
|
-
throw new Error("Ticket 20 evidence family field codes are not the distinct host-owned Ticket 09 field codes (one per active family member).");
|
|
750
|
-
if (!isTicket09TargetField(evidence.targetFieldCode) || evidence.targetFieldCode !== evidence.targetFieldCodes[activeFieldFamily().ruleDemoTargetPosition - 1])
|
|
751
|
-
throw new Error("Ticket 20 evidence required field is not the active family's registered rule-demo member.");
|
|
752
|
-
if (evidence.ruleContract.dataRuleType !== 4 || evidence.ruleContract.options !== CLOUDPIVOT_8_6_26_SERIALIZED_DATA_RULE_CREATE.ruleContract.options || evidence.ruleContract.propertyTypeName !== fieldFamilyRuleDemoMember().propertyTypeName)
|
|
753
|
-
throw new Error("Ticket 20 evidence rule contract is not the pinned unconditional required rule.");
|
|
754
|
-
if ([evidence.environmentFingerprint, evidence.baselineFingerprint, evidence.discoveryFingerprint].some((value) => !SHA256.test(value)))
|
|
755
|
-
throw new Error("Ticket 20 evidence fingerprints are not canonical.");
|
|
756
|
-
if (evidence.modelPresence.classification !== "present" || evidence.modelPresence.query.schemaCode !== evidence.targetSchemaCode)
|
|
757
|
-
throw new Error("Ticket 20 evidence does not carry the authoritative fixture model presence handoff.");
|
|
758
|
-
evidence.targetFieldCodes.forEach((fieldCode, index) => {
|
|
759
|
-
const presence = evidence.publishedFieldPresence[index];
|
|
760
|
-
if (presence?.classification !== "present" || presence.query.schemaCode !== evidence.targetSchemaCode || presence.query.propertyCode !== fieldCode || presence.query.isPublish !== "true" || presence.field?.published !== true)
|
|
761
|
-
throw new Error("Ticket 20 evidence does not carry exact authenticated published family field presence.");
|
|
762
|
-
});
|
|
763
|
-
if (evidence.formDraftState.classification !== "present" || evidence.formDraftState.query.schemaCode !== evidence.targetSchemaCode || evidence.formDraftState.query.sheetCode !== evidence.targetSheetCode || !evidence.formDraftState.form || evidence.formDraftState.form.published !== false)
|
|
764
|
-
throw new Error("Ticket 20 evidence does not carry the exact authenticated draft-configured form state.");
|
|
765
|
-
if (evidence.requiredRulePreState.classification !== "present" || evidence.requiredRulePreState.query.dataRuleType !== 4 || evidence.readonlyRulePreState.classification !== "present" || evidence.readonlyRulePreState.query.dataRuleType !== 5)
|
|
766
|
-
throw new Error("Ticket 20 evidence does not carry the exact authenticated rule-list pre-states.");
|
|
767
|
-
if (!evidence.folder.id || !evidence.folder.name)
|
|
768
|
-
throw new Error("Ticket 20 evidence folder binding is missing.");
|
|
769
|
-
return evidence;
|
|
770
|
-
}
|
|
771
|
-
function assertDigest(value, label) {
|
|
772
|
-
if (typeof value !== "string" || !SHA256.test(value) || !/[1-9a-f]/i.test(value))
|
|
773
|
-
throw new Error(`Ticket 20 ${label} is invalid.`);
|
|
774
|
-
}
|
|
775
|
-
function digest(value) { return createHash("sha256").update(stable(value)).digest("hex"); }
|
|
776
|
-
function stable(value) { return JSON.stringify(sort(value)); }
|
|
777
|
-
function sort(value) { if (Array.isArray(value))
|
|
778
|
-
return value.map(sort); if (value && typeof value === "object")
|
|
779
|
-
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, sort(item)])); return value; }
|
|
780
|
-
//# sourceMappingURL=ticket20-test-data-rule.js.map
|