@tiangong-lca/cli 0.0.25 → 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 +37 -8
- package/dist/src/cli.js +292 -3
- package/dist/src/cli.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-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 +54 -158
- package/dist/src/lib/dataset-maintenance-protected-run.js.map +1 -1
- 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/package.json +1 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { TextDecoder } from 'node:util';
|
|
5
|
+
import { stableJsonText } from './dataset-maintenance-contract.js';
|
|
6
|
+
import { CliError } from './errors.js';
|
|
7
|
+
const STRICT_UTF8 = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
|
8
|
+
export function readProtectedTextArtifact(filePath) {
|
|
9
|
+
const resolved = path.resolve(filePath);
|
|
10
|
+
const bytes = readFileSync(resolved);
|
|
11
|
+
let text;
|
|
12
|
+
try {
|
|
13
|
+
text = STRICT_UTF8.decode(bytes);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
throw new CliError(`Protected artifact is not valid UTF-8: ${resolved}`, {
|
|
17
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_ARTIFACT_UTF8_INVALID',
|
|
18
|
+
exitCode: 2,
|
|
19
|
+
details: String(error),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
resolved,
|
|
24
|
+
text,
|
|
25
|
+
file_sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function readProtectedJsonArtifact(options) {
|
|
29
|
+
const artifact = readProtectedTextArtifact(options.filePath);
|
|
30
|
+
let value;
|
|
31
|
+
try {
|
|
32
|
+
value = JSON.parse(artifact.text);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw new CliError(`${options.label} is not valid JSON: ${artifact.resolved}`, {
|
|
36
|
+
code: 'DATASET_MAINTENANCE_ARTIFACT_INVALID',
|
|
37
|
+
exitCode: 2,
|
|
38
|
+
details: String(error),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
...artifact,
|
|
43
|
+
value,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function ensurePrivateArtifactDirectory(directory) {
|
|
47
|
+
const resolved = path.resolve(directory);
|
|
48
|
+
mkdirSync(resolved, { recursive: true, mode: 0o700 });
|
|
49
|
+
chmodSync(resolved, 0o700);
|
|
50
|
+
return resolved;
|
|
51
|
+
}
|
|
52
|
+
export function materializePrivateArtifactDirectoryAtomically(directory, materialize) {
|
|
53
|
+
const resolved = path.resolve(directory);
|
|
54
|
+
if (existsSync(resolved)) {
|
|
55
|
+
throw new CliError(`Protected artifact directory already exists: ${resolved}`, {
|
|
56
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_ARTIFACT_DIRECTORY_EXISTS',
|
|
57
|
+
exitCode: 1,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const parent = path.dirname(resolved);
|
|
61
|
+
mkdirSync(parent, { recursive: true, mode: 0o700 });
|
|
62
|
+
const staging = mkdtempSync(path.join(parent, `.${path.basename(resolved)}.staging-`));
|
|
63
|
+
chmodSync(staging, 0o700);
|
|
64
|
+
try {
|
|
65
|
+
const result = materialize(staging);
|
|
66
|
+
if (existsSync(resolved)) {
|
|
67
|
+
throw new CliError(`Protected artifact directory appeared during materialization: ${resolved}`, {
|
|
68
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_ARTIFACT_DIRECTORY_RACE',
|
|
69
|
+
exitCode: 1,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
renameSync(staging, resolved);
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
rmSync(staging, { recursive: true, force: true });
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function writePrivateImmutableText(filePath, text) {
|
|
81
|
+
const resolved = path.resolve(filePath);
|
|
82
|
+
const bytes = Buffer.from(text, 'utf8');
|
|
83
|
+
ensurePrivateArtifactDirectory(path.dirname(resolved));
|
|
84
|
+
if (existsSync(resolved)) {
|
|
85
|
+
if (!readFileSync(resolved).equals(bytes)) {
|
|
86
|
+
throw new CliError(`Refusing to overwrite protected evidence: ${resolved}`, {
|
|
87
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_ARTIFACT_IMMUTABLE',
|
|
88
|
+
exitCode: 1,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
chmodSync(resolved, 0o600);
|
|
92
|
+
return resolved;
|
|
93
|
+
}
|
|
94
|
+
writeFileSync(resolved, bytes, { flag: 'wx', mode: 0o600 });
|
|
95
|
+
return resolved;
|
|
96
|
+
}
|
|
97
|
+
export function writePrivateImmutableJson(filePath, value) {
|
|
98
|
+
return writePrivateImmutableText(filePath, `${stableJsonText(value)}\n`);
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=dataset-maintenance-protected-artifacts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-maintenance-protected-artifacts.js","sourceRoot":"","sources":["../../../src/lib/dataset-maintenance-protected-artifacts.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport {\n chmodSync,\n existsSync,\n mkdirSync,\n mkdtempSync,\n readFileSync,\n renameSync,\n rmSync,\n writeFileSync,\n} from 'node:fs';\nimport path from 'node:path';\nimport { TextDecoder } from 'node:util';\nimport { stableJsonText } from './dataset-maintenance-contract.js';\nimport { CliError } from './errors.js';\n\nconst STRICT_UTF8 = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });\n\nexport type ProtectedJsonArtifact = {\n resolved: string;\n value: unknown;\n text: string;\n file_sha256: string;\n};\n\nexport type ProtectedTextArtifact = Omit<ProtectedJsonArtifact, 'value'>;\n\nexport function readProtectedTextArtifact(filePath: string): ProtectedTextArtifact {\n const resolved = path.resolve(filePath);\n const bytes = readFileSync(resolved);\n let text: string;\n try {\n text = STRICT_UTF8.decode(bytes);\n } catch (error) {\n throw new CliError(`Protected artifact is not valid UTF-8: ${resolved}`, {\n code: 'DATASET_MAINTENANCE_PROTECTED_ARTIFACT_UTF8_INVALID',\n exitCode: 2,\n details: String(error),\n });\n }\n return {\n resolved,\n text,\n file_sha256: createHash('sha256').update(bytes).digest('hex'),\n };\n}\n\nexport function readProtectedJsonArtifact(options: {\n filePath: string;\n label: string;\n}): ProtectedJsonArtifact {\n const artifact = readProtectedTextArtifact(options.filePath);\n let value: unknown;\n try {\n value = JSON.parse(artifact.text);\n } catch (error) {\n throw new CliError(`${options.label} is not valid JSON: ${artifact.resolved}`, {\n code: 'DATASET_MAINTENANCE_ARTIFACT_INVALID',\n exitCode: 2,\n details: String(error),\n });\n }\n return {\n ...artifact,\n value,\n };\n}\n\nexport function ensurePrivateArtifactDirectory(directory: string): string {\n const resolved = path.resolve(directory);\n mkdirSync(resolved, { recursive: true, mode: 0o700 });\n chmodSync(resolved, 0o700);\n return resolved;\n}\n\nexport function materializePrivateArtifactDirectoryAtomically<T>(\n directory: string,\n materialize: (stagingDirectory: string) => T,\n): T {\n const resolved = path.resolve(directory);\n if (existsSync(resolved)) {\n throw new CliError(`Protected artifact directory already exists: ${resolved}`, {\n code: 'DATASET_MAINTENANCE_PROTECTED_ARTIFACT_DIRECTORY_EXISTS',\n exitCode: 1,\n });\n }\n const parent = path.dirname(resolved);\n mkdirSync(parent, { recursive: true, mode: 0o700 });\n const staging = mkdtempSync(path.join(parent, `.${path.basename(resolved)}.staging-`));\n chmodSync(staging, 0o700);\n try {\n const result = materialize(staging);\n if (existsSync(resolved)) {\n throw new CliError(\n `Protected artifact directory appeared during materialization: ${resolved}`,\n {\n code: 'DATASET_MAINTENANCE_PROTECTED_ARTIFACT_DIRECTORY_RACE',\n exitCode: 1,\n },\n );\n }\n renameSync(staging, resolved);\n return result;\n } catch (error) {\n rmSync(staging, { recursive: true, force: true });\n throw error;\n }\n}\n\nexport function writePrivateImmutableText(filePath: string, text: string): string {\n const resolved = path.resolve(filePath);\n const bytes = Buffer.from(text, 'utf8');\n ensurePrivateArtifactDirectory(path.dirname(resolved));\n if (existsSync(resolved)) {\n if (!readFileSync(resolved).equals(bytes)) {\n throw new CliError(`Refusing to overwrite protected evidence: ${resolved}`, {\n code: 'DATASET_MAINTENANCE_PROTECTED_ARTIFACT_IMMUTABLE',\n exitCode: 1,\n });\n }\n chmodSync(resolved, 0o600);\n return resolved;\n }\n writeFileSync(resolved, bytes, { flag: 'wx', mode: 0o600 });\n return resolved;\n}\n\nexport function writePrivateImmutableJson(filePath: string, value: unknown): string {\n return writePrivateImmutableText(filePath, `${stableJsonText(value)}\\n`);\n}\n"]}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { loadMaintenanceDesiredPayload } from './dataset-maintenance-alias-request.js';
|
|
2
|
+
import { MAINTENANCE_SCAN_TABLES, maintenanceRowKey, sha256Json, snapshotRemoteRow, } from './dataset-maintenance-contract.js';
|
|
3
|
+
import { PROTECTED_EXECUTION_COUNTS, parseProtectedDerivativeSnapshot, } from './dataset-maintenance-protected-contract.js';
|
|
4
|
+
import { maintenanceProjectedReferenceFingerprint } from './dataset-maintenance-plan.js';
|
|
5
|
+
import { isSnapshotCompletenessCompatible } from './dataset-maintenance-pagination.js';
|
|
6
|
+
import { fetchMaintenanceDerivativeSnapshot, fetchMaintenanceExactRows, } from './dataset-maintenance-remote.js';
|
|
7
|
+
import { CliError } from './errors.js';
|
|
8
|
+
const DERIVATIVE_READ_CONCURRENCY = 5;
|
|
9
|
+
function readDependencies(overrides = {}) {
|
|
10
|
+
return {
|
|
11
|
+
fetchExactRows: overrides.fetchExactRows ?? fetchMaintenanceExactRows,
|
|
12
|
+
fetchDerivativeSnapshot: overrides.fetchDerivativeSnapshot ?? fetchMaintenanceDerivativeSnapshot,
|
|
13
|
+
parseDerivativeSnapshot: overrides.parseDerivativeSnapshot ?? parseProtectedDerivativeSnapshot,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function actorMismatch(message) {
|
|
17
|
+
throw new CliError(message, {
|
|
18
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_ACTOR_MISMATCH',
|
|
19
|
+
exitCode: 1,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function assertPlanActor(plan, actorUserId) {
|
|
23
|
+
if (!actorUserId.trim() ||
|
|
24
|
+
plan.account.user_id !== actorUserId ||
|
|
25
|
+
plan.actions.some((action) => action.expected_user_id !== actorUserId)) {
|
|
26
|
+
actorMismatch('Protected preparation plan and actions must belong to the exact actor.');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function assertReadContextActor(context, actorUserId) {
|
|
30
|
+
if (context.account.user_id !== actorUserId) {
|
|
31
|
+
actorMismatch('Authenticated read context does not match the protected preparation actor.');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function projectedRows(options) {
|
|
35
|
+
const projected = new Map(options.currentRows.map((row) => [maintenanceRowKey(row), { ...row }]));
|
|
36
|
+
for (const action of options.plan.actions) {
|
|
37
|
+
const key = maintenanceRowKey(action);
|
|
38
|
+
const row = projected.get(key);
|
|
39
|
+
if (row) {
|
|
40
|
+
projected.set(key, {
|
|
41
|
+
...row,
|
|
42
|
+
json_ordered: loadMaintenanceDesiredPayload(options.planDir, action),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return [...projected.values()].sort((left, right) => maintenanceRowKey(left).localeCompare(maintenanceRowKey(right)));
|
|
47
|
+
}
|
|
48
|
+
export function assertStrictBeforeState(options) {
|
|
49
|
+
const { plan } = options;
|
|
50
|
+
assertPlanActor(plan, options.actorUserId);
|
|
51
|
+
if (!plan.snapshot_completeness ||
|
|
52
|
+
!isSnapshotCompletenessCompatible(options.completeness, plan.snapshot_completeness, MAINTENANCE_SCAN_TABLES)) {
|
|
53
|
+
throw new CliError('Production RLS census does not match the frozen complete snapshot.', {
|
|
54
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_SNAPSHOT_INCOMPLETE',
|
|
55
|
+
exitCode: 1,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const snapshots = options.currentRows
|
|
59
|
+
.map(snapshotRemoteRow)
|
|
60
|
+
.sort((left, right) => maintenanceRowKey(left).localeCompare(maintenanceRowKey(right)));
|
|
61
|
+
if (sha256Json(snapshots) !== plan.visible_snapshot_sha256) {
|
|
62
|
+
throw new CliError('Production RLS visible snapshot drifted after the freeze.', {
|
|
63
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_VISIBLE_SNAPSHOT_DRIFT',
|
|
64
|
+
exitCode: 1,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const expectedKeys = new Set([
|
|
68
|
+
...plan.actions.map(maintenanceRowKey),
|
|
69
|
+
...plan.protected_rows.map(maintenanceRowKey),
|
|
70
|
+
]);
|
|
71
|
+
const currentKeys = options.currentRows.map(maintenanceRowKey);
|
|
72
|
+
if (expectedKeys.size !== options.currentRows.length ||
|
|
73
|
+
new Set(currentKeys).size !== options.currentRows.length ||
|
|
74
|
+
options.currentRows.some((row) => row.user_id !== options.actorUserId || !expectedKeys.has(maintenanceRowKey(row)))) {
|
|
75
|
+
throw new CliError('Production owner account contains missing or unexpected rows.', {
|
|
76
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_ACCOUNT_CENSUS_DRIFT',
|
|
77
|
+
exitCode: 1,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const current = new Map(options.currentRows.map((row) => [maintenanceRowKey(row), row]));
|
|
81
|
+
for (const row of plan.protected_rows) {
|
|
82
|
+
const observed = current.get(maintenanceRowKey(row));
|
|
83
|
+
if (snapshotRemoteRow(observed).row_sha256 !== row.row_sha256) {
|
|
84
|
+
throw new CliError(`Protected row drifted: ${row.id}`, {
|
|
85
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_ROW_DRIFT',
|
|
86
|
+
exitCode: 1,
|
|
87
|
+
details: row,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (const action of plan.actions) {
|
|
92
|
+
const observed = current.get(maintenanceRowKey(action));
|
|
93
|
+
if (!action.before ||
|
|
94
|
+
observed.user_id !== options.actorUserId ||
|
|
95
|
+
observed.user_id !== action.expected_user_id ||
|
|
96
|
+
observed.state_code !== 0 ||
|
|
97
|
+
snapshotRemoteRow(observed).row_sha256 !== action.before.row_sha256) {
|
|
98
|
+
throw new CliError(`Action row is no longer in the exact frozen before state: ${action.action_id}`, {
|
|
99
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_ACTION_DRIFT',
|
|
100
|
+
exitCode: 1,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const finalRows = projectedRows({
|
|
105
|
+
plan,
|
|
106
|
+
planDir: options.planDir,
|
|
107
|
+
currentRows: options.currentRows,
|
|
108
|
+
});
|
|
109
|
+
if (sha256Json(maintenanceProjectedReferenceFingerprint(finalRows)) !==
|
|
110
|
+
plan.projected_reference_sha256) {
|
|
111
|
+
throw new CliError('Projected reference closure drifted before protected execution.', {
|
|
112
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_REFERENCE_DRIFT',
|
|
113
|
+
exitCode: 1,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return finalRows;
|
|
117
|
+
}
|
|
118
|
+
export async function assertSupportSnapshots(options) {
|
|
119
|
+
assertPlanActor(options.plan, options.actorUserId);
|
|
120
|
+
assertReadContextActor(options.context, options.actorUserId);
|
|
121
|
+
const dependencies = readDependencies(options.dependencies);
|
|
122
|
+
const verified = [];
|
|
123
|
+
for (const batch of options.plan.alias_batches ?? []) {
|
|
124
|
+
for (const snapshot of [
|
|
125
|
+
batch.target_snapshots.unitgroup,
|
|
126
|
+
batch.target_snapshots.flowproperty,
|
|
127
|
+
batch.target_snapshots.source_unitgroup,
|
|
128
|
+
]) {
|
|
129
|
+
if (!snapshot) {
|
|
130
|
+
throw new CliError(`Alias support snapshot is absent for ${batch.batch_id}.`, {
|
|
131
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_SUPPORT_DRIFT',
|
|
132
|
+
exitCode: 1,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const exact = await dependencies.fetchExactRows({
|
|
136
|
+
context: options.context,
|
|
137
|
+
table: snapshot.table,
|
|
138
|
+
id: snapshot.id,
|
|
139
|
+
version: snapshot.version,
|
|
140
|
+
});
|
|
141
|
+
const row = exact.rows.length === 1 ? exact.rows[0] : null;
|
|
142
|
+
if (!row ||
|
|
143
|
+
row.user_id !== options.actorUserId ||
|
|
144
|
+
row.state_code !== 0 ||
|
|
145
|
+
snapshotRemoteRow(row).row_sha256 !== snapshot.row_sha256) {
|
|
146
|
+
throw new CliError(`Alias support row drifted for ${batch.batch_id}.`, {
|
|
147
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_SUPPORT_DRIFT',
|
|
148
|
+
exitCode: 1,
|
|
149
|
+
details: { table: snapshot.table, id: snapshot.id, version: snapshot.version },
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
verified.push(snapshotRemoteRow(row));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return verified;
|
|
156
|
+
}
|
|
157
|
+
function derivativeTargetKey(target) {
|
|
158
|
+
return `${target.table}\u0000${target.id}\u0000${target.version}`;
|
|
159
|
+
}
|
|
160
|
+
function stableDerivativeTargets(targets, actorUserId) {
|
|
161
|
+
const keys = targets.map(derivativeTargetKey);
|
|
162
|
+
if (targets.length !== PROTECTED_EXECUTION_COUNTS.derivative_target_count ||
|
|
163
|
+
new Set(keys).size !== targets.length ||
|
|
164
|
+
targets.filter((target) => target.table === 'flows').length !==
|
|
165
|
+
PROTECTED_EXECUTION_COUNTS.flow_count ||
|
|
166
|
+
targets.filter((target) => target.table === 'processes').length !==
|
|
167
|
+
PROTECTED_EXECUTION_COUNTS.process_count ||
|
|
168
|
+
targets.some((target) => !target.id.trim() ||
|
|
169
|
+
!target.version.trim() ||
|
|
170
|
+
target.user_id !== actorUserId ||
|
|
171
|
+
target.state_code !== 0)) {
|
|
172
|
+
throw new CliError('Protected derivative targets are incomplete, duplicate, or foreign.', {
|
|
173
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_DERIVATIVE_TARGET_INVALID',
|
|
174
|
+
exitCode: 1,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return [...targets].sort((left, right) => derivativeTargetKey(left).localeCompare(derivativeTargetKey(right)));
|
|
178
|
+
}
|
|
179
|
+
export async function captureDerivativeSnapshots(options) {
|
|
180
|
+
assertReadContextActor(options.context, options.actorUserId);
|
|
181
|
+
const targets = stableDerivativeTargets(options.derivativeTargets, options.actorUserId);
|
|
182
|
+
const dependencies = readDependencies(options.dependencies);
|
|
183
|
+
const verified = [];
|
|
184
|
+
for (let offset = 0; offset < targets.length; offset += DERIVATIVE_READ_CONCURRENCY) {
|
|
185
|
+
const chunk = targets.slice(offset, offset + DERIVATIVE_READ_CONCURRENCY);
|
|
186
|
+
const snapshots = await Promise.all(chunk.map(async (target) => dependencies.parseDerivativeSnapshot(await dependencies.fetchDerivativeSnapshot({
|
|
187
|
+
context: options.context,
|
|
188
|
+
table: target.table,
|
|
189
|
+
id: target.id,
|
|
190
|
+
version: target.version,
|
|
191
|
+
}), {
|
|
192
|
+
table: target.table,
|
|
193
|
+
id: target.id,
|
|
194
|
+
version: target.version,
|
|
195
|
+
userId: target.user_id,
|
|
196
|
+
})));
|
|
197
|
+
verified.push(...snapshots);
|
|
198
|
+
}
|
|
199
|
+
return verified;
|
|
200
|
+
}
|
|
201
|
+
export async function assertDerivativeBaselines(options) {
|
|
202
|
+
const snapshots = await captureDerivativeSnapshots({
|
|
203
|
+
actorUserId: options.actorUserId,
|
|
204
|
+
context: options.context,
|
|
205
|
+
derivativeTargets: options.derivativeTargets,
|
|
206
|
+
dependencies: options.dependencies,
|
|
207
|
+
});
|
|
208
|
+
const expectedByKey = new Map(options.derivativeTargets.map((target) => [derivativeTargetKey(target), target]));
|
|
209
|
+
for (const snapshot of snapshots) {
|
|
210
|
+
const target = expectedByKey.get(derivativeTargetKey(snapshot));
|
|
211
|
+
if (!target || snapshot.snapshot_sha256 !== target.baseline_snapshot_sha256) {
|
|
212
|
+
throw new CliError('A protected derivative baseline drifted before preflight.', {
|
|
213
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_DERIVATIVE_BASELINE_DRIFT',
|
|
214
|
+
exitCode: 1,
|
|
215
|
+
details: { table: snapshot.table, id: snapshot.id, version: snapshot.version },
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return snapshots;
|
|
220
|
+
}
|
|
221
|
+
export function assertDerivativeCensusBindings(options) {
|
|
222
|
+
const current = new Map(options.currentRows
|
|
223
|
+
.filter((row) => row.table === 'flows' || row.table === 'processes')
|
|
224
|
+
.map((row) => [maintenanceRowKey(row), row]));
|
|
225
|
+
for (const snapshot of options.derivativeSnapshots) {
|
|
226
|
+
const row = current.get(maintenanceRowKey(snapshot));
|
|
227
|
+
if (!row ||
|
|
228
|
+
row.user_id !== options.actorUserId ||
|
|
229
|
+
row.state_code !== 0 ||
|
|
230
|
+
row.modified_at !== snapshot.modified_at) {
|
|
231
|
+
throw new CliError('A protected derivative snapshot does not match the immediately preceding account census.', {
|
|
232
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_DERIVATIVE_CENSUS_DRIFT',
|
|
233
|
+
exitCode: 1,
|
|
234
|
+
details: {
|
|
235
|
+
table: snapshot.table,
|
|
236
|
+
id: snapshot.id,
|
|
237
|
+
version: snapshot.version,
|
|
238
|
+
census_modified_at: row?.modified_at ?? null,
|
|
239
|
+
derivative_modified_at: snapshot.modified_at,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
export async function validateProtectedBeforeState(options) {
|
|
246
|
+
const projected = assertStrictBeforeState({
|
|
247
|
+
plan: options.plan,
|
|
248
|
+
planDir: options.planDir,
|
|
249
|
+
actorUserId: options.actorUserId,
|
|
250
|
+
currentRows: options.currentRows,
|
|
251
|
+
completeness: options.completeness,
|
|
252
|
+
});
|
|
253
|
+
const support = await assertSupportSnapshots({
|
|
254
|
+
plan: options.plan,
|
|
255
|
+
actorUserId: options.actorUserId,
|
|
256
|
+
context: options.context,
|
|
257
|
+
dependencies: options.dependencies,
|
|
258
|
+
});
|
|
259
|
+
const derivatives = options.derivativeMode === 'capture'
|
|
260
|
+
? await captureDerivativeSnapshots({
|
|
261
|
+
actorUserId: options.actorUserId,
|
|
262
|
+
context: options.context,
|
|
263
|
+
derivativeTargets: options.derivativeTargets,
|
|
264
|
+
dependencies: options.dependencies,
|
|
265
|
+
})
|
|
266
|
+
: await assertDerivativeBaselines({
|
|
267
|
+
actorUserId: options.actorUserId,
|
|
268
|
+
context: options.context,
|
|
269
|
+
derivativeTargets: options.derivativeTargets,
|
|
270
|
+
dependencies: options.dependencies,
|
|
271
|
+
});
|
|
272
|
+
assertDerivativeCensusBindings({
|
|
273
|
+
actorUserId: options.actorUserId,
|
|
274
|
+
currentRows: options.currentRows,
|
|
275
|
+
derivativeSnapshots: derivatives,
|
|
276
|
+
});
|
|
277
|
+
return {
|
|
278
|
+
projected_rows: projected,
|
|
279
|
+
support_snapshots: support,
|
|
280
|
+
derivative_snapshots: derivatives,
|
|
281
|
+
derivative_mode: options.derivativeMode,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=dataset-maintenance-protected-before.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-maintenance-protected-before.js","sourceRoot":"","sources":["../../../src/lib/dataset-maintenance-protected-before.ts"],"names":[],"mappings":"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","sourcesContent":["import { loadMaintenanceDesiredPayload } from './dataset-maintenance-alias-request.js';\nimport {\n MAINTENANCE_SCAN_TABLES,\n maintenanceRowKey,\n sha256Json,\n snapshotRemoteRow,\n type DatasetMaintenancePlan,\n type DatasetMaintenanceRemoteRow,\n type DatasetMaintenanceRowSnapshot,\n} from './dataset-maintenance-contract.js';\nimport {\n PROTECTED_EXECUTION_COUNTS,\n parseProtectedDerivativeSnapshot,\n type ProtectedDerivativeSnapshot,\n type ProtectedDerivativeTarget,\n} from './dataset-maintenance-protected-contract.js';\nimport { maintenanceProjectedReferenceFingerprint } from './dataset-maintenance-plan.js';\nimport { isSnapshotCompletenessCompatible } from './dataset-maintenance-pagination.js';\nimport {\n fetchMaintenanceDerivativeSnapshot,\n fetchMaintenanceExactRows,\n type DatasetMaintenanceRemoteContext,\n} from './dataset-maintenance-remote.js';\nimport { CliError } from './errors.js';\n\nconst DERIVATIVE_READ_CONCURRENCY = 5;\n\nexport type ProtectedDerivativeSnapshotTarget = Omit<\n ProtectedDerivativeTarget,\n 'baseline_snapshot_sha256'\n> & {\n baseline_snapshot_sha256?: string;\n};\n\nexport type ProtectedBeforeReadDependencies = {\n fetchExactRows: typeof fetchMaintenanceExactRows;\n fetchDerivativeSnapshot: typeof fetchMaintenanceDerivativeSnapshot;\n parseDerivativeSnapshot: typeof parseProtectedDerivativeSnapshot;\n};\n\nexport type ProtectedBeforeReadDependencyOverrides = Partial<ProtectedBeforeReadDependencies>;\n\nexport type ProtectedBeforeValidationResult = {\n projected_rows: DatasetMaintenanceRemoteRow[];\n support_snapshots: DatasetMaintenanceRowSnapshot[];\n derivative_snapshots: ProtectedDerivativeSnapshot[];\n derivative_mode: 'capture' | 'compare';\n};\n\ntype ProtectedBeforeBaseOptions = {\n plan: DatasetMaintenancePlan;\n planDir: string;\n actorUserId: string;\n currentRows: DatasetMaintenanceRemoteRow[];\n completeness: unknown;\n context: DatasetMaintenanceRemoteContext;\n dependencies?: ProtectedBeforeReadDependencyOverrides;\n};\n\nexport type ValidateProtectedBeforeStateOptions = ProtectedBeforeBaseOptions &\n (\n | {\n derivativeMode: 'capture';\n derivativeTargets: ProtectedDerivativeSnapshotTarget[];\n }\n | {\n derivativeMode: 'compare';\n derivativeTargets: ProtectedDerivativeTarget[];\n }\n );\n\nfunction readDependencies(\n overrides: ProtectedBeforeReadDependencyOverrides = {},\n): ProtectedBeforeReadDependencies {\n return {\n fetchExactRows: overrides.fetchExactRows ?? fetchMaintenanceExactRows,\n fetchDerivativeSnapshot:\n overrides.fetchDerivativeSnapshot ?? fetchMaintenanceDerivativeSnapshot,\n parseDerivativeSnapshot: overrides.parseDerivativeSnapshot ?? parseProtectedDerivativeSnapshot,\n };\n}\n\nfunction actorMismatch(message: string): never {\n throw new CliError(message, {\n code: 'DATASET_MAINTENANCE_PROTECTED_ACTOR_MISMATCH',\n exitCode: 1,\n });\n}\n\nfunction assertPlanActor(plan: DatasetMaintenancePlan, actorUserId: string): void {\n if (\n !actorUserId.trim() ||\n plan.account.user_id !== actorUserId ||\n plan.actions.some((action) => action.expected_user_id !== actorUserId)\n ) {\n actorMismatch('Protected preparation plan and actions must belong to the exact actor.');\n }\n}\n\nfunction assertReadContextActor(\n context: DatasetMaintenanceRemoteContext,\n actorUserId: string,\n): void {\n if (context.account.user_id !== actorUserId) {\n actorMismatch('Authenticated read context does not match the protected preparation actor.');\n }\n}\n\nexport function projectedRows(options: {\n plan: DatasetMaintenancePlan;\n planDir: string;\n currentRows: DatasetMaintenanceRemoteRow[];\n}): DatasetMaintenanceRemoteRow[] {\n const projected = new Map(options.currentRows.map((row) => [maintenanceRowKey(row), { ...row }]));\n for (const action of options.plan.actions) {\n const key = maintenanceRowKey(action);\n const row = projected.get(key);\n if (row) {\n projected.set(key, {\n ...row,\n json_ordered: loadMaintenanceDesiredPayload(options.planDir, action),\n });\n }\n }\n return [...projected.values()].sort((left, right) =>\n maintenanceRowKey(left).localeCompare(maintenanceRowKey(right)),\n );\n}\n\nexport function assertStrictBeforeState(options: {\n plan: DatasetMaintenancePlan;\n planDir: string;\n actorUserId: string;\n currentRows: DatasetMaintenanceRemoteRow[];\n completeness: unknown;\n}): DatasetMaintenanceRemoteRow[] {\n const { plan } = options;\n assertPlanActor(plan, options.actorUserId);\n if (\n !plan.snapshot_completeness ||\n !isSnapshotCompletenessCompatible(\n options.completeness,\n plan.snapshot_completeness,\n MAINTENANCE_SCAN_TABLES,\n )\n ) {\n throw new CliError('Production RLS census does not match the frozen complete snapshot.', {\n code: 'DATASET_MAINTENANCE_PROTECTED_SNAPSHOT_INCOMPLETE',\n exitCode: 1,\n });\n }\n\n const snapshots = options.currentRows\n .map(snapshotRemoteRow)\n .sort((left, right) => maintenanceRowKey(left).localeCompare(maintenanceRowKey(right)));\n if (sha256Json(snapshots) !== plan.visible_snapshot_sha256) {\n throw new CliError('Production RLS visible snapshot drifted after the freeze.', {\n code: 'DATASET_MAINTENANCE_PROTECTED_VISIBLE_SNAPSHOT_DRIFT',\n exitCode: 1,\n });\n }\n\n const expectedKeys = new Set([\n ...plan.actions.map(maintenanceRowKey),\n ...plan.protected_rows.map(maintenanceRowKey),\n ]);\n const currentKeys = options.currentRows.map(maintenanceRowKey);\n if (\n expectedKeys.size !== options.currentRows.length ||\n new Set(currentKeys).size !== options.currentRows.length ||\n options.currentRows.some(\n (row) => row.user_id !== options.actorUserId || !expectedKeys.has(maintenanceRowKey(row)),\n )\n ) {\n throw new CliError('Production owner account contains missing or unexpected rows.', {\n code: 'DATASET_MAINTENANCE_PROTECTED_ACCOUNT_CENSUS_DRIFT',\n exitCode: 1,\n });\n }\n\n const current = new Map(options.currentRows.map((row) => [maintenanceRowKey(row), row]));\n for (const row of plan.protected_rows) {\n const observed = current.get(maintenanceRowKey(row))!;\n if (snapshotRemoteRow(observed).row_sha256 !== row.row_sha256) {\n throw new CliError(`Protected row drifted: ${row.id}`, {\n code: 'DATASET_MAINTENANCE_PROTECTED_ROW_DRIFT',\n exitCode: 1,\n details: row,\n });\n }\n }\n\n for (const action of plan.actions) {\n const observed = current.get(maintenanceRowKey(action))!;\n if (\n !action.before ||\n observed.user_id !== options.actorUserId ||\n observed.user_id !== action.expected_user_id ||\n observed.state_code !== 0 ||\n snapshotRemoteRow(observed).row_sha256 !== action.before.row_sha256\n ) {\n throw new CliError(\n `Action row is no longer in the exact frozen before state: ${action.action_id}`,\n {\n code: 'DATASET_MAINTENANCE_PROTECTED_ACTION_DRIFT',\n exitCode: 1,\n },\n );\n }\n }\n\n const finalRows = projectedRows({\n plan,\n planDir: options.planDir,\n currentRows: options.currentRows,\n });\n if (\n sha256Json(maintenanceProjectedReferenceFingerprint(finalRows)) !==\n plan.projected_reference_sha256\n ) {\n throw new CliError('Projected reference closure drifted before protected execution.', {\n code: 'DATASET_MAINTENANCE_PROTECTED_REFERENCE_DRIFT',\n exitCode: 1,\n });\n }\n return finalRows;\n}\n\nexport async function assertSupportSnapshots(options: {\n plan: DatasetMaintenancePlan;\n actorUserId: string;\n context: DatasetMaintenanceRemoteContext;\n dependencies?: ProtectedBeforeReadDependencyOverrides;\n}): Promise<DatasetMaintenanceRowSnapshot[]> {\n assertPlanActor(options.plan, options.actorUserId);\n assertReadContextActor(options.context, options.actorUserId);\n const dependencies = readDependencies(options.dependencies);\n const verified: DatasetMaintenanceRowSnapshot[] = [];\n for (const batch of options.plan.alias_batches ?? []) {\n for (const snapshot of [\n batch.target_snapshots.unitgroup,\n batch.target_snapshots.flowproperty,\n batch.target_snapshots.source_unitgroup,\n ]) {\n if (!snapshot) {\n throw new CliError(`Alias support snapshot is absent for ${batch.batch_id}.`, {\n code: 'DATASET_MAINTENANCE_PROTECTED_SUPPORT_DRIFT',\n exitCode: 1,\n });\n }\n const exact = await dependencies.fetchExactRows({\n context: options.context,\n table: snapshot.table,\n id: snapshot.id,\n version: snapshot.version,\n });\n const row = exact.rows.length === 1 ? exact.rows[0] : null;\n if (\n !row ||\n row.user_id !== options.actorUserId ||\n row.state_code !== 0 ||\n snapshotRemoteRow(row).row_sha256 !== snapshot.row_sha256\n ) {\n throw new CliError(`Alias support row drifted for ${batch.batch_id}.`, {\n code: 'DATASET_MAINTENANCE_PROTECTED_SUPPORT_DRIFT',\n exitCode: 1,\n details: { table: snapshot.table, id: snapshot.id, version: snapshot.version },\n });\n }\n verified.push(snapshotRemoteRow(row));\n }\n }\n return verified;\n}\n\nfunction derivativeTargetKey(target: ProtectedDerivativeSnapshotTarget): string {\n return `${target.table}\\u0000${target.id}\\u0000${target.version}`;\n}\n\nfunction stableDerivativeTargets(\n targets: ProtectedDerivativeSnapshotTarget[],\n actorUserId: string,\n): ProtectedDerivativeSnapshotTarget[] {\n const keys = targets.map(derivativeTargetKey);\n if (\n targets.length !== PROTECTED_EXECUTION_COUNTS.derivative_target_count ||\n new Set(keys).size !== targets.length ||\n targets.filter((target) => target.table === 'flows').length !==\n PROTECTED_EXECUTION_COUNTS.flow_count ||\n targets.filter((target) => target.table === 'processes').length !==\n PROTECTED_EXECUTION_COUNTS.process_count ||\n targets.some(\n (target) =>\n !target.id.trim() ||\n !target.version.trim() ||\n target.user_id !== actorUserId ||\n target.state_code !== 0,\n )\n ) {\n throw new CliError('Protected derivative targets are incomplete, duplicate, or foreign.', {\n code: 'DATASET_MAINTENANCE_PROTECTED_DERIVATIVE_TARGET_INVALID',\n exitCode: 1,\n });\n }\n return [...targets].sort((left, right) =>\n derivativeTargetKey(left).localeCompare(derivativeTargetKey(right)),\n );\n}\n\nexport async function captureDerivativeSnapshots(options: {\n actorUserId: string;\n context: DatasetMaintenanceRemoteContext;\n derivativeTargets: ProtectedDerivativeSnapshotTarget[];\n dependencies?: ProtectedBeforeReadDependencyOverrides;\n}): Promise<ProtectedDerivativeSnapshot[]> {\n assertReadContextActor(options.context, options.actorUserId);\n const targets = stableDerivativeTargets(options.derivativeTargets, options.actorUserId);\n const dependencies = readDependencies(options.dependencies);\n const verified: ProtectedDerivativeSnapshot[] = [];\n for (let offset = 0; offset < targets.length; offset += DERIVATIVE_READ_CONCURRENCY) {\n const chunk = targets.slice(offset, offset + DERIVATIVE_READ_CONCURRENCY);\n const snapshots = await Promise.all(\n chunk.map(async (target) =>\n dependencies.parseDerivativeSnapshot(\n await dependencies.fetchDerivativeSnapshot({\n context: options.context,\n table: target.table,\n id: target.id,\n version: target.version,\n }),\n {\n table: target.table,\n id: target.id,\n version: target.version,\n userId: target.user_id,\n },\n ),\n ),\n );\n verified.push(...snapshots);\n }\n return verified;\n}\n\nexport async function assertDerivativeBaselines(options: {\n actorUserId: string;\n context: DatasetMaintenanceRemoteContext;\n derivativeTargets: ProtectedDerivativeTarget[];\n dependencies?: ProtectedBeforeReadDependencyOverrides;\n}): Promise<ProtectedDerivativeSnapshot[]> {\n const snapshots = await captureDerivativeSnapshots({\n actorUserId: options.actorUserId,\n context: options.context,\n derivativeTargets: options.derivativeTargets,\n dependencies: options.dependencies,\n });\n const expectedByKey = new Map(\n options.derivativeTargets.map((target) => [derivativeTargetKey(target), target]),\n );\n for (const snapshot of snapshots) {\n const target = expectedByKey.get(derivativeTargetKey(snapshot));\n if (!target || snapshot.snapshot_sha256 !== target.baseline_snapshot_sha256) {\n throw new CliError('A protected derivative baseline drifted before preflight.', {\n code: 'DATASET_MAINTENANCE_PROTECTED_DERIVATIVE_BASELINE_DRIFT',\n exitCode: 1,\n details: { table: snapshot.table, id: snapshot.id, version: snapshot.version },\n });\n }\n }\n return snapshots;\n}\n\nexport function assertDerivativeCensusBindings(options: {\n actorUserId: string;\n currentRows: DatasetMaintenanceRemoteRow[];\n derivativeSnapshots: ProtectedDerivativeSnapshot[];\n}): void {\n const current = new Map(\n options.currentRows\n .filter((row) => row.table === 'flows' || row.table === 'processes')\n .map((row) => [maintenanceRowKey(row), row]),\n );\n for (const snapshot of options.derivativeSnapshots) {\n const row = current.get(maintenanceRowKey(snapshot));\n if (\n !row ||\n row.user_id !== options.actorUserId ||\n row.state_code !== 0 ||\n row.modified_at !== snapshot.modified_at\n ) {\n throw new CliError(\n 'A protected derivative snapshot does not match the immediately preceding account census.',\n {\n code: 'DATASET_MAINTENANCE_PROTECTED_DERIVATIVE_CENSUS_DRIFT',\n exitCode: 1,\n details: {\n table: snapshot.table,\n id: snapshot.id,\n version: snapshot.version,\n census_modified_at: row?.modified_at ?? null,\n derivative_modified_at: snapshot.modified_at,\n },\n },\n );\n }\n }\n}\n\nexport async function validateProtectedBeforeState(\n options: ValidateProtectedBeforeStateOptions,\n): Promise<ProtectedBeforeValidationResult> {\n const projected = assertStrictBeforeState({\n plan: options.plan,\n planDir: options.planDir,\n actorUserId: options.actorUserId,\n currentRows: options.currentRows,\n completeness: options.completeness,\n });\n const support = await assertSupportSnapshots({\n plan: options.plan,\n actorUserId: options.actorUserId,\n context: options.context,\n dependencies: options.dependencies,\n });\n const derivatives =\n options.derivativeMode === 'capture'\n ? await captureDerivativeSnapshots({\n actorUserId: options.actorUserId,\n context: options.context,\n derivativeTargets: options.derivativeTargets,\n dependencies: options.dependencies,\n })\n : await assertDerivativeBaselines({\n actorUserId: options.actorUserId,\n context: options.context,\n derivativeTargets: options.derivativeTargets,\n dependencies: options.dependencies,\n });\n assertDerivativeCensusBindings({\n actorUserId: options.actorUserId,\n currentRows: options.currentRows,\n derivativeSnapshots: derivatives,\n });\n return {\n projected_rows: projected,\n support_snapshots: support,\n derivative_snapshots: derivatives,\n derivative_mode: options.derivativeMode,\n };\n}\n"]}
|