@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,160 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readProtectedJsonArtifact, readProtectedTextArtifact, materializePrivateArtifactDirectoryAtomically, writePrivateImmutableJson, writePrivateImmutableText, } from './dataset-maintenance-protected-artifacts.js';
|
|
3
|
+
import { sha256Text, stableJsonText } from './dataset-maintenance-contract.js';
|
|
4
|
+
import { parseProtectedFreeze } from './dataset-maintenance-protected-contract.js';
|
|
5
|
+
import { assertProtectedProductionProjectRef, parseProtectedApprovalRequest, sealProtectedApproval, } from './dataset-maintenance-protected-preparation.js';
|
|
6
|
+
import { CliError } from './errors.js';
|
|
7
|
+
export const PROTECTED_APPROVAL_SEAL_ARTIFACTS = {
|
|
8
|
+
human_approval_text: 'protected-human-approval.txt',
|
|
9
|
+
approval: 'protected-approval.json',
|
|
10
|
+
report: 'protected-approval-seal-report.json',
|
|
11
|
+
};
|
|
12
|
+
const DEFAULT_SEAL_DEPENDENCIES = {
|
|
13
|
+
readJson: readProtectedJsonArtifact,
|
|
14
|
+
readText: readProtectedTextArtifact,
|
|
15
|
+
parseFreeze: parseProtectedFreeze,
|
|
16
|
+
parseApprovalRequest: parseProtectedApprovalRequest,
|
|
17
|
+
sealApproval: sealProtectedApproval,
|
|
18
|
+
writeJson: writePrivateImmutableJson,
|
|
19
|
+
writeText: writePrivateImmutableText,
|
|
20
|
+
materializeArtifacts: materializePrivateArtifactDirectoryAtomically,
|
|
21
|
+
};
|
|
22
|
+
const SHA256 = /^[a-f0-9]{64}$/u;
|
|
23
|
+
function requiredToken(value, label) {
|
|
24
|
+
if (!value || value.trim() !== value) {
|
|
25
|
+
throw new CliError(`${label} must be a non-empty canonical string.`, {
|
|
26
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_SEAL_INPUT_INVALID',
|
|
27
|
+
exitCode: 2,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function requiredHash(value, label) {
|
|
33
|
+
const token = requiredToken(value, label);
|
|
34
|
+
if (!SHA256.test(token)) {
|
|
35
|
+
throw new CliError(`${label} must be a lowercase SHA-256 digest.`, {
|
|
36
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_SEAL_INPUT_INVALID',
|
|
37
|
+
exitCode: 2,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return token;
|
|
41
|
+
}
|
|
42
|
+
function assertCanonicalJsonArtifact(options) {
|
|
43
|
+
if (options.text !== `${stableJsonText(options.value)}\n`) {
|
|
44
|
+
throw new CliError(`${options.label} must be canonical JSON with one final newline.`, {
|
|
45
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_SEAL_NONCANONICAL',
|
|
46
|
+
exitCode: 2,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function artifactPaths(outDir) {
|
|
51
|
+
const resolved = path.resolve(outDir);
|
|
52
|
+
return Object.fromEntries(Object.entries(PROTECTED_APPROVAL_SEAL_ARTIFACTS).map(([key, name]) => [
|
|
53
|
+
key,
|
|
54
|
+
path.join(resolved, name),
|
|
55
|
+
]));
|
|
56
|
+
}
|
|
57
|
+
export async function sealDatasetMaintenanceProtectedApproval(options, dependencies = DEFAULT_SEAL_DEPENDENCIES) {
|
|
58
|
+
const freezePath = requiredToken(options.freezePath, 'freezePath');
|
|
59
|
+
const approvalRequestPath = requiredToken(options.approvalRequestPath, 'approvalRequestPath');
|
|
60
|
+
const humanApprovalPath = requiredToken(options.humanApprovalPath, 'humanApprovalPath');
|
|
61
|
+
const outDir = requiredToken(options.outDir, 'outDir');
|
|
62
|
+
const approveFreezeFile = requiredHash(options.approveFreezeFile, 'approveFreezeFile');
|
|
63
|
+
const approveRequest = requiredHash(options.approveRequest, 'approveRequest');
|
|
64
|
+
const approveText = requiredHash(options.approveText, 'approveText');
|
|
65
|
+
const confirm = requiredToken(options.confirm, 'confirm');
|
|
66
|
+
const approvedAtUtc = requiredToken(options.approvedAtUtc, 'approvedAtUtc');
|
|
67
|
+
const freezeArtifact = dependencies.readJson({
|
|
68
|
+
filePath: freezePath,
|
|
69
|
+
label: 'Protected execution freeze',
|
|
70
|
+
});
|
|
71
|
+
const freeze = dependencies.parseFreeze(freezeArtifact.value);
|
|
72
|
+
assertProtectedProductionProjectRef(freeze.project_ref);
|
|
73
|
+
assertCanonicalJsonArtifact({
|
|
74
|
+
label: 'Protected execution freeze',
|
|
75
|
+
text: freezeArtifact.text,
|
|
76
|
+
value: freeze,
|
|
77
|
+
});
|
|
78
|
+
if (approveFreezeFile !== freezeArtifact.file_sha256) {
|
|
79
|
+
throw new CliError('Explicit freeze file hash does not match the supplied freeze bytes.', {
|
|
80
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_SEAL_FREEZE_MISMATCH',
|
|
81
|
+
exitCode: 1,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const requestArtifact = dependencies.readJson({
|
|
85
|
+
filePath: approvalRequestPath,
|
|
86
|
+
label: 'Protected approval request',
|
|
87
|
+
});
|
|
88
|
+
const request = dependencies.parseApprovalRequest(requestArtifact.value);
|
|
89
|
+
assertCanonicalJsonArtifact({
|
|
90
|
+
label: 'Protected approval request',
|
|
91
|
+
text: requestArtifact.text,
|
|
92
|
+
value: request,
|
|
93
|
+
});
|
|
94
|
+
const humanApproval = dependencies.readText(humanApprovalPath);
|
|
95
|
+
const approval = dependencies.sealApproval({
|
|
96
|
+
approvalRequest: request,
|
|
97
|
+
freeze,
|
|
98
|
+
freezeFileSha256: freezeArtifact.file_sha256,
|
|
99
|
+
humanApprovalText: humanApproval.text,
|
|
100
|
+
approveRequestSha256: approveRequest,
|
|
101
|
+
approveTextSha256: approveText,
|
|
102
|
+
approvedAtUtc,
|
|
103
|
+
confirmAccountEmail: confirm,
|
|
104
|
+
});
|
|
105
|
+
const paths = artifactPaths(outDir);
|
|
106
|
+
const report = {
|
|
107
|
+
schema_version: 'dataset-alias-protected-approval-seal-report.v1',
|
|
108
|
+
generated_at_utc: (options.now ?? new Date()).toISOString(),
|
|
109
|
+
approval_authority_at_utc: approvedAtUtc,
|
|
110
|
+
status: 'sealed',
|
|
111
|
+
execution_submitted: false,
|
|
112
|
+
environment: 'production',
|
|
113
|
+
project_ref: freeze.project_ref,
|
|
114
|
+
account: freeze.account,
|
|
115
|
+
plan_sha256: freeze.plan.plan_sha256,
|
|
116
|
+
operation_id: freeze.plan.operation_id,
|
|
117
|
+
freeze_file_sha256: freezeArtifact.file_sha256,
|
|
118
|
+
freeze_sha256: freeze.freeze_sha256,
|
|
119
|
+
approval_request_file_sha256: requestArtifact.file_sha256,
|
|
120
|
+
approval_request_sha256: request.request_sha256,
|
|
121
|
+
approval_text_sha256: request.approval_text_sha256,
|
|
122
|
+
approval_file_sha256: approval.file_sha256,
|
|
123
|
+
approval_identity_sha256: approval.value.approval_identity_sha256,
|
|
124
|
+
assertions: {
|
|
125
|
+
byte_exact_human_text: true,
|
|
126
|
+
explicit_freeze_file_hash: true,
|
|
127
|
+
explicit_request_hash: true,
|
|
128
|
+
explicit_text_hash: true,
|
|
129
|
+
explicit_account_confirmation: true,
|
|
130
|
+
authentication_calls: 0,
|
|
131
|
+
network_calls: 0,
|
|
132
|
+
database_calls: 0,
|
|
133
|
+
preflight_calls: 0,
|
|
134
|
+
gate_calls: 0,
|
|
135
|
+
admission_calls: 0,
|
|
136
|
+
execution_calls: 0,
|
|
137
|
+
},
|
|
138
|
+
artifacts: paths,
|
|
139
|
+
artifact_sha256: {
|
|
140
|
+
human_approval_text: sha256Text(humanApproval.text),
|
|
141
|
+
approval: approval.file_sha256,
|
|
142
|
+
report: null,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
dependencies.materializeArtifacts(outDir, (stagingDirectory) => {
|
|
146
|
+
const staging = artifactPaths(stagingDirectory);
|
|
147
|
+
dependencies.writeText(staging.human_approval_text, humanApproval.text);
|
|
148
|
+
dependencies.writeText(staging.approval, approval.canonical_file_text);
|
|
149
|
+
dependencies.writeJson(staging.report, report);
|
|
150
|
+
});
|
|
151
|
+
return report;
|
|
152
|
+
}
|
|
153
|
+
export const __testInternals = {
|
|
154
|
+
DEFAULT_SEAL_DEPENDENCIES,
|
|
155
|
+
artifactPaths,
|
|
156
|
+
assertCanonicalJsonArtifact,
|
|
157
|
+
requiredHash,
|
|
158
|
+
requiredToken,
|
|
159
|
+
};
|
|
160
|
+
//# sourceMappingURL=dataset-maintenance-protected-seal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-maintenance-protected-seal.js","sourceRoot":"","sources":["../../../src/lib/dataset-maintenance-protected-seal.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\nimport {\n readProtectedJsonArtifact,\n readProtectedTextArtifact,\n materializePrivateArtifactDirectoryAtomically,\n writePrivateImmutableJson,\n writePrivateImmutableText,\n} from './dataset-maintenance-protected-artifacts.js';\nimport { sha256Text, stableJsonText } from './dataset-maintenance-contract.js';\nimport { parseProtectedFreeze } from './dataset-maintenance-protected-contract.js';\nimport {\n assertProtectedProductionProjectRef,\n parseProtectedApprovalRequest,\n sealProtectedApproval,\n} from './dataset-maintenance-protected-preparation.js';\nimport { CliError } from './errors.js';\n\nexport const PROTECTED_APPROVAL_SEAL_ARTIFACTS = {\n human_approval_text: 'protected-human-approval.txt',\n approval: 'protected-approval.json',\n report: 'protected-approval-seal-report.json',\n} as const;\n\nexport type SealDatasetMaintenanceProtectedApprovalOptions = {\n freezePath: string;\n approvalRequestPath: string;\n humanApprovalPath: string;\n outDir: string;\n approveFreezeFile: string;\n approveRequest: string;\n approveText: string;\n confirm: string;\n approvedAtUtc: string;\n now?: Date;\n};\n\nexport type DatasetMaintenanceProtectedApprovalSealReport = {\n schema_version: 'dataset-alias-protected-approval-seal-report.v1';\n generated_at_utc: string;\n approval_authority_at_utc: string;\n status: 'sealed';\n execution_submitted: false;\n environment: 'production';\n project_ref: string;\n account: { user_id: string; email: string };\n plan_sha256: string;\n operation_id: string;\n freeze_file_sha256: string;\n freeze_sha256: string;\n approval_request_file_sha256: string;\n approval_request_sha256: string;\n approval_text_sha256: string;\n approval_file_sha256: string;\n approval_identity_sha256: string;\n assertions: {\n byte_exact_human_text: true;\n explicit_freeze_file_hash: true;\n explicit_request_hash: true;\n explicit_text_hash: true;\n explicit_account_confirmation: true;\n authentication_calls: 0;\n network_calls: 0;\n database_calls: 0;\n preflight_calls: 0;\n gate_calls: 0;\n admission_calls: 0;\n execution_calls: 0;\n };\n artifacts: {\n human_approval_text: string;\n approval: string;\n report: string;\n };\n artifact_sha256: {\n human_approval_text: string;\n approval: string;\n report: null;\n };\n};\n\ntype SealProtectedDependencies = {\n readJson: typeof readProtectedJsonArtifact;\n readText: typeof readProtectedTextArtifact;\n parseFreeze: typeof parseProtectedFreeze;\n parseApprovalRequest: typeof parseProtectedApprovalRequest;\n sealApproval: typeof sealProtectedApproval;\n writeJson: typeof writePrivateImmutableJson;\n writeText: typeof writePrivateImmutableText;\n materializeArtifacts: typeof materializePrivateArtifactDirectoryAtomically;\n};\n\nconst DEFAULT_SEAL_DEPENDENCIES: SealProtectedDependencies = {\n readJson: readProtectedJsonArtifact,\n readText: readProtectedTextArtifact,\n parseFreeze: parseProtectedFreeze,\n parseApprovalRequest: parseProtectedApprovalRequest,\n sealApproval: sealProtectedApproval,\n writeJson: writePrivateImmutableJson,\n writeText: writePrivateImmutableText,\n materializeArtifacts: materializePrivateArtifactDirectoryAtomically,\n};\n\nconst SHA256 = /^[a-f0-9]{64}$/u;\n\nfunction requiredToken(value: string, label: string): string {\n if (!value || value.trim() !== value) {\n throw new CliError(`${label} must be a non-empty canonical string.`, {\n code: 'DATASET_MAINTENANCE_PROTECTED_SEAL_INPUT_INVALID',\n exitCode: 2,\n });\n }\n return value;\n}\n\nfunction requiredHash(value: string, label: string): string {\n const token = requiredToken(value, label);\n if (!SHA256.test(token)) {\n throw new CliError(`${label} must be a lowercase SHA-256 digest.`, {\n code: 'DATASET_MAINTENANCE_PROTECTED_SEAL_INPUT_INVALID',\n exitCode: 2,\n });\n }\n return token;\n}\n\nfunction assertCanonicalJsonArtifact(options: {\n label: string;\n text: string;\n value: unknown;\n}): void {\n if (options.text !== `${stableJsonText(options.value)}\\n`) {\n throw new CliError(`${options.label} must be canonical JSON with one final newline.`, {\n code: 'DATASET_MAINTENANCE_PROTECTED_SEAL_NONCANONICAL',\n exitCode: 2,\n });\n }\n}\n\nfunction artifactPaths(outDir: string): DatasetMaintenanceProtectedApprovalSealReport['artifacts'] {\n const resolved = path.resolve(outDir);\n return Object.fromEntries(\n Object.entries(PROTECTED_APPROVAL_SEAL_ARTIFACTS).map(([key, name]) => [\n key,\n path.join(resolved, name),\n ]),\n ) as DatasetMaintenanceProtectedApprovalSealReport['artifacts'];\n}\n\nexport async function sealDatasetMaintenanceProtectedApproval(\n options: SealDatasetMaintenanceProtectedApprovalOptions,\n dependencies: SealProtectedDependencies = DEFAULT_SEAL_DEPENDENCIES,\n): Promise<DatasetMaintenanceProtectedApprovalSealReport> {\n const freezePath = requiredToken(options.freezePath, 'freezePath');\n const approvalRequestPath = requiredToken(options.approvalRequestPath, 'approvalRequestPath');\n const humanApprovalPath = requiredToken(options.humanApprovalPath, 'humanApprovalPath');\n const outDir = requiredToken(options.outDir, 'outDir');\n const approveFreezeFile = requiredHash(options.approveFreezeFile, 'approveFreezeFile');\n const approveRequest = requiredHash(options.approveRequest, 'approveRequest');\n const approveText = requiredHash(options.approveText, 'approveText');\n const confirm = requiredToken(options.confirm, 'confirm');\n const approvedAtUtc = requiredToken(options.approvedAtUtc, 'approvedAtUtc');\n\n const freezeArtifact = dependencies.readJson({\n filePath: freezePath,\n label: 'Protected execution freeze',\n });\n const freeze = dependencies.parseFreeze(freezeArtifact.value);\n assertProtectedProductionProjectRef(freeze.project_ref);\n assertCanonicalJsonArtifact({\n label: 'Protected execution freeze',\n text: freezeArtifact.text,\n value: freeze,\n });\n if (approveFreezeFile !== freezeArtifact.file_sha256) {\n throw new CliError('Explicit freeze file hash does not match the supplied freeze bytes.', {\n code: 'DATASET_MAINTENANCE_PROTECTED_SEAL_FREEZE_MISMATCH',\n exitCode: 1,\n });\n }\n\n const requestArtifact = dependencies.readJson({\n filePath: approvalRequestPath,\n label: 'Protected approval request',\n });\n const request = dependencies.parseApprovalRequest(requestArtifact.value);\n assertCanonicalJsonArtifact({\n label: 'Protected approval request',\n text: requestArtifact.text,\n value: request,\n });\n const humanApproval = dependencies.readText(humanApprovalPath);\n const approval = dependencies.sealApproval({\n approvalRequest: request,\n freeze,\n freezeFileSha256: freezeArtifact.file_sha256,\n humanApprovalText: humanApproval.text,\n approveRequestSha256: approveRequest,\n approveTextSha256: approveText,\n approvedAtUtc,\n confirmAccountEmail: confirm,\n });\n const paths = artifactPaths(outDir);\n const report: DatasetMaintenanceProtectedApprovalSealReport = {\n schema_version: 'dataset-alias-protected-approval-seal-report.v1',\n generated_at_utc: (options.now ?? new Date()).toISOString(),\n approval_authority_at_utc: approvedAtUtc,\n status: 'sealed',\n execution_submitted: false,\n environment: 'production',\n project_ref: freeze.project_ref,\n account: freeze.account,\n plan_sha256: freeze.plan.plan_sha256,\n operation_id: freeze.plan.operation_id,\n freeze_file_sha256: freezeArtifact.file_sha256,\n freeze_sha256: freeze.freeze_sha256,\n approval_request_file_sha256: requestArtifact.file_sha256,\n approval_request_sha256: request.request_sha256,\n approval_text_sha256: request.approval_text_sha256,\n approval_file_sha256: approval.file_sha256,\n approval_identity_sha256: approval.value.approval_identity_sha256,\n assertions: {\n byte_exact_human_text: true,\n explicit_freeze_file_hash: true,\n explicit_request_hash: true,\n explicit_text_hash: true,\n explicit_account_confirmation: true,\n authentication_calls: 0,\n network_calls: 0,\n database_calls: 0,\n preflight_calls: 0,\n gate_calls: 0,\n admission_calls: 0,\n execution_calls: 0,\n },\n artifacts: paths,\n artifact_sha256: {\n human_approval_text: sha256Text(humanApproval.text),\n approval: approval.file_sha256,\n report: null,\n },\n };\n dependencies.materializeArtifacts(outDir, (stagingDirectory) => {\n const staging = artifactPaths(stagingDirectory);\n dependencies.writeText(staging.human_approval_text, humanApproval.text);\n dependencies.writeText(staging.approval, approval.canonical_file_text);\n dependencies.writeJson(staging.report, report);\n });\n return report;\n}\n\nexport const __testInternals = {\n DEFAULT_SEAL_DEPENDENCIES,\n artifactPaths,\n assertCanonicalJsonArtifact,\n requiredHash,\n requiredToken,\n};\n"]}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { isJsonObject } from './dataset-maintenance-contract.js';
|
|
2
|
+
import { CliError } from './errors.js';
|
|
3
|
+
export const PROTECTED_TOOLCHAIN_EVIDENCE_SCHEMA = 'dataset-alias-protected-toolchain-evidence.v1';
|
|
4
|
+
const SHA256 = /^[a-f0-9]{64}$/u;
|
|
5
|
+
const GIT_SHA = /^[a-f0-9]{40}$/u;
|
|
6
|
+
const VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
|
|
7
|
+
const WORKSPACE_ISSUE = /^https:\/\/github\.com\/tiangong-lca\/workspace\/issues\/\d+$/u;
|
|
8
|
+
function invalid(message) {
|
|
9
|
+
throw new CliError(message, {
|
|
10
|
+
code: 'DATASET_MAINTENANCE_PROTECTED_TOOLCHAIN_INVALID',
|
|
11
|
+
exitCode: 2,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function token(value, label) {
|
|
15
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
16
|
+
return invalid(`${label} must be a non-empty string.`);
|
|
17
|
+
}
|
|
18
|
+
return value.trim();
|
|
19
|
+
}
|
|
20
|
+
function exact(value, expected, label) {
|
|
21
|
+
if (value !== expected)
|
|
22
|
+
return invalid(`${label} must equal ${expected}.`);
|
|
23
|
+
return expected;
|
|
24
|
+
}
|
|
25
|
+
function digest(value, label) {
|
|
26
|
+
const result = token(value, label);
|
|
27
|
+
return SHA256.test(result) ? result : invalid(`${label} must be a lowercase SHA-256 digest.`);
|
|
28
|
+
}
|
|
29
|
+
function commit(value, label) {
|
|
30
|
+
const result = token(value, label);
|
|
31
|
+
return GIT_SHA.test(result) ? result : invalid(`${label} must be a lowercase 40-hex git SHA.`);
|
|
32
|
+
}
|
|
33
|
+
export function parseProtectedToolchainEvidence(value, expected) {
|
|
34
|
+
if (!isJsonObject(value) ||
|
|
35
|
+
!isJsonObject(value.database_engine) ||
|
|
36
|
+
!isJsonObject(value.cli) ||
|
|
37
|
+
!isJsonObject(value.workspace)) {
|
|
38
|
+
return invalid('Protected toolchain evidence must contain database, CLI, and workspace objects.');
|
|
39
|
+
}
|
|
40
|
+
exact(value.schema_version, PROTECTED_TOOLCHAIN_EVIDENCE_SCHEMA, 'schema_version');
|
|
41
|
+
exact(value.environment, 'production', 'environment');
|
|
42
|
+
const projectRef = token(value.project_ref, 'project_ref');
|
|
43
|
+
if (projectRef !== expected.projectRef) {
|
|
44
|
+
return invalid('Toolchain evidence does not bind the authenticated production project.');
|
|
45
|
+
}
|
|
46
|
+
const verifiedAtUtc = token(value.verified_at_utc, 'verified_at_utc');
|
|
47
|
+
const verifiedAtMs = Date.parse(verifiedAtUtc);
|
|
48
|
+
if (!Number.isFinite(verifiedAtMs) || new Date(verifiedAtMs).toISOString() !== verifiedAtUtc) {
|
|
49
|
+
return invalid('verified_at_utc must be a canonical UTC ISO timestamp.');
|
|
50
|
+
}
|
|
51
|
+
const packageVersion = token(value.cli.package_version, 'cli.package_version');
|
|
52
|
+
if (!VERSION.test(packageVersion) || packageVersion !== expected.cliVersion) {
|
|
53
|
+
return invalid('Toolchain evidence does not bind the running published CLI version.');
|
|
54
|
+
}
|
|
55
|
+
const integrationIssueUrl = token(value.workspace.integration_issue_url, 'workspace.integration_issue_url');
|
|
56
|
+
if (!WORKSPACE_ISSUE.test(integrationIssueUrl)) {
|
|
57
|
+
return invalid('workspace.integration_issue_url must identify a tracked workspace Issue.');
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
schema_version: PROTECTED_TOOLCHAIN_EVIDENCE_SCHEMA,
|
|
61
|
+
environment: 'production',
|
|
62
|
+
project_ref: projectRef,
|
|
63
|
+
verified_at_utc: verifiedAtUtc,
|
|
64
|
+
database_engine: {
|
|
65
|
+
repository: exact(value.database_engine.repository, 'tiangong-lca/database-engine', 'database_engine.repository'),
|
|
66
|
+
production_main_commit_sha: commit(value.database_engine.production_main_commit_sha, 'database_engine.production_main_commit_sha'),
|
|
67
|
+
production_readback_evidence_sha256: digest(value.database_engine.production_readback_evidence_sha256, 'database_engine.production_readback_evidence_sha256'),
|
|
68
|
+
status: exact(value.database_engine.status, 'released_and_read_back', 'database_engine.status'),
|
|
69
|
+
},
|
|
70
|
+
cli: {
|
|
71
|
+
repository: exact(value.cli.repository, 'tiangong-lca/tiangong-cli', 'cli.repository'),
|
|
72
|
+
package_name: exact(value.cli.package_name, '@tiangong-lca/cli', 'cli.package_name'),
|
|
73
|
+
package_version: packageVersion,
|
|
74
|
+
release_commit_sha: commit(value.cli.release_commit_sha, 'cli.release_commit_sha'),
|
|
75
|
+
release_evidence_sha256: digest(value.cli.release_evidence_sha256, 'cli.release_evidence_sha256'),
|
|
76
|
+
status: exact(value.cli.status, 'published_and_verified', 'cli.status'),
|
|
77
|
+
},
|
|
78
|
+
workspace: {
|
|
79
|
+
repository: exact(value.workspace.repository, 'tiangong-lca/workspace', 'workspace.repository'),
|
|
80
|
+
integration_commit_sha: commit(value.workspace.integration_commit_sha, 'workspace.integration_commit_sha'),
|
|
81
|
+
integration_issue_url: integrationIssueUrl,
|
|
82
|
+
status: exact(value.workspace.status, 'integrated', 'workspace.status'),
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=dataset-maintenance-protected-toolchain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-maintenance-protected-toolchain.js","sourceRoot":"","sources":["../../../src/lib/dataset-maintenance-protected-toolchain.ts"],"names":[],"mappings":"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","sourcesContent":["import { isJsonObject } from './dataset-maintenance-contract.js';\nimport { CliError } from './errors.js';\n\nexport const PROTECTED_TOOLCHAIN_EVIDENCE_SCHEMA =\n 'dataset-alias-protected-toolchain-evidence.v1' as const;\n\nconst SHA256 = /^[a-f0-9]{64}$/u;\nconst GIT_SHA = /^[a-f0-9]{40}$/u;\nconst VERSION = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$/u;\nconst WORKSPACE_ISSUE = /^https:\\/\\/github\\.com\\/tiangong-lca\\/workspace\\/issues\\/\\d+$/u;\n\nexport type DatasetMaintenanceProtectedToolchainEvidence = {\n schema_version: typeof PROTECTED_TOOLCHAIN_EVIDENCE_SCHEMA;\n environment: 'production';\n project_ref: string;\n verified_at_utc: string;\n database_engine: {\n repository: 'tiangong-lca/database-engine';\n production_main_commit_sha: string;\n production_readback_evidence_sha256: string;\n status: 'released_and_read_back';\n };\n cli: {\n repository: 'tiangong-lca/tiangong-cli';\n package_name: '@tiangong-lca/cli';\n package_version: string;\n release_commit_sha: string;\n release_evidence_sha256: string;\n status: 'published_and_verified';\n };\n workspace: {\n repository: 'tiangong-lca/workspace';\n integration_commit_sha: string;\n integration_issue_url: string;\n status: 'integrated';\n };\n};\n\nfunction invalid(message: string): never {\n throw new CliError(message, {\n code: 'DATASET_MAINTENANCE_PROTECTED_TOOLCHAIN_INVALID',\n exitCode: 2,\n });\n}\n\nfunction token(value: unknown, label: string): string {\n if (typeof value !== 'string' || !value.trim()) {\n return invalid(`${label} must be a non-empty string.`);\n }\n return value.trim();\n}\n\nfunction exact<T extends string>(value: unknown, expected: T, label: string): T {\n if (value !== expected) return invalid(`${label} must equal ${expected}.`);\n return expected;\n}\n\nfunction digest(value: unknown, label: string): string {\n const result = token(value, label);\n return SHA256.test(result) ? result : invalid(`${label} must be a lowercase SHA-256 digest.`);\n}\n\nfunction commit(value: unknown, label: string): string {\n const result = token(value, label);\n return GIT_SHA.test(result) ? result : invalid(`${label} must be a lowercase 40-hex git SHA.`);\n}\n\nexport function parseProtectedToolchainEvidence(\n value: unknown,\n expected: { projectRef: string; cliVersion: string },\n): DatasetMaintenanceProtectedToolchainEvidence {\n if (\n !isJsonObject(value) ||\n !isJsonObject(value.database_engine) ||\n !isJsonObject(value.cli) ||\n !isJsonObject(value.workspace)\n ) {\n return invalid(\n 'Protected toolchain evidence must contain database, CLI, and workspace objects.',\n );\n }\n exact(value.schema_version, PROTECTED_TOOLCHAIN_EVIDENCE_SCHEMA, 'schema_version');\n exact(value.environment, 'production', 'environment');\n const projectRef = token(value.project_ref, 'project_ref');\n if (projectRef !== expected.projectRef) {\n return invalid('Toolchain evidence does not bind the authenticated production project.');\n }\n const verifiedAtUtc = token(value.verified_at_utc, 'verified_at_utc');\n const verifiedAtMs = Date.parse(verifiedAtUtc);\n if (!Number.isFinite(verifiedAtMs) || new Date(verifiedAtMs).toISOString() !== verifiedAtUtc) {\n return invalid('verified_at_utc must be a canonical UTC ISO timestamp.');\n }\n const packageVersion = token(value.cli.package_version, 'cli.package_version');\n if (!VERSION.test(packageVersion) || packageVersion !== expected.cliVersion) {\n return invalid('Toolchain evidence does not bind the running published CLI version.');\n }\n const integrationIssueUrl = token(\n value.workspace.integration_issue_url,\n 'workspace.integration_issue_url',\n );\n if (!WORKSPACE_ISSUE.test(integrationIssueUrl)) {\n return invalid('workspace.integration_issue_url must identify a tracked workspace Issue.');\n }\n return {\n schema_version: PROTECTED_TOOLCHAIN_EVIDENCE_SCHEMA,\n environment: 'production',\n project_ref: projectRef,\n verified_at_utc: verifiedAtUtc,\n database_engine: {\n repository: exact(\n value.database_engine.repository,\n 'tiangong-lca/database-engine',\n 'database_engine.repository',\n ),\n production_main_commit_sha: commit(\n value.database_engine.production_main_commit_sha,\n 'database_engine.production_main_commit_sha',\n ),\n production_readback_evidence_sha256: digest(\n value.database_engine.production_readback_evidence_sha256,\n 'database_engine.production_readback_evidence_sha256',\n ),\n status: exact(\n value.database_engine.status,\n 'released_and_read_back',\n 'database_engine.status',\n ),\n },\n cli: {\n repository: exact(value.cli.repository, 'tiangong-lca/tiangong-cli', 'cli.repository'),\n package_name: exact(value.cli.package_name, '@tiangong-lca/cli', 'cli.package_name'),\n package_version: packageVersion,\n release_commit_sha: commit(value.cli.release_commit_sha, 'cli.release_commit_sha'),\n release_evidence_sha256: digest(\n value.cli.release_evidence_sha256,\n 'cli.release_evidence_sha256',\n ),\n status: exact(value.cli.status, 'published_and_verified', 'cli.status'),\n },\n workspace: {\n repository: exact(\n value.workspace.repository,\n 'tiangong-lca/workspace',\n 'workspace.repository',\n ),\n integration_commit_sha: commit(\n value.workspace.integration_commit_sha,\n 'workspace.integration_commit_sha',\n ),\n integration_issue_url: integrationIssueUrl,\n status: exact(value.workspace.status, 'integrated', 'workspace.status'),\n },\n };\n}\n"]}
|