@tiangong-lca/cli 0.0.24 → 0.0.26
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/README.md +66 -5
- package/dist/src/cli.js +484 -5
- package/dist/src/cli.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-alias-request.js +99 -0
- package/dist/src/lib/dataset-maintenance-alias-request.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-apply.js +12 -367
- package/dist/src/lib/dataset-maintenance-apply.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-protected-artifacts.js +100 -0
- package/dist/src/lib/dataset-maintenance-protected-artifacts.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-protected-before.js +284 -0
- package/dist/src/lib/dataset-maintenance-protected-before.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-protected-contract.js +918 -0
- package/dist/src/lib/dataset-maintenance-protected-contract.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-protected-freeze.js +230 -0
- package/dist/src/lib/dataset-maintenance-protected-freeze.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-protected-preparation.js +524 -0
- package/dist/src/lib/dataset-maintenance-protected-preparation.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-protected-run.js +667 -0
- package/dist/src/lib/dataset-maintenance-protected-run.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-protected-seal.js +160 -0
- package/dist/src/lib/dataset-maintenance-protected-seal.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-protected-toolchain.js +86 -0
- package/dist/src/lib/dataset-maintenance-protected-toolchain.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-protected-verify.js +435 -0
- package/dist/src/lib/dataset-maintenance-protected-verify.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-remote.js +116 -2
- package/dist/src/lib/dataset-maintenance-remote.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
import { CliError } from './errors.js';
|
|
2
|
+
import { isJsonObject, sha256Json, sha256Text, } from './dataset-maintenance-contract.js';
|
|
3
|
+
export const PROTECTED_EXECUTION_CONTRACT = {
|
|
4
|
+
freeze_schema: 'dataset-alias-execution-freeze.v1',
|
|
5
|
+
approval_schema: 'dataset-alias-execution-approval.v1',
|
|
6
|
+
preflight_request_schema: 'dataset-alias-execution-preflight.v1',
|
|
7
|
+
preflight_response_schema: 'dataset-alias-execution-preflight-proof.v1',
|
|
8
|
+
gate_response_schema: 'dataset-alias-execution-gate-receipt.v1',
|
|
9
|
+
admit_request_schema: 'dataset-alias-execution-admit.v1',
|
|
10
|
+
admit_response_schema: 'dataset-alias-execution-admit.v1',
|
|
11
|
+
status_response_schema: 'dataset-alias-execution-status.v1',
|
|
12
|
+
terminal_proof_schema: 'dataset-alias-execution-terminal-proof.v1',
|
|
13
|
+
marker_schema: 'dataset-alias-execution-attempt.v1',
|
|
14
|
+
report_schema: 'dataset-alias-execution-report.v1',
|
|
15
|
+
preflight_command: 'cmd_dataset_alias_execution_preflight_guarded',
|
|
16
|
+
gate_command: 'cmd_dataset_alias_execution_gate_guarded',
|
|
17
|
+
admit_command: 'cmd_dataset_alias_execution_admit_guarded',
|
|
18
|
+
read_command: 'cmd_dataset_alias_execution_read',
|
|
19
|
+
};
|
|
20
|
+
export const PROTECTED_EXECUTION_COUNTS = {
|
|
21
|
+
action_count: 52,
|
|
22
|
+
batch_count: 2,
|
|
23
|
+
exchange_count: 59,
|
|
24
|
+
amount_field_count: 118,
|
|
25
|
+
unrelated_exchange_count: 309,
|
|
26
|
+
audit_count: 55,
|
|
27
|
+
flowproperty_count: 2,
|
|
28
|
+
flow_count: 23,
|
|
29
|
+
process_count: 27,
|
|
30
|
+
derivative_target_count: 50,
|
|
31
|
+
};
|
|
32
|
+
const SHA256 = /^[a-f0-9]{64}$/u;
|
|
33
|
+
const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/iu;
|
|
34
|
+
const VERSION = /^[0-9]{2}\.[0-9]{2}\.[0-9]{3}$/u;
|
|
35
|
+
function fail(message) {
|
|
36
|
+
throw new CliError(message, {
|
|
37
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_CONTRACT_INVALID',
|
|
38
|
+
exitCode: 2,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function token(value, label) {
|
|
42
|
+
if (typeof value !== 'string' || !value.trim())
|
|
43
|
+
fail(`${label} must be a non-empty string.`);
|
|
44
|
+
return value.trim();
|
|
45
|
+
}
|
|
46
|
+
function hash(value, label) {
|
|
47
|
+
const result = token(value, label);
|
|
48
|
+
if (!SHA256.test(result))
|
|
49
|
+
fail(`${label} must be a lowercase SHA-256 digest.`);
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
function timestamp(value, label) {
|
|
53
|
+
const result = token(value, label);
|
|
54
|
+
if (!Number.isFinite(Date.parse(result)))
|
|
55
|
+
fail(`${label} must be an ISO timestamp.`);
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
function nullableHash(value, label) {
|
|
59
|
+
return value === null ? null : hash(value, label);
|
|
60
|
+
}
|
|
61
|
+
function nullableTimestamp(value, label) {
|
|
62
|
+
return value === null ? null : timestamp(value, label);
|
|
63
|
+
}
|
|
64
|
+
function parseAccount(value, label) {
|
|
65
|
+
if (!isJsonObject(value))
|
|
66
|
+
fail(`${label} must be an object.`);
|
|
67
|
+
return {
|
|
68
|
+
user_id: token(value.user_id, `${label}.user_id`),
|
|
69
|
+
email: token(value.email, `${label}.email`),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function parseExpected(value) {
|
|
73
|
+
if (!isJsonObject(value))
|
|
74
|
+
fail('expected must be an object.');
|
|
75
|
+
for (const [key, expected] of Object.entries(PROTECTED_EXECUTION_COUNTS)) {
|
|
76
|
+
if (value[key] !== expected)
|
|
77
|
+
fail(`expected.${key} must equal ${expected}.`);
|
|
78
|
+
}
|
|
79
|
+
return PROTECTED_EXECUTION_COUNTS;
|
|
80
|
+
}
|
|
81
|
+
function parseGateExpectations(value) {
|
|
82
|
+
if (!isJsonObject(value))
|
|
83
|
+
fail('gate_expectations must be an object.');
|
|
84
|
+
return {
|
|
85
|
+
primary_support_plan_sha256: hash(value.primary_support_plan_sha256, 'gate_expectations.primary_support_plan_sha256'),
|
|
86
|
+
execution_unused_sha256: hash(value.execution_unused_sha256, 'gate_expectations.execution_unused_sha256'),
|
|
87
|
+
derivative_quiescence_sha256: hash(value.derivative_quiescence_sha256, 'gate_expectations.derivative_quiescence_sha256'),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function parseDerivativeTarget(value, index) {
|
|
91
|
+
const label = `derivative_targets[${index}]`;
|
|
92
|
+
if (!isJsonObject(value))
|
|
93
|
+
fail(`${label} must be an object.`);
|
|
94
|
+
if (value.table !== 'flows' && value.table !== 'processes') {
|
|
95
|
+
fail(`${label}.table must be flows or processes.`);
|
|
96
|
+
}
|
|
97
|
+
if (value.state_code !== 0)
|
|
98
|
+
fail(`${label}.state_code must equal 0.`);
|
|
99
|
+
const id = token(value.id, `${label}.id`);
|
|
100
|
+
const version = token(value.version, `${label}.version`);
|
|
101
|
+
if (!UUID.test(id) || !VERSION.test(version)) {
|
|
102
|
+
fail(`${label} must contain a UUID id and canonical version.`);
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
table: value.table,
|
|
106
|
+
id,
|
|
107
|
+
version,
|
|
108
|
+
user_id: token(value.user_id, `${label}.user_id`),
|
|
109
|
+
state_code: 0,
|
|
110
|
+
baseline_snapshot_sha256: hash(value.baseline_snapshot_sha256, `${label}.baseline_snapshot_sha256`),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function parseDerivativeTargets(value) {
|
|
114
|
+
if (!Array.isArray(value))
|
|
115
|
+
fail('derivative_targets must be an array.');
|
|
116
|
+
const targets = value.map(parseDerivativeTarget);
|
|
117
|
+
const keys = targets.map((target) => `${target.table}\u0000${target.id}\u0000${target.version}`);
|
|
118
|
+
if (targets.length !== PROTECTED_EXECUTION_COUNTS.derivative_target_count ||
|
|
119
|
+
new Set(keys).size !== targets.length ||
|
|
120
|
+
targets.filter((target) => target.table === 'flows').length !==
|
|
121
|
+
PROTECTED_EXECUTION_COUNTS.flow_count ||
|
|
122
|
+
targets.filter((target) => target.table === 'processes').length !==
|
|
123
|
+
PROTECTED_EXECUTION_COUNTS.process_count) {
|
|
124
|
+
fail('derivative_targets must contain exactly 23 flows and 27 processes with no duplicates.');
|
|
125
|
+
}
|
|
126
|
+
const sorted = [...targets].sort((left, right) => `${left.table}\u0000${left.id}\u0000${left.version}`.localeCompare(`${right.table}\u0000${right.id}\u0000${right.version}`));
|
|
127
|
+
if (sorted.some((target, index) => target !== targets[index])) {
|
|
128
|
+
fail('derivative_targets must use stable table/id/version order.');
|
|
129
|
+
}
|
|
130
|
+
return targets;
|
|
131
|
+
}
|
|
132
|
+
export function parseProtectedDerivativeSnapshot(value, expected) {
|
|
133
|
+
if (!isJsonObject(value) ||
|
|
134
|
+
value.ok !== true ||
|
|
135
|
+
value.command !== 'cmd_dataset_derivative_rebuild_snapshot' ||
|
|
136
|
+
value.schema_version !== 'dataset-derivative-snapshot.v1' ||
|
|
137
|
+
value.table !== expected.table ||
|
|
138
|
+
value.id !== expected.id ||
|
|
139
|
+
value.version !== expected.version ||
|
|
140
|
+
value.user_id !== expected.userId ||
|
|
141
|
+
value.state_code !== 0) {
|
|
142
|
+
fail('Derivative snapshot RPC returned a foreign or unsupported snapshot.');
|
|
143
|
+
}
|
|
144
|
+
const snapshot = {
|
|
145
|
+
schema_version: 'dataset-derivative-snapshot.v1',
|
|
146
|
+
table: expected.table,
|
|
147
|
+
id: expected.id,
|
|
148
|
+
version: expected.version,
|
|
149
|
+
user_id: expected.userId,
|
|
150
|
+
state_code: 0,
|
|
151
|
+
modified_at: timestamp(value.modified_at, 'derivative_snapshot.modified_at'),
|
|
152
|
+
json_sha256: hash(value.json_sha256, 'derivative_snapshot.json_sha256'),
|
|
153
|
+
json_ordered_sha256: hash(value.json_ordered_sha256, 'derivative_snapshot.json_ordered_sha256'),
|
|
154
|
+
extracted_text_sha256: hash(value.extracted_text_sha256, 'derivative_snapshot.extracted_text_sha256'),
|
|
155
|
+
extracted_md_sha256: nullableHash(value.extracted_md_sha256, 'derivative_snapshot.extracted_md_sha256'),
|
|
156
|
+
embedding_ft_sha256: nullableHash(value.embedding_ft_sha256, 'derivative_snapshot.embedding_ft_sha256'),
|
|
157
|
+
embedding_ft_at: nullableTimestamp(value.embedding_ft_at, 'derivative_snapshot.embedding_ft_at'),
|
|
158
|
+
snapshot_sha256: hash(value.snapshot_sha256, 'derivative_snapshot.snapshot_sha256'),
|
|
159
|
+
};
|
|
160
|
+
if (snapshot.json_sha256 !== snapshot.json_ordered_sha256) {
|
|
161
|
+
fail('Derivative snapshot json and json_ordered hashes are inconsistent.');
|
|
162
|
+
}
|
|
163
|
+
return snapshot;
|
|
164
|
+
}
|
|
165
|
+
function parseFreezeSets(value) {
|
|
166
|
+
if (!isJsonObject(value))
|
|
167
|
+
fail('sets must be an object.');
|
|
168
|
+
return {
|
|
169
|
+
alias_plan_request_sha256: hash(value.alias_plan_request_sha256, 'sets.alias_plan_request_sha256'),
|
|
170
|
+
before_hash_set_sha256: hash(value.before_hash_set_sha256, 'sets.before_hash_set_sha256'),
|
|
171
|
+
desired_hash_set_sha256: hash(value.desired_hash_set_sha256, 'sets.desired_hash_set_sha256'),
|
|
172
|
+
exchange_rewrite_set_sha256: hash(value.exchange_rewrite_set_sha256, 'sets.exchange_rewrite_set_sha256'),
|
|
173
|
+
support_snapshot_set_sha256: hash(value.support_snapshot_set_sha256, 'sets.support_snapshot_set_sha256'),
|
|
174
|
+
derivative_baseline_set_sha256: hash(value.derivative_baseline_set_sha256, 'sets.derivative_baseline_set_sha256'),
|
|
175
|
+
derivative_target_set_sha256: hash(value.derivative_target_set_sha256, 'sets.derivative_target_set_sha256'),
|
|
176
|
+
toolchain_evidence_sha256: hash(value.toolchain_evidence_sha256, 'sets.toolchain_evidence_sha256'),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
export function computeProtectedFreezeSha256(freeze) {
|
|
180
|
+
return sha256Json({ ...freeze, freeze_sha256: '' });
|
|
181
|
+
}
|
|
182
|
+
export function parseProtectedFreeze(value) {
|
|
183
|
+
if (!isJsonObject(value) ||
|
|
184
|
+
value.schema_version !== PROTECTED_EXECUTION_CONTRACT.freeze_schema ||
|
|
185
|
+
value.environment !== 'production' ||
|
|
186
|
+
value.target_visibility !== 'owner_draft' ||
|
|
187
|
+
!isJsonObject(value.plan) ||
|
|
188
|
+
!isJsonObject(value.policy)) {
|
|
189
|
+
fail(`Freeze must use ${PROTECTED_EXECUTION_CONTRACT.freeze_schema} for production owner_draft.`);
|
|
190
|
+
}
|
|
191
|
+
const targets = parseDerivativeTargets(value.derivative_targets);
|
|
192
|
+
const account = parseAccount(value.account, 'account');
|
|
193
|
+
if (targets.some((target) => target.user_id !== account.user_id)) {
|
|
194
|
+
fail('Every derivative target must belong to the frozen account.');
|
|
195
|
+
}
|
|
196
|
+
const freeze = {
|
|
197
|
+
schema_version: PROTECTED_EXECUTION_CONTRACT.freeze_schema,
|
|
198
|
+
environment: 'production',
|
|
199
|
+
project_ref: token(value.project_ref, 'project_ref'),
|
|
200
|
+
account,
|
|
201
|
+
target_visibility: 'owner_draft',
|
|
202
|
+
plan: {
|
|
203
|
+
plan_file_sha256: hash(value.plan.plan_file_sha256, 'plan.plan_file_sha256'),
|
|
204
|
+
plan_sha256: hash(value.plan.plan_sha256, 'plan.plan_sha256'),
|
|
205
|
+
operation_id: token(value.plan.operation_id, 'plan.operation_id'),
|
|
206
|
+
},
|
|
207
|
+
sets: parseFreezeSets(value.sets),
|
|
208
|
+
expected: parseExpected(value.expected),
|
|
209
|
+
derivative_targets: targets,
|
|
210
|
+
policy: {
|
|
211
|
+
state_code_changes: value.policy.state_code_changes,
|
|
212
|
+
save_draft: value.policy.save_draft,
|
|
213
|
+
deletes: value.policy.deletes,
|
|
214
|
+
rebuild_derivatives: value.policy.rebuild_derivatives,
|
|
215
|
+
unitgroup_actions: value.policy.unitgroup_actions,
|
|
216
|
+
person_distance_actions: value.policy.person_distance_actions,
|
|
217
|
+
max_admit_posts: value.policy.max_admit_posts,
|
|
218
|
+
automatic_retry: value.policy.automatic_retry,
|
|
219
|
+
},
|
|
220
|
+
freeze_sha256: hash(value.freeze_sha256, 'freeze_sha256'),
|
|
221
|
+
};
|
|
222
|
+
if (freeze.policy.state_code_changes !== 0 ||
|
|
223
|
+
freeze.policy.save_draft !== 0 ||
|
|
224
|
+
freeze.policy.deletes !== 0 ||
|
|
225
|
+
freeze.policy.rebuild_derivatives !== 0 ||
|
|
226
|
+
freeze.policy.unitgroup_actions !== 0 ||
|
|
227
|
+
freeze.policy.person_distance_actions !== 0 ||
|
|
228
|
+
freeze.policy.max_admit_posts !== 1 ||
|
|
229
|
+
freeze.policy.automatic_retry !== false) {
|
|
230
|
+
fail('Freeze policy permits an operation outside the one-shot owner-draft alias execution.');
|
|
231
|
+
}
|
|
232
|
+
if (computeProtectedFreezeSha256(freeze) !== freeze.freeze_sha256) {
|
|
233
|
+
fail('freeze_sha256 does not match the canonical freeze contents.');
|
|
234
|
+
}
|
|
235
|
+
return freeze;
|
|
236
|
+
}
|
|
237
|
+
export function computeProtectedApprovalIdentitySha256(approval) {
|
|
238
|
+
return sha256Json({ ...approval, approval_identity_sha256: '' });
|
|
239
|
+
}
|
|
240
|
+
export function parseProtectedApproval(value) {
|
|
241
|
+
if (!isJsonObject(value) ||
|
|
242
|
+
value.schema_version !== PROTECTED_EXECUTION_CONTRACT.approval_schema ||
|
|
243
|
+
value.environment !== 'production' ||
|
|
244
|
+
value.target_visibility !== 'owner_draft') {
|
|
245
|
+
fail(`Approval must use ${PROTECTED_EXECUTION_CONTRACT.approval_schema} for production owner_draft.`);
|
|
246
|
+
}
|
|
247
|
+
const approval = {
|
|
248
|
+
schema_version: PROTECTED_EXECUTION_CONTRACT.approval_schema,
|
|
249
|
+
approved_at_utc: timestamp(value.approved_at_utc, 'approved_at_utc'),
|
|
250
|
+
environment: 'production',
|
|
251
|
+
project_ref: token(value.project_ref, 'project_ref'),
|
|
252
|
+
account: parseAccount(value.account, 'account'),
|
|
253
|
+
target_visibility: 'owner_draft',
|
|
254
|
+
plan_sha256: hash(value.plan_sha256, 'plan_sha256'),
|
|
255
|
+
operation_id: token(value.operation_id, 'operation_id'),
|
|
256
|
+
plan_file_sha256: hash(value.plan_file_sha256, 'plan_file_sha256'),
|
|
257
|
+
freeze_file_sha256: hash(value.freeze_file_sha256, 'freeze_file_sha256'),
|
|
258
|
+
freeze_sha256: hash(value.freeze_sha256, 'freeze_sha256'),
|
|
259
|
+
approval_text_sha256: hash(value.approval_text_sha256, 'approval_text_sha256'),
|
|
260
|
+
max_admit_posts: value.max_admit_posts,
|
|
261
|
+
automatic_retry: value.automatic_retry,
|
|
262
|
+
approval_identity_sha256: hash(value.approval_identity_sha256, 'approval_identity_sha256'),
|
|
263
|
+
};
|
|
264
|
+
if (approval.max_admit_posts !== 1 || approval.automatic_retry !== false) {
|
|
265
|
+
fail('Approval must authorize exactly one admit POST with no automatic retry.');
|
|
266
|
+
}
|
|
267
|
+
if (computeProtectedApprovalIdentitySha256(approval) !== approval.approval_identity_sha256) {
|
|
268
|
+
fail('approval_identity_sha256 does not match the canonical approval contents.');
|
|
269
|
+
}
|
|
270
|
+
return approval;
|
|
271
|
+
}
|
|
272
|
+
export function protectedPlanSetHashes(plan) {
|
|
273
|
+
const orderedActions = [...plan.actions].sort((left, right) => left.action_id.localeCompare(right.action_id));
|
|
274
|
+
const derivativeTargets = plan.actions
|
|
275
|
+
.filter((action) => action.table === 'flows' || action.table === 'processes')
|
|
276
|
+
.sort((left, right) => left.table.localeCompare(right.table) ||
|
|
277
|
+
left.id.localeCompare(right.id) ||
|
|
278
|
+
left.version.localeCompare(right.version))
|
|
279
|
+
.map((action) => ({
|
|
280
|
+
table: action.table,
|
|
281
|
+
id: action.id,
|
|
282
|
+
version: action.version,
|
|
283
|
+
user_id: action.expected_user_id,
|
|
284
|
+
state_code: action.expected_state_code,
|
|
285
|
+
}));
|
|
286
|
+
return {
|
|
287
|
+
before_hash_set_sha256: sha256Json(orderedActions.map((action) => ({
|
|
288
|
+
action_id: action.action_id,
|
|
289
|
+
table: action.table,
|
|
290
|
+
id: action.id,
|
|
291
|
+
version: action.version,
|
|
292
|
+
row_sha256: action.before?.row_sha256 ?? null,
|
|
293
|
+
}))),
|
|
294
|
+
desired_hash_set_sha256: sha256Json(orderedActions.map((action) => ({
|
|
295
|
+
action_id: action.action_id,
|
|
296
|
+
table: action.table,
|
|
297
|
+
id: action.id,
|
|
298
|
+
version: action.version,
|
|
299
|
+
payload_sha256: action.desired_payload?.sha256 ?? null,
|
|
300
|
+
}))),
|
|
301
|
+
exchange_rewrite_set_sha256: sha256Json((plan.alias_batches ?? []).flatMap((batch) => batch.exchange_rewrites)),
|
|
302
|
+
support_snapshot_set_sha256: sha256Json((plan.alias_batches ?? []).map((batch) => ({
|
|
303
|
+
batch_id: batch.batch_id,
|
|
304
|
+
target_snapshots: batch.target_snapshots,
|
|
305
|
+
}))),
|
|
306
|
+
derivative_target_set_sha256: sha256Json(derivativeTargets),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
export function protectedDerivativeBaselineSetSha256(targets) {
|
|
310
|
+
return sha256Json(targets.map((target) => ({
|
|
311
|
+
table: target.table,
|
|
312
|
+
id: target.id,
|
|
313
|
+
version: target.version,
|
|
314
|
+
baseline_snapshot_sha256: target.baseline_snapshot_sha256,
|
|
315
|
+
})));
|
|
316
|
+
}
|
|
317
|
+
export function assertProtectedFreezeMatchesPlan(options) {
|
|
318
|
+
const { plan, planFileSha256, freeze } = options;
|
|
319
|
+
if (plan.operation !== 'merge-support-aliases' ||
|
|
320
|
+
plan.status !== 'ready' ||
|
|
321
|
+
plan.target_mode !== 'owner_draft' ||
|
|
322
|
+
plan.blockers.length !== 0 ||
|
|
323
|
+
plan.account.user_id !== freeze.account.user_id ||
|
|
324
|
+
plan.account.email !== freeze.account.email ||
|
|
325
|
+
plan.plan_sha256 !== freeze.plan.plan_sha256 ||
|
|
326
|
+
plan.operation_id !== freeze.plan.operation_id ||
|
|
327
|
+
planFileSha256 !== freeze.plan.plan_file_sha256) {
|
|
328
|
+
fail('Protected freeze does not bind the ready owner-draft alias plan and account exactly.');
|
|
329
|
+
}
|
|
330
|
+
const actionCounts = {
|
|
331
|
+
flowproperties: plan.actions.filter((action) => action.table === 'flowproperties').length,
|
|
332
|
+
flows: plan.actions.filter((action) => action.table === 'flows').length,
|
|
333
|
+
processes: plan.actions.filter((action) => action.table === 'processes').length,
|
|
334
|
+
};
|
|
335
|
+
if (plan.actions.length !== PROTECTED_EXECUTION_COUNTS.action_count ||
|
|
336
|
+
plan.alias_batches?.length !== PROTECTED_EXECUTION_COUNTS.batch_count ||
|
|
337
|
+
actionCounts.flowproperties !== PROTECTED_EXECUTION_COUNTS.flowproperty_count ||
|
|
338
|
+
actionCounts.flows !== PROTECTED_EXECUTION_COUNTS.flow_count ||
|
|
339
|
+
actionCounts.processes !== PROTECTED_EXECUTION_COUNTS.process_count ||
|
|
340
|
+
(plan.summary.scaled_exchanges ?? 0) !== PROTECTED_EXECUTION_COUNTS.exchange_count ||
|
|
341
|
+
(plan.summary.scaled_amount_fields ?? 0) !== PROTECTED_EXECUTION_COUNTS.amount_field_count ||
|
|
342
|
+
(plan.summary.unrelated_exchanges_preserved ?? 0) !==
|
|
343
|
+
PROTECTED_EXECUTION_COUNTS.unrelated_exchange_count) {
|
|
344
|
+
fail('Protected plan counts do not match the Step 2 execution contract.');
|
|
345
|
+
}
|
|
346
|
+
const sets = protectedPlanSetHashes(plan);
|
|
347
|
+
for (const [key, actual] of Object.entries(sets)) {
|
|
348
|
+
if (freeze.sets[key] !== actual)
|
|
349
|
+
fail(`Freeze set hash mismatch: ${key}.`);
|
|
350
|
+
}
|
|
351
|
+
if (freeze.sets.alias_plan_request_sha256 !== options.aliasPlanRequestSha256) {
|
|
352
|
+
fail('Freeze alias request hash does not match the exact serialized database plan.');
|
|
353
|
+
}
|
|
354
|
+
if (freeze.sets.derivative_baseline_set_sha256 !==
|
|
355
|
+
protectedDerivativeBaselineSetSha256(freeze.derivative_targets)) {
|
|
356
|
+
fail('Freeze derivative baseline set hash does not match the 50 targets.');
|
|
357
|
+
}
|
|
358
|
+
const targetByKey = new Map(freeze.derivative_targets.map((target) => [
|
|
359
|
+
`${target.table}\u0000${target.id}\u0000${target.version}`,
|
|
360
|
+
target,
|
|
361
|
+
]));
|
|
362
|
+
for (const action of plan.actions.filter((entry) => entry.table === 'flows' || entry.table === 'processes')) {
|
|
363
|
+
const target = targetByKey.get(`${action.table}\u0000${action.id}\u0000${action.version}`);
|
|
364
|
+
if (!target || target.user_id !== action.expected_user_id) {
|
|
365
|
+
fail(`Derivative baseline does not bind action ${action.action_id}.`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
export function assertProtectedApprovalBindings(options) {
|
|
370
|
+
const { approval, freeze } = options;
|
|
371
|
+
if (approval.environment !== freeze.environment ||
|
|
372
|
+
approval.project_ref !== freeze.project_ref ||
|
|
373
|
+
approval.account.user_id !== freeze.account.user_id ||
|
|
374
|
+
approval.account.email !== freeze.account.email ||
|
|
375
|
+
approval.plan_sha256 !== freeze.plan.plan_sha256 ||
|
|
376
|
+
approval.operation_id !== freeze.plan.operation_id ||
|
|
377
|
+
approval.plan_file_sha256 !== freeze.plan.plan_file_sha256 ||
|
|
378
|
+
approval.freeze_file_sha256 !== options.freezeFileSha256 ||
|
|
379
|
+
approval.freeze_sha256 !== freeze.freeze_sha256 ||
|
|
380
|
+
approval.approval_identity_sha256 !== options.approveExecution) {
|
|
381
|
+
fail('Approval does not bind this exact production freeze, plan, actor, and CLI confirmation.');
|
|
382
|
+
}
|
|
383
|
+
hash(options.approvalFileSha256, 'approval_file_sha256');
|
|
384
|
+
}
|
|
385
|
+
function deterministicUuidFromSha256(digest) {
|
|
386
|
+
const chars = digest.slice(0, 32).split('');
|
|
387
|
+
chars[12] = '5';
|
|
388
|
+
chars[16] = ((Number.parseInt(chars[16], 16) & 0x3) | 0x8).toString(16);
|
|
389
|
+
const hex = chars.join('');
|
|
390
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
391
|
+
}
|
|
392
|
+
export function buildProtectedExecutionIdentity(options) {
|
|
393
|
+
const bindings = {
|
|
394
|
+
plan_file_sha256: options.freeze.plan.plan_file_sha256,
|
|
395
|
+
freeze_file_sha256: options.freezeFileSha256,
|
|
396
|
+
freeze_sha256: options.freeze.freeze_sha256,
|
|
397
|
+
approval_file_sha256: options.approvalFileSha256,
|
|
398
|
+
approval_identity_sha256: options.approval.approval_identity_sha256,
|
|
399
|
+
approval_text_sha256: options.approval.approval_text_sha256,
|
|
400
|
+
...options.freeze.sets,
|
|
401
|
+
};
|
|
402
|
+
const body = {
|
|
403
|
+
environment: 'production',
|
|
404
|
+
project_ref: options.freeze.project_ref,
|
|
405
|
+
actor: options.freeze.account,
|
|
406
|
+
target_visibility: 'owner_draft',
|
|
407
|
+
plan_sha256: options.freeze.plan.plan_sha256,
|
|
408
|
+
operation_id: options.freeze.plan.operation_id,
|
|
409
|
+
bindings,
|
|
410
|
+
expected: PROTECTED_EXECUTION_COUNTS,
|
|
411
|
+
derivative_targets: options.freeze.derivative_targets,
|
|
412
|
+
};
|
|
413
|
+
const identitySha256 = sha256Json(body);
|
|
414
|
+
const requestId = deterministicUuidFromSha256(sha256Text(`dataset-alias-protected-request.v1\u0000${identitySha256}`));
|
|
415
|
+
return { request_id: requestId, identity_sha256: identitySha256, ...body };
|
|
416
|
+
}
|
|
417
|
+
export function buildProtectedPreflightRequest(options) {
|
|
418
|
+
return {
|
|
419
|
+
schema_version: PROTECTED_EXECUTION_CONTRACT.preflight_request_schema,
|
|
420
|
+
request_id: options.identity.request_id,
|
|
421
|
+
environment: options.identity.environment,
|
|
422
|
+
project_ref: options.identity.project_ref,
|
|
423
|
+
actor: options.identity.actor,
|
|
424
|
+
target_visibility: options.identity.target_visibility,
|
|
425
|
+
plan: options.plan,
|
|
426
|
+
freeze: options.freeze,
|
|
427
|
+
approval: options.approval,
|
|
428
|
+
bindings: options.identity.bindings,
|
|
429
|
+
expected: options.identity.expected,
|
|
430
|
+
derivative_targets: options.identity.derivative_targets,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
export function parseProtectedPreflightProof(value, identity, now = new Date()) {
|
|
434
|
+
if (!isJsonObject(value) ||
|
|
435
|
+
value.ok !== true ||
|
|
436
|
+
value.schema_version !== PROTECTED_EXECUTION_CONTRACT.preflight_response_schema ||
|
|
437
|
+
value.command !== PROTECTED_EXECUTION_CONTRACT.preflight_command ||
|
|
438
|
+
value.request_id !== identity.request_id ||
|
|
439
|
+
value.actor_user_id !== identity.actor.user_id ||
|
|
440
|
+
value.environment !== identity.environment ||
|
|
441
|
+
value.project_ref !== identity.project_ref ||
|
|
442
|
+
value.plan_sha256 !== identity.plan_sha256 ||
|
|
443
|
+
value.operation_id !== identity.operation_id ||
|
|
444
|
+
!isJsonObject(value.simulation)) {
|
|
445
|
+
fail('Preflight RPC returned a foreign or unsupported proof envelope.');
|
|
446
|
+
}
|
|
447
|
+
const proof = {
|
|
448
|
+
schema_version: PROTECTED_EXECUTION_CONTRACT.preflight_response_schema,
|
|
449
|
+
command: PROTECTED_EXECUTION_CONTRACT.preflight_command,
|
|
450
|
+
request_id: identity.request_id,
|
|
451
|
+
actor_user_id: identity.actor.user_id,
|
|
452
|
+
environment: 'production',
|
|
453
|
+
project_ref: identity.project_ref,
|
|
454
|
+
server_context_sha256: hash(value.server_context_sha256, 'server_context_sha256'),
|
|
455
|
+
plan_sha256: identity.plan_sha256,
|
|
456
|
+
operation_id: identity.operation_id,
|
|
457
|
+
alias_plan_request_sha256: hash(value.alias_plan_request_sha256, 'alias_plan_request_sha256'),
|
|
458
|
+
freeze_sha256: hash(value.freeze_sha256, 'freeze_sha256'),
|
|
459
|
+
approval_identity_sha256: hash(value.approval_identity_sha256, 'approval_identity_sha256'),
|
|
460
|
+
plan_request_sha256: hash(value.plan_request_sha256, 'plan_request_sha256'),
|
|
461
|
+
bindings_sha256: hash(value.bindings_sha256, 'bindings_sha256'),
|
|
462
|
+
expected_sha256: hash(value.expected_sha256, 'expected_sha256'),
|
|
463
|
+
derivative_targets_sha256: hash(value.derivative_targets_sha256, 'derivative_targets_sha256'),
|
|
464
|
+
gate_expectations: parseGateExpectations(value.gate_expectations),
|
|
465
|
+
gate_expectations_sha256: hash(value.gate_expectations_sha256, 'gate_expectations_sha256'),
|
|
466
|
+
failure_baseline_sha256: hash(value.failure_baseline_sha256, 'failure_baseline_sha256'),
|
|
467
|
+
preflight_request_sha256: hash(value.preflight_request_sha256, 'preflight_request_sha256'),
|
|
468
|
+
preflight_token: token(value.preflight_token, 'preflight_token'),
|
|
469
|
+
preflight_proof_sha256: hash(value.preflight_proof_sha256, 'preflight_proof_sha256'),
|
|
470
|
+
completed_at: timestamp(value.completed_at, 'completed_at'),
|
|
471
|
+
expires_at: timestamp(value.expires_at, 'expires_at'),
|
|
472
|
+
simulation: {
|
|
473
|
+
plan_rows: value.simulation.plan_rows,
|
|
474
|
+
plan_exchanges: value.simulation.plan_exchanges,
|
|
475
|
+
alias_audits: value.simulation.alias_audits,
|
|
476
|
+
derivative_targets: value.simulation.derivative_targets,
|
|
477
|
+
rolled_back: value.simulation.rolled_back,
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
if (proof.alias_plan_request_sha256 !== identity.bindings.alias_plan_request_sha256 ||
|
|
481
|
+
proof.freeze_sha256 !== identity.bindings.freeze_sha256 ||
|
|
482
|
+
proof.approval_identity_sha256 !== identity.bindings.approval_identity_sha256 ||
|
|
483
|
+
proof.simulation.plan_rows !== PROTECTED_EXECUTION_COUNTS.action_count ||
|
|
484
|
+
proof.simulation.plan_exchanges !== PROTECTED_EXECUTION_COUNTS.exchange_count ||
|
|
485
|
+
proof.simulation.alias_audits !== PROTECTED_EXECUTION_COUNTS.audit_count ||
|
|
486
|
+
proof.simulation.derivative_targets !== PROTECTED_EXECUTION_COUNTS.derivative_target_count ||
|
|
487
|
+
proof.simulation.rolled_back !== true) {
|
|
488
|
+
fail('Preflight simulation did not prove the exact protected profile and rollback.');
|
|
489
|
+
}
|
|
490
|
+
const issued = Date.parse(proof.completed_at);
|
|
491
|
+
const expires = Date.parse(proof.expires_at);
|
|
492
|
+
if (issued > now.getTime() || expires <= now.getTime() || expires - issued > 180_000) {
|
|
493
|
+
fail('Preflight token is stale, future-issued, or exceeds the 180-second admission window.');
|
|
494
|
+
}
|
|
495
|
+
return proof;
|
|
496
|
+
}
|
|
497
|
+
export function parseProtectedGateProof(value, options) {
|
|
498
|
+
if (!isJsonObject(value) ||
|
|
499
|
+
value.ok !== true ||
|
|
500
|
+
value.schema_version !== PROTECTED_EXECUTION_CONTRACT.gate_response_schema ||
|
|
501
|
+
value.command !== PROTECTED_EXECUTION_CONTRACT.gate_command ||
|
|
502
|
+
value.request_id !== options.identity.request_id ||
|
|
503
|
+
value.actor_user_id !== options.identity.actor.user_id ||
|
|
504
|
+
value.preflight_proof_sha256 !== options.preflight.preflight_proof_sha256 ||
|
|
505
|
+
value.gate !== options.gate ||
|
|
506
|
+
value.status !== 'passed') {
|
|
507
|
+
fail('Gate RPC returned a foreign, failed, or unsupported receipt.');
|
|
508
|
+
}
|
|
509
|
+
const expectedName = `${options.gate}_sha256`;
|
|
510
|
+
const expectedSha256 = hash(value.expected_sha256, 'gate.expected_sha256');
|
|
511
|
+
const observedSha256 = hash(value.observed_sha256, 'gate.observed_sha256');
|
|
512
|
+
const capturedAt = timestamp(value.captured_at, 'gate.captured_at');
|
|
513
|
+
if (expectedSha256 !== options.preflight.gate_expectations[expectedName] ||
|
|
514
|
+
observedSha256 !== expectedSha256 ||
|
|
515
|
+
Date.parse(capturedAt) < Date.parse(options.preflight.completed_at) ||
|
|
516
|
+
Date.parse(capturedAt) > Date.parse(options.preflight.expires_at)) {
|
|
517
|
+
fail('Gate receipt does not match the frozen digest or server preflight window.');
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
schema_version: PROTECTED_EXECUTION_CONTRACT.gate_response_schema,
|
|
521
|
+
command: PROTECTED_EXECUTION_CONTRACT.gate_command,
|
|
522
|
+
request_id: options.identity.request_id,
|
|
523
|
+
actor_user_id: options.identity.actor.user_id,
|
|
524
|
+
preflight_proof_sha256: options.preflight.preflight_proof_sha256,
|
|
525
|
+
gate: options.gate,
|
|
526
|
+
result: {
|
|
527
|
+
expected_sha256: expectedSha256,
|
|
528
|
+
observed_sha256: observedSha256,
|
|
529
|
+
status: 'passed',
|
|
530
|
+
captured_at: capturedAt,
|
|
531
|
+
},
|
|
532
|
+
receipt_sha256: hash(value.receipt_sha256, 'gate.receipt_sha256'),
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
export function buildProtectedAdmitRequest(options) {
|
|
536
|
+
return {
|
|
537
|
+
schema_version: PROTECTED_EXECUTION_CONTRACT.admit_request_schema,
|
|
538
|
+
request_id: options.preflight.request_id,
|
|
539
|
+
preflight_token: options.preflight.preflight_token,
|
|
540
|
+
preflight_proof_sha256: options.preflight.preflight_proof_sha256,
|
|
541
|
+
gate_results: options.gateResults,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
export function parseProtectedAdmissionProof(value, identity, preflight) {
|
|
545
|
+
if (!isJsonObject(value) ||
|
|
546
|
+
value.ok !== true ||
|
|
547
|
+
value.schema_version !== PROTECTED_EXECUTION_CONTRACT.admit_response_schema ||
|
|
548
|
+
value.command !== PROTECTED_EXECUTION_CONTRACT.admit_command ||
|
|
549
|
+
value.request_id !== identity.request_id ||
|
|
550
|
+
value.plan_sha256 !== identity.plan_sha256 ||
|
|
551
|
+
value.operation_id !== identity.operation_id ||
|
|
552
|
+
value.plan_request_sha256 !== preflight.plan_request_sha256 ||
|
|
553
|
+
value.preflight_proof_sha256 !== preflight.preflight_proof_sha256 ||
|
|
554
|
+
value.status !== 'dispatched' ||
|
|
555
|
+
value.attempt_count !== 1 ||
|
|
556
|
+
value.dispatch_count !== 1 ||
|
|
557
|
+
value.attempt_consumed !== true ||
|
|
558
|
+
value.retry_allowed !== false) {
|
|
559
|
+
fail('Admission RPC returned a foreign, duplicate, or unsupported proof envelope.');
|
|
560
|
+
}
|
|
561
|
+
return {
|
|
562
|
+
schema_version: PROTECTED_EXECUTION_CONTRACT.admit_response_schema,
|
|
563
|
+
command: PROTECTED_EXECUTION_CONTRACT.admit_command,
|
|
564
|
+
request_id: identity.request_id,
|
|
565
|
+
plan_sha256: identity.plan_sha256,
|
|
566
|
+
operation_id: identity.operation_id,
|
|
567
|
+
plan_request_sha256: preflight.plan_request_sha256,
|
|
568
|
+
preflight_proof_sha256: preflight.preflight_proof_sha256,
|
|
569
|
+
admission_request_sha256: hash(value.admission_request_sha256, 'admission_request_sha256'),
|
|
570
|
+
gate_results_sha256: hash(value.gate_results_sha256, 'gate_results_sha256'),
|
|
571
|
+
status: 'dispatched',
|
|
572
|
+
attempt_count: 1,
|
|
573
|
+
dispatch_count: 1,
|
|
574
|
+
net_request_id: token(value.net_request_id, 'net_request_id'),
|
|
575
|
+
attempt_consumed: true,
|
|
576
|
+
retry_allowed: false,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
function parseBindings(value) {
|
|
580
|
+
if (!isJsonObject(value))
|
|
581
|
+
fail('bindings must be an object.');
|
|
582
|
+
return Object.fromEntries([
|
|
583
|
+
'plan_file_sha256',
|
|
584
|
+
'freeze_file_sha256',
|
|
585
|
+
'freeze_sha256',
|
|
586
|
+
'approval_file_sha256',
|
|
587
|
+
'approval_identity_sha256',
|
|
588
|
+
'approval_text_sha256',
|
|
589
|
+
'alias_plan_request_sha256',
|
|
590
|
+
'before_hash_set_sha256',
|
|
591
|
+
'desired_hash_set_sha256',
|
|
592
|
+
'exchange_rewrite_set_sha256',
|
|
593
|
+
'support_snapshot_set_sha256',
|
|
594
|
+
'derivative_baseline_set_sha256',
|
|
595
|
+
'derivative_target_set_sha256',
|
|
596
|
+
'toolchain_evidence_sha256',
|
|
597
|
+
].map((key) => [key, hash(value[key], `bindings.${key}`)]));
|
|
598
|
+
}
|
|
599
|
+
function nonNegativeInteger(value, label) {
|
|
600
|
+
if (!Number.isInteger(value) || Number(value) < 0)
|
|
601
|
+
fail(`${label} must be a non-negative integer.`);
|
|
602
|
+
return Number(value);
|
|
603
|
+
}
|
|
604
|
+
function requiredBoolean(value, label) {
|
|
605
|
+
return typeof value === 'boolean' ? value : fail(`${label} must be boolean.`);
|
|
606
|
+
}
|
|
607
|
+
function parseTerminalTarget(value, index) {
|
|
608
|
+
const label = `derivative_readback.targets[${index}]`;
|
|
609
|
+
if (!isJsonObject(value) || !isJsonObject(value.residue)) {
|
|
610
|
+
fail(`${label} must be an object with residue counts.`);
|
|
611
|
+
}
|
|
612
|
+
if (value.table !== 'flows' && value.table !== 'processes') {
|
|
613
|
+
fail(`${label}.table must be flows or processes.`);
|
|
614
|
+
}
|
|
615
|
+
const ordinal = nonNegativeInteger(value.ordinal, `${label}.ordinal`);
|
|
616
|
+
if (ordinal < 1 || ordinal > PROTECTED_EXECUTION_COUNTS.derivative_target_count) {
|
|
617
|
+
fail(`${label}.ordinal is outside the exact 50-target range.`);
|
|
618
|
+
}
|
|
619
|
+
return {
|
|
620
|
+
ordinal,
|
|
621
|
+
request_id: token(value.request_id, `${label}.request_id`),
|
|
622
|
+
table: value.table,
|
|
623
|
+
id: token(value.id, `${label}.id`),
|
|
624
|
+
version: token(value.version, `${label}.version`),
|
|
625
|
+
status: token(value.status, `${label}.status`),
|
|
626
|
+
phase: token(value.phase, `${label}.phase`),
|
|
627
|
+
source_baseline_snapshot_sha256: hash(value.source_baseline_snapshot_sha256, `${label}.source_baseline_snapshot_sha256`),
|
|
628
|
+
expected_snapshot_sha256: hash(value.expected_snapshot_sha256, `${label}.expected_snapshot_sha256`),
|
|
629
|
+
completed_snapshot_sha256: nullableHash(value.completed_snapshot_sha256, `${label}.completed_snapshot_sha256`),
|
|
630
|
+
primary_matches: requiredBoolean(value.primary_matches, `${label}.primary_matches`),
|
|
631
|
+
terminal_snapshot_matches: requiredBoolean(value.terminal_snapshot_matches, `${label}.terminal_snapshot_matches`),
|
|
632
|
+
proposals_committed: requiredBoolean(value.proposals_committed, `${label}.proposals_committed`),
|
|
633
|
+
derivative_fresh: requiredBoolean(value.derivative_fresh, `${label}.derivative_fresh`),
|
|
634
|
+
lifecycle_complete: requiredBoolean(value.lifecycle_complete, `${label}.lifecycle_complete`),
|
|
635
|
+
terminal_audit_present: requiredBoolean(value.terminal_audit_present, `${label}.terminal_audit_present`),
|
|
636
|
+
residue: {
|
|
637
|
+
http_requests: nonNegativeInteger(value.residue.http_requests, `${label}.residue.http_requests`),
|
|
638
|
+
embedding_jobs: nonNegativeInteger(value.residue.embedding_jobs, `${label}.residue.embedding_jobs`),
|
|
639
|
+
pending_jobs: nonNegativeInteger(value.residue.pending_jobs, `${label}.residue.pending_jobs`),
|
|
640
|
+
failure_rows: nonNegativeInteger(value.residue.failure_rows, `${label}.residue.failure_rows`),
|
|
641
|
+
other_active_fences: nonNegativeInteger(value.residue.other_active_fences, `${label}.residue.other_active_fences`),
|
|
642
|
+
},
|
|
643
|
+
causal_terminal_proof: requiredBoolean(value.causal_terminal_proof, `${label}.causal_terminal_proof`),
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
function parseStatusTarget(value, index) {
|
|
647
|
+
const label = `derivative_readback.targets[${index}]`;
|
|
648
|
+
if (!isJsonObject(value) || (value.table !== 'flows' && value.table !== 'processes')) {
|
|
649
|
+
fail(`${label} must be a lightweight flow/process status proof.`);
|
|
650
|
+
}
|
|
651
|
+
const ordinal = nonNegativeInteger(value.ordinal, `${label}.ordinal`);
|
|
652
|
+
if (ordinal < 1 || ordinal > PROTECTED_EXECUTION_COUNTS.derivative_target_count) {
|
|
653
|
+
fail(`${label}.ordinal is outside the exact 50-target range.`);
|
|
654
|
+
}
|
|
655
|
+
if (value.causal_terminal_proof !== false) {
|
|
656
|
+
fail(`${label}.causal_terminal_proof must be false while proof is deferred.`);
|
|
657
|
+
}
|
|
658
|
+
return {
|
|
659
|
+
ordinal,
|
|
660
|
+
request_id: token(value.request_id, `${label}.request_id`),
|
|
661
|
+
table: value.table,
|
|
662
|
+
id: token(value.id, `${label}.id`),
|
|
663
|
+
version: token(value.version, `${label}.version`),
|
|
664
|
+
status: token(value.status, `${label}.status`),
|
|
665
|
+
phase: token(value.phase, `${label}.phase`),
|
|
666
|
+
error: value.error === null
|
|
667
|
+
? null
|
|
668
|
+
: isJsonObject(value.error)
|
|
669
|
+
? value.error
|
|
670
|
+
: fail(`${label}.error must be an object or null.`),
|
|
671
|
+
causal_terminal_proof: false,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
function parseReadGates(value) {
|
|
675
|
+
if (!Array.isArray(value))
|
|
676
|
+
fail('gates must be an array.');
|
|
677
|
+
return value.map((entry, index) => {
|
|
678
|
+
if (!isJsonObject(entry) ||
|
|
679
|
+
!['primary_support_plan', 'execution_unused', 'derivative_quiescence'].includes(String(entry.gate)) ||
|
|
680
|
+
entry.status !== 'passed') {
|
|
681
|
+
fail(`gates[${index}] is invalid.`);
|
|
682
|
+
}
|
|
683
|
+
return {
|
|
684
|
+
gate: entry.gate,
|
|
685
|
+
expected_sha256: hash(entry.expected_sha256, `gates[${index}].expected_sha256`),
|
|
686
|
+
observed_sha256: hash(entry.observed_sha256, `gates[${index}].observed_sha256`),
|
|
687
|
+
status: 'passed',
|
|
688
|
+
captured_at: timestamp(entry.captured_at, `gates[${index}].captured_at`),
|
|
689
|
+
receipt_sha256: hash(entry.receipt_sha256, `gates[${index}].receipt_sha256`),
|
|
690
|
+
};
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
function parseDerivativeReadback(value, requestId) {
|
|
694
|
+
if (!isJsonObject(value) ||
|
|
695
|
+
value.schema_version !== 'dataset-derivative-rebuild-batch-status.v1' ||
|
|
696
|
+
value.batch_id !== requestId ||
|
|
697
|
+
!['not_started', 'pending', 'completed', 'failed'].includes(String(value.status))) {
|
|
698
|
+
fail('derivative_readback is invalid.');
|
|
699
|
+
}
|
|
700
|
+
const notStarted = value.status === 'not_started';
|
|
701
|
+
const common = {
|
|
702
|
+
schema_version: 'dataset-derivative-rebuild-batch-status.v1',
|
|
703
|
+
batch_id: requestId,
|
|
704
|
+
code: value.code === undefined || value.code === null
|
|
705
|
+
? null
|
|
706
|
+
: token(value.code, 'derivative_readback.code'),
|
|
707
|
+
causal_terminal_proof: requiredBoolean(value.causal_terminal_proof, 'derivative_readback.causal_terminal_proof'),
|
|
708
|
+
target_count: nonNegativeInteger(value.target_count, 'derivative_readback.target_count'),
|
|
709
|
+
flow_count: nonNegativeInteger(value.flow_count, 'derivative_readback.flow_count'),
|
|
710
|
+
process_count: nonNegativeInteger(value.process_count, 'derivative_readback.process_count'),
|
|
711
|
+
completed_count: nonNegativeInteger(value.completed_count, 'derivative_readback.completed_count'),
|
|
712
|
+
nonterminal_count: nonNegativeInteger(value.nonterminal_count, 'derivative_readback.nonterminal_count'),
|
|
713
|
+
failed_count: nonNegativeInteger(value.failed_count, 'derivative_readback.failed_count'),
|
|
714
|
+
};
|
|
715
|
+
if (notStarted) {
|
|
716
|
+
if (value.code !== 'DERIVATIVE_BATCH_NOT_STARTED' ||
|
|
717
|
+
value.proof_level !== 'none' ||
|
|
718
|
+
value.proof_deferred !== false ||
|
|
719
|
+
value.invalid_proof_count !== null ||
|
|
720
|
+
!Array.isArray(value.targets) ||
|
|
721
|
+
value.targets.length !== 0 ||
|
|
722
|
+
common.causal_terminal_proof !== false ||
|
|
723
|
+
common.target_count !== 0 ||
|
|
724
|
+
common.flow_count !== 0 ||
|
|
725
|
+
common.process_count !== 0 ||
|
|
726
|
+
common.completed_count !== 0 ||
|
|
727
|
+
common.nonterminal_count !== 0 ||
|
|
728
|
+
common.failed_count !== 0) {
|
|
729
|
+
fail('Not-started derivative readback must carry the exact zero-count proof envelope.');
|
|
730
|
+
}
|
|
731
|
+
return {
|
|
732
|
+
...common,
|
|
733
|
+
status: 'not_started',
|
|
734
|
+
proof_level: 'none',
|
|
735
|
+
proof_deferred: false,
|
|
736
|
+
invalid_proof_count: null,
|
|
737
|
+
targets: [],
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
if (!Array.isArray(value.targets)) {
|
|
741
|
+
fail('derivative_readback.targets must be an array.');
|
|
742
|
+
}
|
|
743
|
+
if (value.proof_level === 'status_only') {
|
|
744
|
+
if ((value.status !== 'pending' && value.status !== 'failed') ||
|
|
745
|
+
typeof value.proof_deferred !== 'boolean' ||
|
|
746
|
+
value.invalid_proof_count !== null ||
|
|
747
|
+
common.causal_terminal_proof !== false) {
|
|
748
|
+
fail('Status-only derivative readback has inconsistent proof metadata.');
|
|
749
|
+
}
|
|
750
|
+
return {
|
|
751
|
+
...common,
|
|
752
|
+
status: value.status,
|
|
753
|
+
proof_level: 'status_only',
|
|
754
|
+
proof_deferred: value.proof_deferred,
|
|
755
|
+
invalid_proof_count: null,
|
|
756
|
+
targets: value.targets.map(parseStatusTarget),
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
if (value.proof_level !== 'causal_terminal' ||
|
|
760
|
+
value.proof_deferred !== false ||
|
|
761
|
+
(value.status !== 'completed' && value.status !== 'failed')) {
|
|
762
|
+
fail('Terminal derivative readback must carry a causal proof envelope.');
|
|
763
|
+
}
|
|
764
|
+
return {
|
|
765
|
+
...common,
|
|
766
|
+
status: value.status,
|
|
767
|
+
proof_level: 'causal_terminal',
|
|
768
|
+
proof_deferred: false,
|
|
769
|
+
invalid_proof_count: nonNegativeInteger(value.invalid_proof_count, 'derivative_readback.invalid_proof_count'),
|
|
770
|
+
targets: value.targets.map(parseTerminalTarget),
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
export function parseProtectedStatusProof(value, identity) {
|
|
774
|
+
if (!isJsonObject(value) ||
|
|
775
|
+
value.ok !== true ||
|
|
776
|
+
value.schema_version !== PROTECTED_EXECUTION_CONTRACT.status_response_schema ||
|
|
777
|
+
value.command !== PROTECTED_EXECUTION_CONTRACT.read_command ||
|
|
778
|
+
value.request_id !== identity.request_id ||
|
|
779
|
+
!['pending', 'passed', 'failed', 'indeterminate'].includes(String(value.status)) ||
|
|
780
|
+
![
|
|
781
|
+
'not_admitted',
|
|
782
|
+
'dispatching',
|
|
783
|
+
'dispatched',
|
|
784
|
+
'running',
|
|
785
|
+
'derivatives_pending',
|
|
786
|
+
'completed',
|
|
787
|
+
'failed',
|
|
788
|
+
'indeterminate',
|
|
789
|
+
].includes(String(value.execution_status)) ||
|
|
790
|
+
value.retry_allowed !== false ||
|
|
791
|
+
value.actor_user_id !== identity.actor.user_id ||
|
|
792
|
+
value.environment !== identity.environment ||
|
|
793
|
+
value.project_ref !== identity.project_ref ||
|
|
794
|
+
value.plan_sha256 !== identity.plan_sha256 ||
|
|
795
|
+
value.operation_id !== identity.operation_id) {
|
|
796
|
+
fail('Read RPC returned a foreign or unsupported status envelope.');
|
|
797
|
+
}
|
|
798
|
+
const executionStatus = value.execution_status;
|
|
799
|
+
const gates = parseReadGates(value.gates);
|
|
800
|
+
if (executionStatus === 'not_admitted') {
|
|
801
|
+
if (value.status !== 'indeterminate' || gates.length > 3) {
|
|
802
|
+
fail('A not-admitted read must be indeterminate and contain at most three gate receipts.');
|
|
803
|
+
}
|
|
804
|
+
return {
|
|
805
|
+
schema_version: PROTECTED_EXECUTION_CONTRACT.status_response_schema,
|
|
806
|
+
command: PROTECTED_EXECUTION_CONTRACT.read_command,
|
|
807
|
+
request_id: identity.request_id,
|
|
808
|
+
status: 'indeterminate',
|
|
809
|
+
execution_status: 'not_admitted',
|
|
810
|
+
retry_allowed: false,
|
|
811
|
+
actor_user_id: identity.actor.user_id,
|
|
812
|
+
environment: 'production',
|
|
813
|
+
project_ref: identity.project_ref,
|
|
814
|
+
target_visibility: null,
|
|
815
|
+
plan_sha256: identity.plan_sha256,
|
|
816
|
+
operation_id: identity.operation_id,
|
|
817
|
+
plan_request_sha256: hash(value.plan_request_sha256, 'plan_request_sha256'),
|
|
818
|
+
freeze_sha256: null,
|
|
819
|
+
approval_identity_sha256: null,
|
|
820
|
+
approval_text_sha256: null,
|
|
821
|
+
derivative_target_set_sha256: null,
|
|
822
|
+
preflight_proof_sha256: hash(value.preflight_proof_sha256, 'preflight_proof_sha256'),
|
|
823
|
+
admission_request_sha256: null,
|
|
824
|
+
gate_results_sha256: null,
|
|
825
|
+
attempt_count: 0,
|
|
826
|
+
dispatch_count: 0,
|
|
827
|
+
gate_count: nonNegativeInteger(value.gate_count, 'gate_count'),
|
|
828
|
+
gates,
|
|
829
|
+
primary_readback: null,
|
|
830
|
+
derivative_readback: {
|
|
831
|
+
schema_version: 'dataset-derivative-rebuild-batch-status.v1',
|
|
832
|
+
batch_id: identity.request_id,
|
|
833
|
+
status: 'not_started',
|
|
834
|
+
proof_level: 'none',
|
|
835
|
+
proof_deferred: false,
|
|
836
|
+
code: 'ALIAS_EXECUTION_NOT_ADMITTED',
|
|
837
|
+
causal_terminal_proof: false,
|
|
838
|
+
target_count: 0,
|
|
839
|
+
flow_count: 0,
|
|
840
|
+
process_count: 0,
|
|
841
|
+
completed_count: 0,
|
|
842
|
+
nonterminal_count: 0,
|
|
843
|
+
failed_count: 0,
|
|
844
|
+
invalid_proof_count: null,
|
|
845
|
+
targets: [],
|
|
846
|
+
},
|
|
847
|
+
failure: null,
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
if (value.target_visibility !== 'owner_draft' ||
|
|
851
|
+
value.freeze_sha256 !== identity.bindings.freeze_sha256 ||
|
|
852
|
+
value.approval_identity_sha256 !== identity.bindings.approval_identity_sha256 ||
|
|
853
|
+
value.approval_text_sha256 !== identity.bindings.approval_text_sha256 ||
|
|
854
|
+
value.derivative_target_set_sha256 !== identity.bindings.derivative_target_set_sha256 ||
|
|
855
|
+
!isJsonObject(value.primary_readback)) {
|
|
856
|
+
fail('Read RPC did not round-trip the sealed owner-draft execution bindings.');
|
|
857
|
+
}
|
|
858
|
+
const derivativeReadback = parseDerivativeReadback(value.derivative_readback, identity.request_id);
|
|
859
|
+
if (derivativeReadback.status === 'not_started' &&
|
|
860
|
+
!['dispatching', 'dispatched', 'running', 'failed', 'indeterminate'].includes(executionStatus)) {
|
|
861
|
+
fail('A not-started derivative readback is only valid before derivative dispatch or after a zero-child terminal failure.');
|
|
862
|
+
}
|
|
863
|
+
return {
|
|
864
|
+
schema_version: PROTECTED_EXECUTION_CONTRACT.status_response_schema,
|
|
865
|
+
command: PROTECTED_EXECUTION_CONTRACT.read_command,
|
|
866
|
+
request_id: identity.request_id,
|
|
867
|
+
status: value.status,
|
|
868
|
+
execution_status: executionStatus,
|
|
869
|
+
retry_allowed: false,
|
|
870
|
+
actor_user_id: identity.actor.user_id,
|
|
871
|
+
environment: 'production',
|
|
872
|
+
project_ref: identity.project_ref,
|
|
873
|
+
target_visibility: 'owner_draft',
|
|
874
|
+
plan_sha256: identity.plan_sha256,
|
|
875
|
+
operation_id: identity.operation_id,
|
|
876
|
+
plan_request_sha256: hash(value.plan_request_sha256, 'plan_request_sha256'),
|
|
877
|
+
freeze_sha256: identity.bindings.freeze_sha256,
|
|
878
|
+
approval_identity_sha256: identity.bindings.approval_identity_sha256,
|
|
879
|
+
approval_text_sha256: identity.bindings.approval_text_sha256,
|
|
880
|
+
derivative_target_set_sha256: identity.bindings.derivative_target_set_sha256,
|
|
881
|
+
preflight_proof_sha256: hash(value.preflight_proof_sha256, 'preflight_proof_sha256'),
|
|
882
|
+
admission_request_sha256: hash(value.admission_request_sha256, 'admission_request_sha256'),
|
|
883
|
+
gate_results_sha256: hash(value.gate_results_sha256, 'gate_results_sha256'),
|
|
884
|
+
attempt_count: nonNegativeInteger(value.attempt_count, 'attempt_count'),
|
|
885
|
+
dispatch_count: nonNegativeInteger(value.dispatch_count, 'dispatch_count'),
|
|
886
|
+
gate_count: nonNegativeInteger(value.gate_count, 'gate_count'),
|
|
887
|
+
gates,
|
|
888
|
+
primary_readback: {
|
|
889
|
+
row_count: value.primary_readback.row_count === null
|
|
890
|
+
? null
|
|
891
|
+
: nonNegativeInteger(value.primary_readback.row_count, 'primary_readback.row_count'),
|
|
892
|
+
exchange_count: value.primary_readback.exchange_count === null
|
|
893
|
+
? null
|
|
894
|
+
: nonNegativeInteger(value.primary_readback.exchange_count, 'primary_readback.exchange_count'),
|
|
895
|
+
alias_audit_count: nonNegativeInteger(value.primary_readback.alias_audit_count, 'primary_readback.alias_audit_count'),
|
|
896
|
+
live_closure_proof: requiredBoolean(value.primary_readback.live_closure_proof, 'primary_readback.live_closure_proof'),
|
|
897
|
+
closure: isJsonObject(value.primary_readback.closure)
|
|
898
|
+
? value.primary_readback.closure
|
|
899
|
+
: fail('primary_readback.closure must be an object.'),
|
|
900
|
+
},
|
|
901
|
+
derivative_readback: derivativeReadback,
|
|
902
|
+
failure: value.error === null
|
|
903
|
+
? null
|
|
904
|
+
: isJsonObject(value.error)
|
|
905
|
+
? value.error
|
|
906
|
+
: fail('error must be an object or null.'),
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
export function isUuid(value) {
|
|
910
|
+
return UUID.test(value);
|
|
911
|
+
}
|
|
912
|
+
export const __testInternals = {
|
|
913
|
+
deterministicUuidFromSha256,
|
|
914
|
+
parseBindings,
|
|
915
|
+
parseDerivativeTarget,
|
|
916
|
+
parseExpected,
|
|
917
|
+
};
|
|
918
|
+
//# sourceMappingURL=dataset-maintenance-protected-contract.js.map
|