omc-core 0.4.35 → 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,5 +1,5 @@
|
|
|
1
1
|
import { buildChildTableDeleteRequest, buildSheetControlBinding } from "./childtable-field-contract.js";
|
|
2
|
-
import { ticket11ScaffoldControlKeys, buildTicket11ScaffoldControls, ticket11DraftLayoutIsEmpty } from "./
|
|
2
|
+
import { ticket11ScaffoldControlKeys, buildTicket11ScaffoldControls, ticket11DraftLayoutIsEmpty } from "./form-scaffold-contract.js";
|
|
3
3
|
export const CHILD_TABLE_SHEET201_ENDPOINTS = Object.freeze({
|
|
4
4
|
fieldPublish: "/api/api/app/bizproperty/publish",
|
|
5
5
|
formGet: "/api/api/app/bizsheet/get",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CHILD_TABLE_CONTRACT } from "./childtable-field-contract.js";
|
|
2
2
|
import { FORMULA_FIELD_CONTRACT } from "./formula-field-contract.js";
|
|
3
|
-
import { ticket08TargetSchemaCode, isTicket08TargetSchema } from "./
|
|
3
|
+
import { ticket08TargetSchemaCode, isTicket08TargetSchema } from "./field-probe-schema.js";
|
|
4
4
|
export const FIELD_PROBE_MODES = Object.freeze(["rehearsal", "execute"]);
|
|
5
5
|
export const FIELD_PROBE_KINDS = Object.freeze(["formula", "childtable"]);
|
|
6
6
|
export const FIELD_PROBE_PHASES = Object.freeze(["field-create", "publish", "controls"]);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 字段探针目标模型 schema 契约(生产):omc_t08_<16hex> 生成/识别/断言。
|
|
3
|
+
* 原实现住在 ticket08 测试编排内;C2-lite 拆分——field-probe-runner 与测试岛屿共用。
|
|
4
|
+
*/
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
export const TICKET08_TARGET_SCHEMA_PATTERN = /^omc_t08_[0-9a-f]{16}$/;
|
|
7
|
+
const TICKET08_SCHEMA_CODE_PREFIX = "omc_t08_";
|
|
8
|
+
export function ticket08TargetSchemaCode() {
|
|
9
|
+
return `${TICKET08_SCHEMA_CODE_PREFIX}${randomUUID().replaceAll("-", "").slice(0, 16)}`;
|
|
10
|
+
}
|
|
11
|
+
export function isTicket08TargetSchema(targetSchemaCode) {
|
|
12
|
+
return TICKET08_TARGET_SCHEMA_PATTERN.test(targetSchemaCode);
|
|
13
|
+
}
|
|
14
|
+
export function assertTicket08TargetSchema(targetSchemaCode) {
|
|
15
|
+
if (!isTicket08TargetSchema(targetSchemaCode))
|
|
16
|
+
throw new Error("Ticket 08 targetSchemaCode must be a host-scoped omc_t08_<16-hex> schema within the CloudPivot 8.6.26 26-character code contract.");
|
|
17
|
+
return targetSchemaCode;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=field-probe-schema.js.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表单脚手架契约(生产):默认表单 Title/系统行/Group 三行脚手架的构建与解码。
|
|
3
|
+
* 原实现住在 ticket11 测试编排内;C2-lite 拆分——生产消费者
|
|
4
|
+
* (formula-controls / childtable-sheet201 契约)与测试岛屿共用此唯一事实源。
|
|
5
|
+
*/
|
|
6
|
+
export const TICKET11_SCAFFOLD_SYSTEM_ROW = Object.freeze(["creater", "createdTime", "sequenceNo"]);
|
|
7
|
+
export const TICKET11_SCAFFOLD_SYSTEM_CONTROL_TYPES = Object.freeze({ creater: 101, createdTime: 104, sequenceNo: 100 });
|
|
8
|
+
/** The UID rule value is a designer/composer constant, not a per-model scheme reference (research §13.2). */
|
|
9
|
+
export const TICKET11_SCAFFOLD_SEQNO_UID_VALUE = "aa860e64ff2e4f86851a661d663ae358";
|
|
10
|
+
export const TICKET11_SCAFFOLD_GROUP_NAME = "业务标题";
|
|
11
|
+
export const TICKET11_SCAFFOLD_DATE_FORMAT = "YYYY-MM-DD HH:mm:ss";
|
|
12
|
+
/** One shared timestamp per composition mirrors the designer's Title<ts>/Group<ts> pattern (research §13.4). */
|
|
13
|
+
export function ticket11ScaffoldControlKeys(capturedAt) {
|
|
14
|
+
const epochMs = Date.parse(capturedAt);
|
|
15
|
+
if (!Number.isFinite(epochMs) || epochMs <= 0)
|
|
16
|
+
throw new Error("Ticket 11 scaffold composition requires the evidence capture clock; nothing ships on guessed layout keys.");
|
|
17
|
+
return Object.freeze({ titleKey: `Title${epochMs}`, groupKey: `Group${epochMs}` });
|
|
18
|
+
}
|
|
19
|
+
function deepFrozen(value) {
|
|
20
|
+
if (Array.isArray(value))
|
|
21
|
+
return Object.freeze(value.map(deepFrozen));
|
|
22
|
+
if (value && typeof value === "object")
|
|
23
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, deepFrozen(item)])));
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
/** 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. */
|
|
27
|
+
export function buildTicket11ScaffoldControls(input) {
|
|
28
|
+
return deepFrozen({
|
|
29
|
+
[input.titleKey]: {
|
|
30
|
+
key: input.titleKey,
|
|
31
|
+
type: 203,
|
|
32
|
+
options: {
|
|
33
|
+
name: input.formName,
|
|
34
|
+
name_i18n: { en: input.formName, zh: input.formName },
|
|
35
|
+
visible: true,
|
|
36
|
+
labelVisible: true,
|
|
37
|
+
span: 24,
|
|
38
|
+
labelAlign: "",
|
|
39
|
+
style: "",
|
|
40
|
+
tips: "",
|
|
41
|
+
dataItemName: "",
|
|
42
|
+
useDataTitle: true,
|
|
43
|
+
styleInfo: { height: 1, bgColor: "#ffffff", bgImg: "", textAlign: "center", fontSize: 28, textColor: "#111218", blod: true, italic: false, textCss: "", textCssSwitch: false },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
creater: {
|
|
47
|
+
key: "creater",
|
|
48
|
+
type: TICKET11_SCAFFOLD_SYSTEM_CONTROL_TYPES.creater,
|
|
49
|
+
options: { name: "创建人", name_i18n: "", visible: true, labelVisible: true, span: 8, labelAlign: "", style: "", tips: "", dataItemName: "", widgetType: "", dataItemType: "", mappings: "", verifyFormula: "", defaultValue: "" },
|
|
50
|
+
},
|
|
51
|
+
createdTime: {
|
|
52
|
+
key: "createdTime",
|
|
53
|
+
type: TICKET11_SCAFFOLD_SYSTEM_CONTROL_TYPES.createdTime,
|
|
54
|
+
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: "" },
|
|
55
|
+
},
|
|
56
|
+
sequenceNo: {
|
|
57
|
+
key: "sequenceNo",
|
|
58
|
+
type: TICKET11_SCAFFOLD_SYSTEM_CONTROL_TYPES.sequenceNo,
|
|
59
|
+
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: "" },
|
|
60
|
+
},
|
|
61
|
+
[input.groupKey]: {
|
|
62
|
+
key: input.groupKey,
|
|
63
|
+
type: 200,
|
|
64
|
+
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 },
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** An auto-created default form decodes with an empty draft layout (research §2): draftViewJson null/[] and draftAttributesJson null/{}. */
|
|
69
|
+
export function ticket11DraftLayoutIsEmpty(form) {
|
|
70
|
+
let view = null;
|
|
71
|
+
let attributes = null;
|
|
72
|
+
try {
|
|
73
|
+
view = form.draftViewJson === null ? [] : JSON.parse(form.draftViewJson);
|
|
74
|
+
attributes = form.draftAttributesJson === null ? {} : JSON.parse(form.draftAttributesJson);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return Array.isArray(view) && view.length === 0 && typeof attributes === "object" && attributes !== null && !Array.isArray(attributes) && Object.keys(attributes).length === 0;
|
|
80
|
+
}
|
|
81
|
+
export function decodeTicket11ScaffoldPresence(form) {
|
|
82
|
+
if (form.draftViewJson === null || form.draftAttributesJson === null)
|
|
83
|
+
return { decodable: false, rowOrderMatchesContract: false };
|
|
84
|
+
let view;
|
|
85
|
+
let attributes;
|
|
86
|
+
try {
|
|
87
|
+
view = JSON.parse(form.draftViewJson);
|
|
88
|
+
attributes = JSON.parse(form.draftAttributesJson);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return { decodable: false, rowOrderMatchesContract: false };
|
|
92
|
+
}
|
|
93
|
+
if (!Array.isArray(view) || view.some((row) => !Array.isArray(row) || row.some((key) => typeof key !== "string")) || !attributes || typeof attributes !== "object" || Array.isArray(attributes))
|
|
94
|
+
return { decodable: false, rowOrderMatchesContract: false };
|
|
95
|
+
const rows = view;
|
|
96
|
+
const map = attributes;
|
|
97
|
+
const entry = (key) => {
|
|
98
|
+
const value = map[key];
|
|
99
|
+
return value && typeof value === "object" && !Array.isArray(value) && typeof value.type === "number" && value.options && typeof value.options === "object"
|
|
100
|
+
? value
|
|
101
|
+
: undefined;
|
|
102
|
+
};
|
|
103
|
+
// The row contract is positional (research §13.5): row 0 is the single-key
|
|
104
|
+
// Title row, row 1 is exactly the system row, row 2 is the single-key Group
|
|
105
|
+
// row. Extra or duplicated scaffold rows decode as non-contract.
|
|
106
|
+
const isTitleKey = (key) => /^Title\d+$/.test(key);
|
|
107
|
+
const isGroupKey = (key) => /^Group\d+$/.test(key);
|
|
108
|
+
const titleShaped = rows.length >= 3 && rows[0].length === 1 && isTitleKey(rows[0][0]) && entry(rows[0][0]) !== undefined;
|
|
109
|
+
const systemShaped = rows.length >= 3 && rows[1].length === 3 && rows[1][0] === "creater" && rows[1][1] === "createdTime" && rows[1][2] === "sequenceNo";
|
|
110
|
+
const groupShaped = rows.length >= 3 && rows[2].length === 1 && isGroupKey(rows[2][0]) && entry(rows[2][0]) !== undefined;
|
|
111
|
+
if (!titleShaped || !systemShaped || !groupShaped)
|
|
112
|
+
return { decodable: false, rowOrderMatchesContract: false };
|
|
113
|
+
const title = entry(rows[0][0]);
|
|
114
|
+
const group = entry(rows[2][0]);
|
|
115
|
+
const systemTypes = rows[1].map((key) => entry(key)?.type ?? -1);
|
|
116
|
+
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"));
|
|
117
|
+
return Object.freeze({
|
|
118
|
+
decodable: true,
|
|
119
|
+
title: Object.freeze({ key: rows[0][0], type: title.type, useDataTitle: title.options.useDataTitle === true, name: title.options.name }),
|
|
120
|
+
systemRow: Object.freeze({ keys: Object.freeze([...rows[1]]), types: Object.freeze(systemTypes) }),
|
|
121
|
+
group: Object.freeze({ key: rows[2][0], type: group.type }),
|
|
122
|
+
rowOrder: Object.freeze(rows.map((row) => row.join("+"))),
|
|
123
|
+
rowOrderMatchesContract: scaffoldRowsOnly,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=form-scaffold-contract.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { decodeDraftControl } from "./cloudpivot-low-code.js";
|
|
2
2
|
import { buildFormulaFieldDeleteRequest } from "./formula-field-contract.js";
|
|
3
|
-
import { buildTicket11ScaffoldControls, decodeTicket11ScaffoldPresence, ticket11DraftLayoutIsEmpty, ticket11ScaffoldControlKeys } from "./
|
|
3
|
+
import { buildTicket11ScaffoldControls, decodeTicket11ScaffoldPresence, ticket11DraftLayoutIsEmpty, ticket11ScaffoldControlKeys } from "./form-scaffold-contract.js";
|
|
4
4
|
export const FORMULA_CONTROL_TYPE = 89;
|
|
5
5
|
export const FORMULA_FORM_DRAFT_ENDPOINT = "/api/api/app/bizsheet/update";
|
|
6
6
|
export const FORMULA_FORM_PUBLISH_ENDPOINT = "/api/api/app/bizsheet/publish";
|
package/package.json
CHANGED
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
// Ticket 16 (approval-readability issue 02): the conversational plan-of-record
|
|
2
|
-
// with a single master approval for the standard Ticket 08→12 composite chain.
|
|
3
|
-
//
|
|
4
|
-
// Phase-0 mechanism decisions (recorded in the issue file):
|
|
5
|
-
//
|
|
6
|
-
// 1. HOW ONE APPROVAL BINDS MANY PACKAGES. Later-stage packages cannot exist
|
|
7
|
-
// before earlier stages execute (each stage's evidence consumes the prior
|
|
8
|
-
// stage's completed, behavior-validated execution report; package hashes
|
|
9
|
-
// embed live baseline fingerprints), so literal pre-execution enumeration of
|
|
10
|
-
// all five package ids/hashes is impossible without weakening the serial
|
|
11
|
-
// evidence discipline. The master approval therefore binds the chain by its
|
|
12
|
-
// DETERMINISTIC DERIVED IDENTITY, pre-minted by the Host at compose time:
|
|
13
|
-
// one testRunId for the whole run plus the exact target identities
|
|
14
|
-
// (omc_t08_<16-hex> schema, omc_t09_<16-hex> field, default sheet), the
|
|
15
|
-
// routes, the pinned 8.6.26 capability contract hashes, the Knowledge Pack
|
|
16
|
-
// digests, the exact scopes, and the declared staged-cleanup contracts. The
|
|
17
|
-
// sha256 over that canonical enumeration is the single hash anchor carried
|
|
18
|
-
// by the master approval frame. Every concrete package must then match its
|
|
19
|
-
// enumerated slot EXACTLY to consume the grant, and each consumption binds
|
|
20
|
-
// the concrete packageId/packageHash into the master record — so the
|
|
21
|
-
// acceptance record shows the literal package id/hash per stage after the
|
|
22
|
-
// fact. A package that is not enumerated refuses the grant: it can only
|
|
23
|
-
// execute through its own per-package DSH approval frame (the unchanged
|
|
24
|
-
// legacy/emergency path).
|
|
25
|
-
//
|
|
26
|
-
// 2. PLAN-TEXT PROVENANCE. The conversational plan-of-record is HOST-DERIVED:
|
|
27
|
-
// it is rendered by this module from the immutable master plan record and
|
|
28
|
-
// surfaced as the omc_master_plan_compose tool output (visible in the DSH
|
|
29
|
-
// chat history). It is never accepted as an input anywhere; the approval
|
|
30
|
-
// binds to the record and its hash anchor, not to any displayed text, so
|
|
31
|
-
// model paraphrasing has no effect. The omc_master_plan_approve tool
|
|
32
|
-
// re-renders both the plan text and the approval frame from the record and
|
|
33
|
-
// byte-compares (verifyConversationalMasterPlan /
|
|
34
|
-
// verifyMasterApprovalFrameHumanBlock) — the binding lives in the
|
|
35
|
-
// structure, not in the display text.
|
|
36
|
-
//
|
|
37
|
-
// Ticket 17 (issue 03, grill 2026-09-05) — presentation-only format polish:
|
|
38
|
-
// the plan-of-record renders as the approved markdown table (| # | 操作 |
|
|
39
|
-
// 目标 | 说明 | with the staged cleanups as rows 6-7; boundary promises in
|
|
40
|
-
// one header line; scope strings and per-stage contract hashes only in the
|
|
41
|
-
// machine receipt below the plan text), with the approved compact-list
|
|
42
|
-
// fallback for plain-text tool-output rendering; the master approval frame
|
|
43
|
-
// is exactly three host-derived lines with the machine prefix deleted (new
|
|
44
|
-
// contract, no archived-replay obligation — the legacy per-stage frames stay
|
|
45
|
-
// byte-identical); stage results report one line per success with full
|
|
46
|
-
// verbatim disclosure on failure/blocked/uncertain. Governance mechanics are
|
|
47
|
-
// untouched.
|
|
48
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
49
|
-
import { assertMasterApprovalFrameHumanBlock, renderMasterApprovalFrame, verifyMasterApprovalFrameHumanBlock } from "./approval-frame-readability.js";
|
|
50
|
-
import { activeFieldFamily, fieldFamilyMembers } from "./families/registry.js";
|
|
51
|
-
/** The DSH tool that carries the single master approval frame. */
|
|
52
|
-
export const MASTER_APPROVAL_TOOL_NAME = "omc_master_plan_approve";
|
|
53
|
-
/**
|
|
54
|
-
* Byte-stable line prefix the maintainer runner matches for the single master
|
|
55
|
-
* frame (Ticket 17: the machine prefix line was deleted — the frame's first
|
|
56
|
-
* line IS the approval summary, so this prefix anchors it).
|
|
57
|
-
*/
|
|
58
|
-
export const MASTER_APPROVAL_REASON_PREFIX = "批准执行 ";
|
|
59
|
-
const HEX64 = /^[a-f0-9]{64}$/i;
|
|
60
|
-
const UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
61
|
-
const TARGET_SCHEMA = /^omc_t08_[0-9a-f]{16}$/;
|
|
62
|
-
const TARGET_FIELD = /^omc_t09_[0-9a-f]{16}$/;
|
|
63
|
-
const FOLDER_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{7,63}$/;
|
|
64
|
-
/**
|
|
65
|
-
* Composes the immutable master plan record: pre-mints the whole chain's
|
|
66
|
-
* derived identity (one testRunId, fixture schema/field codes, default sheet)
|
|
67
|
-
* and derives the single hash anchor over the canonical enumeration.
|
|
68
|
-
*/
|
|
69
|
-
export function composeMasterPlan(input) {
|
|
70
|
-
if (input.environmentName !== "test" || input.applicationCode !== "test")
|
|
71
|
-
throw new Error("The master delivery plan is restricted to the named test environment and its dedicated test application.");
|
|
72
|
-
if (!FOLDER_ID.test(input.folder.id) || typeof input.folder.name !== "string" || !input.folder.name.trim())
|
|
73
|
-
throw new Error("The master delivery plan requires the exact discovered folder identity.");
|
|
74
|
-
const testRunId = input.ids?.testRunId ?? randomUUID();
|
|
75
|
-
const targetSchemaCode = input.ids?.targetSchemaCode ?? `omc_t08_${randomUUID().replaceAll("-", "").slice(0, 16)}`;
|
|
76
|
-
// Ticket 25: stage 2 creates the ACTIVE family's typed fields (one code
|
|
77
|
-
// per registered member, in governed order) and every later stage binds the
|
|
78
|
-
// same ordered set.
|
|
79
|
-
const memberCount = fieldFamilyMembers().length;
|
|
80
|
-
const targetFieldCodes = input.ids?.targetFieldCodes ?? Array.from({ length: memberCount }, () => `omc_t09_${randomUUID().replaceAll("-", "").slice(0, 16)}`);
|
|
81
|
-
if (!UUID_V4.test(testRunId) || !TARGET_SCHEMA.test(targetSchemaCode) || targetFieldCodes.length !== memberCount || targetFieldCodes.some((code) => !TARGET_FIELD.test(code)) || new Set(targetFieldCodes).size !== memberCount)
|
|
82
|
-
throw new Error("The master delivery plan identities must be the host-scoped testRunId, omc_t08 schema code, and one distinct omc_t09 family field code per active family member.");
|
|
83
|
-
const stageOrder = ["ticket08-test-model-mutation", "ticket09-test-field-mutation", "ticket10-test-field-publish", "ticket11-test-form-draft", "ticket20-test-data-rule", "ticket12-test-form-publish"];
|
|
84
|
-
const humanCapabilities = {
|
|
85
|
-
"ticket08-test-model-mutation": "ticket08-model-create",
|
|
86
|
-
"ticket09-test-field-mutation": "ticket09-field-create",
|
|
87
|
-
"ticket10-test-field-publish": "ticket10-field-publish",
|
|
88
|
-
"ticket11-test-form-draft": "ticket11-form-draft",
|
|
89
|
-
"ticket20-test-data-rule": "ticket20-data-rule",
|
|
90
|
-
"ticket12-test-form-publish": "ticket12-form-publish",
|
|
91
|
-
};
|
|
92
|
-
const stages = stageOrder.map((route, index) => {
|
|
93
|
-
const pin = input.stagePins[route];
|
|
94
|
-
if (!pin || pin.route !== route || !pin.capabilityId || !HEX64.test(pin.contractHash) || !HEX64.test(pin.knowledgePackDigest))
|
|
95
|
-
throw new Error(`The master delivery plan requires the pinned capability, contract hash, and Knowledge Pack digest for ${route}.`);
|
|
96
|
-
const slot = {
|
|
97
|
-
stage: (index + 1),
|
|
98
|
-
route,
|
|
99
|
-
capabilityId: pin.capabilityId,
|
|
100
|
-
humanCapability: humanCapabilities[route],
|
|
101
|
-
targetSchemaCode,
|
|
102
|
-
targetFieldCodes: index >= 1 ? targetFieldCodes : undefined,
|
|
103
|
-
targetSheetCode: index >= 3 ? targetSchemaCode : undefined,
|
|
104
|
-
scope: index === 0 ? `cloudpivot:model:${targetSchemaCode}` : index <= 2 ? `cloudpivot:fields:${targetSchemaCode}/${targetFieldCodes.join("+")}` : index === 4 ? `cloudpivot:rule:${targetSchemaCode}/${targetFieldCodes[activeFieldFamily().ruleDemoTargetPosition - 1]}` : `cloudpivot:form:${targetSchemaCode}/${targetSchemaCode}`,
|
|
105
|
-
contractHash: pin.contractHash,
|
|
106
|
-
knowledgePackDigest: pin.knowledgePackDigest,
|
|
107
|
-
};
|
|
108
|
-
return slot;
|
|
109
|
-
});
|
|
110
|
-
const fieldCleanupPin = input.cleanupPins["ticket10-test-field-cleanup"];
|
|
111
|
-
const modelCleanupPin = input.cleanupPins["ticket08-test-model-cleanup"];
|
|
112
|
-
if (!fieldCleanupPin || !modelCleanupPin)
|
|
113
|
-
throw new Error("The master delivery plan requires both declared staged-cleanup pins.");
|
|
114
|
-
const cleanups = [
|
|
115
|
-
{ route: "ticket10-test-field-cleanup", followsStage: 3, capabilityId: fieldCleanupPin.capabilityId, contractHash: fieldCleanupPin.contractHash },
|
|
116
|
-
{ route: "ticket08-test-model-cleanup", followsStage: 1, capabilityId: modelCleanupPin.capabilityId, contractHash: modelCleanupPin.contractHash },
|
|
117
|
-
];
|
|
118
|
-
for (const cleanup of cleanups) {
|
|
119
|
-
if (!cleanup.capabilityId || !HEX64.test(cleanup.contractHash))
|
|
120
|
-
throw new Error(`The master delivery plan requires the pinned cleanup capability and contract hash for ${cleanup.route}.`);
|
|
121
|
-
}
|
|
122
|
-
const createdAt = (input.now ?? (() => new Date()))().toISOString();
|
|
123
|
-
const record = {
|
|
124
|
-
masterPlanId: `master-${createHash("sha256").update(`${createdAt}:${testRunId}:${targetSchemaCode}:${targetFieldCodes.join("+")}`).digest("hex").slice(0, 24)}`,
|
|
125
|
-
createdAt,
|
|
126
|
-
environmentName: input.environmentName,
|
|
127
|
-
applicationCode: input.applicationCode,
|
|
128
|
-
folder: { id: input.folder.id, name: input.folder.name },
|
|
129
|
-
testRunId,
|
|
130
|
-
stages,
|
|
131
|
-
cleanups,
|
|
132
|
-
masterHash: "",
|
|
133
|
-
status: "proposed",
|
|
134
|
-
consumed: [],
|
|
135
|
-
ledgerHash: "",
|
|
136
|
-
};
|
|
137
|
-
const sealed = { ...record, masterHash: masterPlanHash(record) };
|
|
138
|
-
return { ...sealed, ledgerHash: masterLedgerHash(sealed.approval, sealed.consumed) };
|
|
139
|
-
}
|
|
140
|
-
function canonical(value) {
|
|
141
|
-
return JSON.stringify(sort(value));
|
|
142
|
-
}
|
|
143
|
-
function sort(value) {
|
|
144
|
-
if (Array.isArray(value))
|
|
145
|
-
return value.map(sort);
|
|
146
|
-
if (value && typeof value === "object")
|
|
147
|
-
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, sort(item)]));
|
|
148
|
-
return value;
|
|
149
|
-
}
|
|
150
|
-
/** The hash anchor is computed over the canonical enumeration only — never over display text. */
|
|
151
|
-
function masterPlanHash(record) {
|
|
152
|
-
return createHash("sha256").update(canonical({
|
|
153
|
-
testRunId: record.testRunId,
|
|
154
|
-
folder: record.folder,
|
|
155
|
-
stages: record.stages.map((slot) => ({ stage: slot.stage, route: slot.route, capabilityId: slot.capabilityId, targetSchemaCode: slot.targetSchemaCode, targetFieldCodes: slot.targetFieldCodes ?? null, targetSheetCode: slot.targetSheetCode ?? null, scope: slot.scope, contractHash: slot.contractHash, knowledgePackDigest: slot.knowledgePackDigest })),
|
|
156
|
-
cleanups: record.cleanups.map((cleanup) => ({ route: cleanup.route, followsStage: cleanup.followsStage, capabilityId: cleanup.capabilityId, contractHash: cleanup.contractHash })),
|
|
157
|
-
})).digest("hex");
|
|
158
|
-
}
|
|
159
|
-
/** Integrity root over the approval binding plus the consumption ledger (the masterHash stays fixed at approval time). */
|
|
160
|
-
function masterLedgerHash(approval, consumed) {
|
|
161
|
-
return createHash("sha256").update(canonical({
|
|
162
|
-
approval: approval ? { callId: approval.callId, approvedAt: approval.approvedAt } : null,
|
|
163
|
-
consumed: consumed.map((item) => ({ kind: item.kind, route: item.route, stage: item.stage, planApprovalId: item.planApprovalId, packageId: item.packageId ?? null, packageHash: item.packageHash ?? null, consumedAt: item.consumedAt })),
|
|
164
|
-
})).digest("hex");
|
|
165
|
-
}
|
|
166
|
-
/** Re-derives both roots and fails any record whose enumeration or approval/consumption ledger diverges. */
|
|
167
|
-
export function verifyMasterPlanRecord(record) {
|
|
168
|
-
if (!record || !Array.isArray(record.stages) || record.stages.length !== 6 || !Array.isArray(record.cleanups) || !Array.isArray(record.consumed))
|
|
169
|
-
throw new Error("Master plan record is malformed.");
|
|
170
|
-
if (record.masterHash !== masterPlanHash(record))
|
|
171
|
-
throw new Error("Master plan record diverges from its own hash anchor.");
|
|
172
|
-
if (record.ledgerHash !== masterLedgerHash(record.approval, record.consumed))
|
|
173
|
-
throw new Error("Master plan record approval or consumption ledger diverges from its integrity root.");
|
|
174
|
-
if (!UUID_V4.test(record.testRunId))
|
|
175
|
-
throw new Error("Master plan record testRunId is malformed.");
|
|
176
|
-
if (record.status !== "proposed" && record.status !== "approved")
|
|
177
|
-
throw new Error("Master plan record status is malformed.");
|
|
178
|
-
if (record.status === "approved" && (!record.approval || !record.approval.callId || !Number.isFinite(Date.parse(record.approval.approvedAt))))
|
|
179
|
-
throw new Error("Approved master plan record is missing its approval binding.");
|
|
180
|
-
if (/(password|token|cookie|client[_-]?secret|authorization|api[_-]?key|private[_-]?key|bearer\s|-----begin|raw[_-]?(request|response|log))/i.test(canonical(record)))
|
|
181
|
-
throw new Error("Master plan record contains prohibited sensitive data.");
|
|
182
|
-
}
|
|
183
|
-
/** Marks the master plan approved under the one DSH approval call. */
|
|
184
|
-
export function approveMasterPlan(record, approval) {
|
|
185
|
-
verifyMasterPlanRecord(record);
|
|
186
|
-
if (record.status === "approved")
|
|
187
|
-
throw new Error("Master plan is already approved.");
|
|
188
|
-
if (!approval.callId)
|
|
189
|
-
throw new Error("Master plan approval requires the DSH call ID provenance.");
|
|
190
|
-
const approved = { ...record, status: "approved", approval: { callId: approval.callId, approvedAt: approval.approvedAt ?? new Date().toISOString() } };
|
|
191
|
-
if (approved.masterHash !== masterPlanHash(approved))
|
|
192
|
-
throw new Error("Approved master plan diverges from its hash anchor.");
|
|
193
|
-
return { ...approved, ledgerHash: masterLedgerHash(approved.approval, approved.consumed) };
|
|
194
|
-
}
|
|
195
|
-
function candidateMatchesSlot(slot, candidate) {
|
|
196
|
-
return slot.route === candidate.route
|
|
197
|
-
&& slot.targetSchemaCode === candidate.targetSchemaCode
|
|
198
|
-
&& (slot.targetFieldCodes ? slot.targetFieldCodes.join("+") : undefined) === (candidate.targetFieldCodes ? candidate.targetFieldCodes.join("+") : undefined)
|
|
199
|
-
&& (slot.targetSheetCode ?? undefined) === (candidate.targetSheetCode ?? undefined);
|
|
200
|
-
}
|
|
201
|
-
/** Exact plan-membership: an approved master plan's slot matches only the identical derived identity. */
|
|
202
|
-
export function matchMasterStageSlot(record, candidate) {
|
|
203
|
-
verifyMasterPlanRecord(record);
|
|
204
|
-
if (record.status !== "approved")
|
|
205
|
-
return undefined;
|
|
206
|
-
if (record.testRunId !== candidate.testRunId)
|
|
207
|
-
return undefined;
|
|
208
|
-
return record.stages.find((slot) => candidateMatchesSlot(slot, candidate));
|
|
209
|
-
}
|
|
210
|
-
/** Exact plan-membership for the declared staged cleanups (lineage: same testRunId + the followed stage's exact targets). */
|
|
211
|
-
export function matchMasterCleanupSlot(record, candidate) {
|
|
212
|
-
verifyMasterPlanRecord(record);
|
|
213
|
-
if (record.status !== "approved")
|
|
214
|
-
return undefined;
|
|
215
|
-
if (record.testRunId !== candidate.testRunId)
|
|
216
|
-
return undefined;
|
|
217
|
-
for (const cleanup of record.cleanups) {
|
|
218
|
-
if (cleanup.route !== candidate.route)
|
|
219
|
-
continue;
|
|
220
|
-
const slot = record.stages.find((stage) => stage.stage === cleanup.followsStage);
|
|
221
|
-
if (!slot)
|
|
222
|
-
continue;
|
|
223
|
-
if (slot.targetSchemaCode === candidate.targetSchemaCode
|
|
224
|
-
&& (slot.targetFieldCodes ? slot.targetFieldCodes.join("+") : undefined) === (candidate.targetFieldCodes ? candidate.targetFieldCodes.join("+") : undefined)
|
|
225
|
-
&& (slot.targetSheetCode === undefined || candidate.targetSheetCode === undefined || slot.targetSheetCode === candidate.targetSheetCode))
|
|
226
|
-
return { slot, cleanup };
|
|
227
|
-
}
|
|
228
|
-
return undefined;
|
|
229
|
-
}
|
|
230
|
-
/** Records one grant consumption; identical re-consumption is a no-op, divergent re-consumption is refused. */
|
|
231
|
-
export function recordMasterPlanConsumption(record, consumption) {
|
|
232
|
-
verifyMasterPlanRecord(record);
|
|
233
|
-
if (record.status !== "approved")
|
|
234
|
-
throw new Error("Master plan consumption requires the approved master plan.");
|
|
235
|
-
if (!consumption.planApprovalId || !Number.isFinite(Date.parse(consumption.consumedAt)))
|
|
236
|
-
throw new Error("Master plan consumption is malformed.");
|
|
237
|
-
if (consumption.packageHash && !HEX64.test(consumption.packageHash))
|
|
238
|
-
throw new Error("Master plan consumption package hash is malformed.");
|
|
239
|
-
const existing = record.consumed.find((item) => item.kind === consumption.kind && item.route === consumption.route && item.stage === consumption.stage);
|
|
240
|
-
if (existing) {
|
|
241
|
-
if (existing.planApprovalId !== consumption.planApprovalId || (existing.packageId ?? undefined) !== (consumption.packageId ?? undefined))
|
|
242
|
-
throw new Error("Master plan grant was already consumed by a different approval or package; operator reconcile is required.");
|
|
243
|
-
return record;
|
|
244
|
-
}
|
|
245
|
-
const consumed = [...record.consumed, consumption];
|
|
246
|
-
return { ...record, consumed, ledgerHash: masterLedgerHash(record.approval, consumed) };
|
|
247
|
-
}
|
|
248
|
-
/** The human block input derived from the immutable record — the only source both renderers share. */
|
|
249
|
-
export function masterApprovalHumanBlockInput(record) {
|
|
250
|
-
verifyMasterPlanRecord(record);
|
|
251
|
-
return {
|
|
252
|
-
stages: record.stages.map((slot) => ({
|
|
253
|
-
capability: slot.humanCapability,
|
|
254
|
-
identities: [
|
|
255
|
-
slot.targetSchemaCode,
|
|
256
|
-
...(slot.humanCapability === "ticket20-data-rule"
|
|
257
|
-
? [slot.targetFieldCodes?.[activeFieldFamily().ruleDemoTargetPosition - 1]]
|
|
258
|
-
: slot.targetFieldCodes ?? []),
|
|
259
|
-
...(slot.targetSheetCode && (slot.humanCapability === "ticket11-form-draft" || slot.humanCapability === "ticket12-form-publish") ? [slot.targetSheetCode] : []),
|
|
260
|
-
],
|
|
261
|
-
scope: slot.scope,
|
|
262
|
-
})),
|
|
263
|
-
cleanupCount: record.cleanups.length,
|
|
264
|
-
environment: record.environmentName,
|
|
265
|
-
application: record.applicationCode,
|
|
266
|
-
folderId: record.folder.id,
|
|
267
|
-
folderName: record.folder.name,
|
|
268
|
-
anchorHash: record.masterHash,
|
|
269
|
-
testRunId: record.testRunId,
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
// --- Ticket 17 (issue 03): the plan-of-record's approved reading shapes ------
|
|
273
|
-
/**
|
|
274
|
-
* Stage-0 render decision (grill decision 3): VERIFIED 2026-09-05 with
|
|
275
|
-
* screenshot evidence (research/stage0-broken-markdown-table*.png) that the
|
|
276
|
-
* DSH web UI renders tool-output text blocks as PLAIN TEXT — the markdown
|
|
277
|
-
* table's pipes and divider row appear verbatim inline (no table/grid
|
|
278
|
-
* element is created), so the table shape would ship broken. The plan
|
|
279
|
-
* therefore ships as the approved compact indented list (divider + indented
|
|
280
|
-
* `N 操作 目标 说明` lines). If a future DSH build renders markdown in tool
|
|
281
|
-
* outputs, flip this to "markdown-table" for the table shape.
|
|
282
|
-
*/
|
|
283
|
-
export const MASTER_PLAN_RENDER_MODE = "compact-list";
|
|
284
|
-
/** Reading-shortening helper: fixture codes keep a cross-checkable prefix; the full codes live in the machine receipt below the plan. */
|
|
285
|
-
function fixtureCodeDisplayPrefix(code) {
|
|
286
|
-
return `${code.slice(0, code.indexOf("_", 4) + 5)}…`;
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* ONE Host-owned registry per stage: the plan table/list row AND the stage
|
|
290
|
-
* result line both derive from it, so the two surfaces can never drift.
|
|
291
|
-
* Ticket 03b: the family-varying cells (the field-create row's short label
|
|
292
|
-
* and qualifiers, the form-draft row's control kinds, the rule row's demo
|
|
293
|
-
* target) derive from the ACTIVE family registration
|
|
294
|
-
* (src/families/<family>.ts) — a future family rung never edits this
|
|
295
|
-
* table.
|
|
296
|
-
*/
|
|
297
|
-
const FAMILY_PLAN_ROW = activeFieldFamily().planRow;
|
|
298
|
-
const FIELD_CREATE_NOTE = [FAMILY_PLAN_ROW.familyShortLabel, ...FAMILY_PLAN_ROW.fieldCreateQualifiers].join("·");
|
|
299
|
-
const FIELD_CREATE_DECODE = `${FAMILY_PLAN_ROW.familyShortLabel}·${FAMILY_PLAN_ROW.fieldSetLabel}`;
|
|
300
|
-
const FORM_DRAFT_NOTE = `脚手架+${FAMILY_PLAN_ROW.formDraftControlKinds}`;
|
|
301
|
-
const RULE_NOTE = `设计器规则面·${FAMILY_PLAN_ROW.ruleTargetLabel}`;
|
|
302
|
-
const MASTER_STAGE_ROWS = {
|
|
303
|
-
"ticket08-model-create": { operation: "创建模型", note: "LIST 测试夹具", decode: "LIST 测试夹具", readback: "权威回读通过" },
|
|
304
|
-
"ticket09-field-create": { operation: "创建字段", note: FIELD_CREATE_NOTE, decode: FIELD_CREATE_DECODE, readback: "两态读回通过" },
|
|
305
|
-
"ticket10-field-publish": { operation: "发布字段", note: `模型级发布·${FAMILY_PLAN_ROW.fieldSetLabel}`, decode: "模型级发布", readback: "两态读回通过" },
|
|
306
|
-
"ticket11-form-draft": { operation: "表单草稿", note: FORM_DRAFT_NOTE, decode: "脚手架+类型化控件", readback: "草稿保存·发布态不变" },
|
|
307
|
-
"ticket20-data-rule": { operation: "配置必填规则", note: RULE_NOTE, decode: "必填规则行(dataRuleType=4)", readback: "规则列表解码通过" },
|
|
308
|
-
"ticket12-form-publish": { operation: "发布表单", note: "模型级发布", decode: "模型级发布", readback: "发布历史解码通过" },
|
|
309
|
-
};
|
|
310
|
-
const MASTER_CLEANUP_ROWS = {
|
|
311
|
-
"ticket10-test-field-cleanup": { operation: "清理·删除字段", note: "自动,两态回读确认", decode: "自动", readback: "两态精确缺失" },
|
|
312
|
-
"ticket08-test-model-cleanup": { operation: "清理·删除模型", note: "自动,级联+精确缺失", decode: "自动", readback: "级联+精确缺失" },
|
|
313
|
-
};
|
|
314
|
-
function masterStageRow(capability) {
|
|
315
|
-
const row = MASTER_STAGE_ROWS[capability];
|
|
316
|
-
if (!row)
|
|
317
|
-
throw new Error(`The conversational plan has no Host-owned stage row for ${capability}.`);
|
|
318
|
-
return row;
|
|
319
|
-
}
|
|
320
|
-
/** The fixed step sequence of the authorized chain: stages 1-6 then the two declared cleanups. */
|
|
321
|
-
const MASTER_RESULT_SEQUENCE = [
|
|
322
|
-
...Object.values(MASTER_STAGE_ROWS),
|
|
323
|
-
MASTER_CLEANUP_ROWS["ticket10-test-field-cleanup"],
|
|
324
|
-
MASTER_CLEANUP_ROWS["ticket08-test-model-cleanup"],
|
|
325
|
-
];
|
|
326
|
-
/** The eight step labels, exported for the maintainer runner's prompt legend (single source with the plan rows). */
|
|
327
|
-
export const MASTER_STAGE_RESULT_TITLES = MASTER_RESULT_SEQUENCE.map((row) => row.operation);
|
|
328
|
-
/** Derives the eight approved plan rows from the immutable record (host-owned fixed texts + truncated codes). */
|
|
329
|
-
function masterPlanTableRows(record) {
|
|
330
|
-
const rows = [];
|
|
331
|
-
record.stages.forEach((slot, index) => {
|
|
332
|
-
const row = masterStageRow(slot.humanCapability);
|
|
333
|
-
rows.push({
|
|
334
|
-
number: index + 1,
|
|
335
|
-
operation: row.operation,
|
|
336
|
-
target: index === 0 ? fixtureCodeDisplayPrefix(slot.targetSchemaCode) : index === 1 ? FAMILY_PLAN_ROW.fieldSetLabel : index === 2 ? `同上${FAMILY_PLAN_ROW.fieldSetLabel}` : index === 4 ? `同上${FAMILY_PLAN_ROW.fieldSetLabel}` : "默认表单",
|
|
337
|
-
note: row.note,
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
const fieldCleanup = record.cleanups.find((cleanup) => cleanup.route === "ticket10-test-field-cleanup");
|
|
341
|
-
const modelCleanup = record.cleanups.find((cleanup) => cleanup.route === "ticket08-test-model-cleanup");
|
|
342
|
-
const fieldCode = record.stages[1].targetFieldCodes?.[0] ?? "";
|
|
343
|
-
const schemaCode = record.stages[0].targetSchemaCode;
|
|
344
|
-
if (fieldCleanup)
|
|
345
|
-
rows.push({ number: 7, operation: MASTER_CLEANUP_ROWS[fieldCleanup.route].operation, target: FAMILY_PLAN_ROW.fieldSetLabel, note: MASTER_CLEANUP_ROWS[fieldCleanup.route].note });
|
|
346
|
-
if (modelCleanup)
|
|
347
|
-
rows.push({ number: 8, operation: MASTER_CLEANUP_ROWS[modelCleanup.route].operation, target: fixtureCodeDisplayPrefix(schemaCode), note: MASTER_CLEANUP_ROWS[modelCleanup.route].note });
|
|
348
|
-
return rows;
|
|
349
|
-
}
|
|
350
|
-
/** Renders the conversational plan-of-record displayed in the DSH chat (host output; never an input). */
|
|
351
|
-
export function renderConversationalMasterPlan(record) {
|
|
352
|
-
verifyMasterPlanRecord(record);
|
|
353
|
-
const header = [
|
|
354
|
-
`交付方案 · ${record.environmentName} 环境 · ${record.folder.name} · 方案编号 ${record.masterPlanId} · testRunId ${record.testRunId.slice(0, 8)}…`,
|
|
355
|
-
"全程绝不 app publish、绝不 Environment release;结果逐档在对话内汇报",
|
|
356
|
-
];
|
|
357
|
-
const rows = masterPlanTableRows(record);
|
|
358
|
-
if (MASTER_PLAN_RENDER_MODE === "markdown-table") {
|
|
359
|
-
return [
|
|
360
|
-
...header,
|
|
361
|
-
"",
|
|
362
|
-
"| # | 操作 | 目标 | 说明 |",
|
|
363
|
-
"|---|------|------|------|",
|
|
364
|
-
...rows.map((row) => `| ${row.number} | ${row.operation} | ${row.target} | ${row.note} |`),
|
|
365
|
-
].join("\n");
|
|
366
|
-
}
|
|
367
|
-
return [
|
|
368
|
-
...header,
|
|
369
|
-
"──────────────────────────────",
|
|
370
|
-
...rows.map((row) => ` ${row.number} ${row.operation} ${row.target} ${row.note}`),
|
|
371
|
-
].join("\n");
|
|
372
|
-
}
|
|
373
|
-
/** Byte-exact gate for plan text surfaced in the conversation (mismatch is rejected). */
|
|
374
|
-
export function verifyConversationalMasterPlan(text, record) {
|
|
375
|
-
if (typeof text !== "string" || text !== renderConversationalMasterPlan(record))
|
|
376
|
-
throw new Error("Conversational plan text diverges from the immutable master plan record.");
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* The single master approval frame: exactly three host-derived lines (summary
|
|
380
|
-
* / impact+boundary / anchor+guidance). The machine prefix was deleted
|
|
381
|
-
* (Ticket 17, grill decision 6) — the binding is structural (this approve
|
|
382
|
-
* tool call plus the plan hash sealed in the record), so the frame is pure
|
|
383
|
-
* display; the 8-hex anchor prefix stays for human cross-check.
|
|
384
|
-
*/
|
|
385
|
-
export function formatMasterPlanApprovalFrame(record) {
|
|
386
|
-
verifyMasterPlanRecord(record);
|
|
387
|
-
if (record.status !== "proposed")
|
|
388
|
-
throw new Error("The master approval frame is rendered from a proposed master plan record.");
|
|
389
|
-
const input = masterApprovalHumanBlockInput(record);
|
|
390
|
-
const frame = renderMasterApprovalFrame(input);
|
|
391
|
-
assertMasterApprovalFrameHumanBlock(frame);
|
|
392
|
-
verifyMasterApprovalFrameHumanBlock(frame, input);
|
|
393
|
-
if (/(payload|credential|password|token|authorization|api[_-]?key|private[_-]?key|raw[_-]?(request|response|log))/i.test(frame))
|
|
394
|
-
throw new Error("Master approval frame contains prohibited sensitive data.");
|
|
395
|
-
return frame;
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
|
-
* The machine receipt: the full binding payload (identities, scopes, per-stage
|
|
399
|
-
* contract hashes, cleanup declarations, consumption ledger) that used to ride
|
|
400
|
-
* as a JSON tail on the compose tool output. Ticket 19 folds the
|
|
401
|
-
* plan-json-tail-to-receipt backlog seed: the compose surface renders the
|
|
402
|
-
* plan-of-record list ONLY, and this receipt travels on the master approval
|
|
403
|
-
* result (and the acceptance report). The binding stays structural — the
|
|
404
|
-
* approve tool call plus the record's hash anchor — regardless of where the
|
|
405
|
-
* JSON renders.
|
|
406
|
-
*/
|
|
407
|
-
export function masterPlanReceipt(record) {
|
|
408
|
-
verifyMasterPlanRecord(record);
|
|
409
|
-
return {
|
|
410
|
-
masterPlanId: record.masterPlanId,
|
|
411
|
-
masterHash: record.masterHash,
|
|
412
|
-
status: record.status,
|
|
413
|
-
testRunId: record.testRunId,
|
|
414
|
-
environment: record.environmentName,
|
|
415
|
-
application: record.applicationCode,
|
|
416
|
-
folder: record.folder,
|
|
417
|
-
targetSchemaCode: record.stages[0].targetSchemaCode,
|
|
418
|
-
targetFieldCodes: record.stages[1].targetFieldCodes,
|
|
419
|
-
targetSheetCode: record.stages[3].targetSheetCode,
|
|
420
|
-
stages: record.stages.map((slot) => ({ stage: slot.stage, route: slot.route, capabilityId: slot.capabilityId, scope: slot.scope, contractHash: slot.contractHash, knowledgePackDigest: slot.knowledgePackDigest })),
|
|
421
|
-
cleanups: record.cleanups.map((cleanup) => ({ route: cleanup.route, followsStage: cleanup.followsStage, capabilityId: cleanup.capabilityId, contractHash: cleanup.contractHash })),
|
|
422
|
-
...(record.approval ? { approval: record.approval } : {}),
|
|
423
|
-
consumed: record.consumed,
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Grill decision 7: a successful stage reports exactly one line
|
|
428
|
-
* (✓ n/8 操作 · 关键解码 · 回读通过); a failed/blocked/uncertain stage
|
|
429
|
-
* reports the ✗ headline AND returns the complete result for verbatim
|
|
430
|
-
* disclosure. The operation/decode/readback texts derive from the same
|
|
431
|
-
* Host-owned row registry as the plan rows, so the plan and the result
|
|
432
|
-
* lines can never drift.
|
|
433
|
-
*/
|
|
434
|
-
export function summarizeMasterStageResult(stage, result) {
|
|
435
|
-
const row = MASTER_RESULT_SEQUENCE[stage - 1];
|
|
436
|
-
const value = (result ?? {});
|
|
437
|
-
const ok = value.status === "completed" && (!value.behavior || value.behavior.status === undefined || value.behavior.status === "validated");
|
|
438
|
-
if (ok)
|
|
439
|
-
return { ok: true, line: `✓ ${stage}/8 ${row.operation} · ${row.decode} · ${row.readback}` };
|
|
440
|
-
const shortReason = typeof value.status === "string" && value.status ? value.status : "结果异常";
|
|
441
|
-
return { ok: false, line: `✗ ${stage}/8 ${row.operation} · ${shortReason} → 完整结果与停工说明全文贴出`, fullDisclosure: JSON.stringify(result ?? null, null, 2) };
|
|
442
|
-
}
|
|
443
|
-
//# sourceMappingURL=master-plan-approval.js.map
|