@tiangong-lca/cli 0.0.28 → 0.0.29
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 +85 -2
- package/dist/src/cli.js +544 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-contract.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-flow-identity-approval-claim.js +175 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-approval-claim.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-capture.js +511 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-capture.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-command.js +26 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-command.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-contract.js +784 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-contract.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-execution-contract.js +1317 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-execution-contract.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-freeze.js +342 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-freeze.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-plan.js +900 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-plan.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-recovery.js +688 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-recovery.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-run.js +1369 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-run.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-seal.js +144 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-seal.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-verify.js +377 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-verify.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-wire.js +178 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-wire.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-remote.js +156 -10
- package/dist/src/lib/dataset-maintenance-remote.js.map +1 -1
- package/dist/src/lib/http.js.map +1 -1
- package/dist/src/lib/lca-release.js +683 -0
- package/dist/src/lib/lca-release.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-maintenance-flow-identity-approval-claim.js","sourceRoot":"","sources":["../../../src/lib/dataset-maintenance-flow-identity-approval-claim.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n closeSync,\n existsSync,\n fsyncSync,\n lstatSync,\n mkdirSync,\n openSync,\n readFileSync,\n writeFileSync,\n} from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { stableJsonText } from './dataset-maintenance-contract.js';\nimport { CliError } from './errors.js';\n\nconst HASH = /^[a-f0-9]{64}$/u;\nconst UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/u;\nconst EMAIL = /^[^\\s@]+@[^\\s@]+$/u;\nconst CLAIM_KEYS = [\n 'schema_version',\n 'claimed_at_utc',\n 'approval_kind',\n 'approval_identity_sha256',\n 'execution_identity_sha256',\n 'request_id',\n 'environment',\n 'project_ref',\n 'actor_user_id',\n 'actor_email',\n 'target_visibility',\n 'user_state_claim',\n 'plan_sha256',\n 'freeze_sha256',\n 'canonical_out_dir',\n 'maximum_cli_apply_spawns',\n 'approval_reusable',\n] as const;\n\nexport type FlowIdentityApprovalClaim = {\n schema_version: 'dataset-flow-identity-local-approval-claim.v1';\n claimed_at_utc: string;\n approval_kind: 'initial' | 'recovery';\n approval_identity_sha256: string;\n execution_identity_sha256: string;\n request_id: string;\n environment: 'production';\n project_ref: string;\n actor_user_id: string;\n actor_email: string;\n target_visibility: 'owner_draft';\n user_state_claim: 'authenticated_actor_state_100_plus_own_state_0';\n plan_sha256: string;\n freeze_sha256: string;\n canonical_out_dir: string;\n maximum_cli_apply_spawns: 1;\n approval_reusable: false;\n};\n\nfunction fail(message: string, code: string, details?: unknown): never {\n throw new CliError(message, { code, exitCode: 1, details });\n}\n\nfunction isErrno(error: unknown, code: string): boolean {\n return Boolean(\n error &&\n typeof error === 'object' &&\n 'code' in error &&\n (error as NodeJS.ErrnoException).code === code,\n );\n}\n\nfunction hasPrivateClaimPermissions(mode: number, platform: NodeJS.Platform): boolean {\n // Windows does not expose its ACLs through POSIX permission bits. The claim\n // still has to be a non-symlink regular file and is created exclusively below\n // the current user's state directory; enforce 0600-style bits where they are\n // meaningful.\n return platform === 'win32' || (mode & 0o077) === 0;\n}\n\nfunction rethrowClaimCreateError(error: unknown, claimPath: string): never {\n if (isErrno(error, 'EEXIST')) {\n fail(\n 'This exact flow identity approval was already claimed by a CLI wrapper. Only read-only status or a newly frozen recovery approval is allowed.',\n 'DATASET_FLOW_IDENTITY_APPROVAL_ALREADY_CLAIMED',\n { claim_path: claimPath },\n );\n }\n throw error;\n}\n\nfunction validateClaim(value: unknown, expectedIdentity: string): FlowIdentityApprovalClaim {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n fail(\n 'Existing flow identity approval claim is malformed or foreign.',\n 'DATASET_FLOW_IDENTITY_APPROVAL_CLAIM_INVALID',\n );\n }\n const claim = value as Partial<FlowIdentityApprovalClaim>;\n const actualKeys = Object.keys(claim).sort();\n const expectedKeys = [...CLAIM_KEYS].sort();\n const claimedAt = String(claim.claimed_at_utc ?? '');\n const canonicalOutDir = String(claim.canonical_out_dir ?? '');\n if (\n actualKeys.length !== expectedKeys.length ||\n actualKeys.some((key, index) => key !== expectedKeys[index]) ||\n claim.schema_version !== 'dataset-flow-identity-local-approval-claim.v1' ||\n !Number.isFinite(Date.parse(claimedAt)) ||\n new Date(claimedAt).toISOString() !== claimedAt ||\n !['initial', 'recovery'].includes(String(claim.approval_kind)) ||\n claim.approval_identity_sha256 !== expectedIdentity ||\n ![\n claim.approval_identity_sha256,\n claim.execution_identity_sha256,\n claim.plan_sha256,\n claim.freeze_sha256,\n ].every((entry) => typeof entry === 'string' && HASH.test(entry)) ||\n typeof claim.request_id !== 'string' ||\n !UUID.test(claim.request_id) ||\n claim.environment !== 'production' ||\n typeof claim.project_ref !== 'string' ||\n !claim.project_ref.trim() ||\n typeof claim.actor_user_id !== 'string' ||\n !UUID.test(claim.actor_user_id) ||\n typeof claim.actor_email !== 'string' ||\n !EMAIL.test(claim.actor_email) ||\n claim.actor_email !== claim.actor_email.trim().toLowerCase() ||\n claim.target_visibility !== 'owner_draft' ||\n claim.user_state_claim !== 'authenticated_actor_state_100_plus_own_state_0' ||\n !path.isAbsolute(canonicalOutDir) ||\n path.resolve(canonicalOutDir) !== canonicalOutDir ||\n claim.maximum_cli_apply_spawns !== 1 ||\n claim.approval_reusable !== false\n ) {\n fail(\n 'Existing flow identity approval claim is malformed or foreign.',\n 'DATASET_FLOW_IDENTITY_APPROVAL_CLAIM_INVALID',\n );\n }\n return claim as FlowIdentityApprovalClaim;\n}\n\nexport function resolveFlowIdentityApprovalClaimRoot(options: {\n env: NodeJS.ProcessEnv;\n platform?: NodeJS.Platform;\n homeDir?: string;\n}): string {\n const platform = options.platform ?? process.platform;\n const homeDir = options.homeDir ?? os.homedir();\n const xdg = options.env.XDG_STATE_HOME?.trim();\n if (xdg) return path.resolve(xdg, 'tiangong-lca-cli');\n if (platform === 'win32') {\n const localAppData = options.env.LOCALAPPDATA?.trim();\n if (localAppData) return path.resolve(localAppData, 'tiangong-lca-cli');\n }\n if (homeDir.trim()) {\n return path.resolve(homeDir, '.local', 'state', 'tiangong-lca-cli');\n }\n if (platform === 'darwin') {\n return path.resolve(os.homedir(), 'Library', 'Application Support', 'tiangong-lca-cli');\n }\n return path.resolve('.tiangong-lca-cli-state');\n}\n\nexport function flowIdentityApprovalClaimPath(options: {\n stateRoot: string;\n approvalIdentitySha256: string;\n}): string {\n if (!HASH.test(options.approvalIdentitySha256)) {\n fail(\n 'Flow identity approval claim requires a lowercase approval identity SHA-256.',\n 'DATASET_FLOW_IDENTITY_APPROVAL_CLAIM_IDENTITY_INVALID',\n );\n }\n return path.join(\n path.resolve(options.stateRoot),\n 'execution-approvals',\n 'flow-identity',\n 'v3',\n `${options.approvalIdentitySha256}.claim.json`,\n );\n}\n\n/** Strict create-only claim. Existing identical bytes are still consumed. */\nexport function claimFlowIdentityApproval(options: {\n claim: FlowIdentityApprovalClaim;\n env: NodeJS.ProcessEnv;\n stateRoot?: string;\n}): string {\n validateClaim(options.claim, options.claim.approval_identity_sha256);\n const claimPath = flowIdentityApprovalClaimPath({\n stateRoot: options.stateRoot ?? resolveFlowIdentityApprovalClaimRoot({ env: options.env }),\n approvalIdentitySha256: options.claim.approval_identity_sha256,\n });\n const directory = path.dirname(claimPath);\n mkdirSync(directory, { recursive: true, mode: 0o700 });\n const bytes = `${stableJsonText(options.claim)}\\n`;\n let descriptor: number | null = null;\n try {\n descriptor = openSync(claimPath, 'wx', 0o600);\n writeFileSync(descriptor, bytes, 'utf8');\n fsyncSync(descriptor);\n } catch (error) {\n rethrowClaimCreateError(error, claimPath);\n } finally {\n if (descriptor !== null) closeSync(descriptor);\n }\n return claimPath;\n}\n\nexport function readFlowIdentityApprovalClaim(options: {\n approvalIdentitySha256: string;\n env: NodeJS.ProcessEnv;\n stateRoot?: string;\n}): FlowIdentityApprovalClaim | null {\n const claimPath = flowIdentityApprovalClaimPath({\n stateRoot: options.stateRoot ?? resolveFlowIdentityApprovalClaimRoot({ env: options.env }),\n approvalIdentitySha256: options.approvalIdentitySha256,\n });\n if (!existsSync(claimPath)) return null;\n const metadata = lstatSync(claimPath);\n if (\n !metadata.isFile() ||\n metadata.isSymbolicLink() ||\n !hasPrivateClaimPermissions(metadata.mode, process.platform)\n ) {\n return fail(\n 'Existing flow identity approval claim is not a private regular file.',\n 'DATASET_FLOW_IDENTITY_APPROVAL_CLAIM_INVALID',\n { claim_path: claimPath },\n );\n }\n let text: string;\n let value: unknown;\n try {\n text = readFileSync(claimPath, 'utf8');\n value = JSON.parse(text);\n } catch {\n return fail(\n 'Existing flow identity approval claim is unreadable.',\n 'DATASET_FLOW_IDENTITY_APPROVAL_CLAIM_INVALID',\n { claim_path: claimPath },\n );\n }\n if (text !== `${stableJsonText(value)}\\n`) {\n return fail(\n 'Existing flow identity approval claim is not canonical JSON.',\n 'DATASET_FLOW_IDENTITY_APPROVAL_CLAIM_INVALID',\n { claim_path: claimPath },\n );\n }\n return validateClaim(value, options.approvalIdentitySha256);\n}\n\nexport const __testInternals = {\n hasPrivateClaimPermissions,\n isErrno,\n rethrowClaimCreateError,\n validateClaim,\n};\n"]}
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { computeFlowIdentityCaptureEvidenceSha256, computeFlowIdentityCaptureSha256, parseFlowIdentityCapture, parseFlowIdentityPolicy, parseFlowIdentityReviewLedger, } from './dataset-maintenance-flow-identity-contract.js';
|
|
4
|
+
import { buildFlowIdentityCaptureRequest, buildFlowIdentitySemantics, flowType, } from './dataset-maintenance-flow-identity-plan.js';
|
|
5
|
+
import { materializePrivateArtifactDirectoryAtomically, readProtectedJsonArtifact, writePrivateImmutableJson, } from './dataset-maintenance-protected-artifacts.js';
|
|
6
|
+
import { parseProtectedToolchainEvidence } from './dataset-maintenance-protected-toolchain.js';
|
|
7
|
+
import { attestMaintenanceFlowIdentityCapture, fetchMaintenanceAccountTableRows, fetchMaintenanceExactRows, isMaintenanceRpcDomainFailure, resolveMaintenanceRemoteContext, } from './dataset-maintenance-remote.js';
|
|
8
|
+
import { isJsonObject, sha256Json, snapshotRemoteRow, stableJsonText, } from './dataset-maintenance-contract.js';
|
|
9
|
+
import { flowIdentityRestrictedSha256 } from './dataset-maintenance-flow-identity-wire.js';
|
|
10
|
+
import { validateFlowPayload } from './flow-payload-validation.js';
|
|
11
|
+
import { validateProcessPayload } from './process-payload-validation.js';
|
|
12
|
+
import { CliError } from './errors.js';
|
|
13
|
+
const HASH = /^[a-f0-9]{64}$/u;
|
|
14
|
+
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}$/u;
|
|
15
|
+
const VERSION = /^\d{2}\.\d{2}\.\d{3}$/u;
|
|
16
|
+
const DEFAULT_DEPENDENCIES = {
|
|
17
|
+
resolveContext: resolveMaintenanceRemoteContext,
|
|
18
|
+
fetchAccountTableRows: fetchMaintenanceAccountTableRows,
|
|
19
|
+
fetchExactRows: fetchMaintenanceExactRows,
|
|
20
|
+
attest: attestMaintenanceFlowIdentityCapture,
|
|
21
|
+
materialize: materializePrivateArtifactDirectoryAtomically,
|
|
22
|
+
};
|
|
23
|
+
function fail(message, code = 'DATASET_FLOW_IDENTITY_CAPTURE_INVALID') {
|
|
24
|
+
throw new CliError(message, { code, exitCode: 1 });
|
|
25
|
+
}
|
|
26
|
+
function exactKeys(value, keys) {
|
|
27
|
+
const actual = Object.keys(value).sort();
|
|
28
|
+
const expected = [...keys].sort();
|
|
29
|
+
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
|
30
|
+
}
|
|
31
|
+
function readCanonical(filePath, label) {
|
|
32
|
+
const artifact = readProtectedJsonArtifact({ filePath, label });
|
|
33
|
+
if (artifact.text !== `${stableJsonText(artifact.value)}\n`) {
|
|
34
|
+
fail(`${label} must be canonical JSON with one trailing newline.`);
|
|
35
|
+
}
|
|
36
|
+
return artifact;
|
|
37
|
+
}
|
|
38
|
+
export function parseFlowIdentityPrerequisites(value) {
|
|
39
|
+
if (!isJsonObject(value) ||
|
|
40
|
+
!isJsonObject(value.step2) ||
|
|
41
|
+
!isJsonObject(value.issue29_target1) ||
|
|
42
|
+
!isJsonObject(value.issue29_target2) ||
|
|
43
|
+
!exactKeys(value, ['schema_version', 'step2', 'issue29_target1', 'issue29_target2']) ||
|
|
44
|
+
!exactKeys(value.step2, ['readback_sha256', 'completed_at_utc', 'status']) ||
|
|
45
|
+
!exactKeys(value.issue29_target1, ['readback_sha256', 'completed_at_utc', 'status']) ||
|
|
46
|
+
!exactKeys(value.issue29_target2, ['readback_sha256', 'completed_at_utc', 'status'])) {
|
|
47
|
+
fail('Flow identity prerequisites are invalid.');
|
|
48
|
+
}
|
|
49
|
+
const parsed = value;
|
|
50
|
+
if (parsed.schema_version !== 'dataset-flow-identity-prerequisites.v2' ||
|
|
51
|
+
parsed.step2.status !== 'passed' ||
|
|
52
|
+
parsed.issue29_target1.status !== 'passed' ||
|
|
53
|
+
parsed.issue29_target2.status !== 'passed' ||
|
|
54
|
+
!HASH.test(parsed.step2.readback_sha256) ||
|
|
55
|
+
!HASH.test(parsed.issue29_target1.readback_sha256) ||
|
|
56
|
+
!HASH.test(parsed.issue29_target2.readback_sha256) ||
|
|
57
|
+
!Number.isFinite(Date.parse(parsed.step2.completed_at_utc)) ||
|
|
58
|
+
!Number.isFinite(Date.parse(parsed.issue29_target1.completed_at_utc)) ||
|
|
59
|
+
!Number.isFinite(Date.parse(parsed.issue29_target2.completed_at_utc))) {
|
|
60
|
+
fail('Flow identity prerequisites do not prove passed Step 2 and issue #29 readbacks.');
|
|
61
|
+
}
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
64
|
+
function snapshot(row) {
|
|
65
|
+
if (!row.user_id ||
|
|
66
|
+
row.state_code === null ||
|
|
67
|
+
!row.modified_at ||
|
|
68
|
+
!row.json_ordered ||
|
|
69
|
+
!row.json ||
|
|
70
|
+
sha256Json(row.json) !== sha256Json(row.json_ordered)) {
|
|
71
|
+
fail('A capture row is incomplete or its json/json_ordered columns differ.');
|
|
72
|
+
}
|
|
73
|
+
return snapshotRemoteRow({
|
|
74
|
+
table: row.table,
|
|
75
|
+
id: row.id,
|
|
76
|
+
version: row.version,
|
|
77
|
+
user_id: row.user_id,
|
|
78
|
+
state_code: row.state_code,
|
|
79
|
+
modified_at: row.modified_at,
|
|
80
|
+
json_ordered: row.json_ordered,
|
|
81
|
+
model_id: row.model_id,
|
|
82
|
+
rule_verification: row.rule_verification,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function identity(id, version) {
|
|
86
|
+
return `${id}\u0000${version}`;
|
|
87
|
+
}
|
|
88
|
+
function referenceIdentity(value) {
|
|
89
|
+
if (!isJsonObject(value) ||
|
|
90
|
+
typeof value['@refObjectId'] !== 'string' ||
|
|
91
|
+
typeof value['@version'] !== 'string' ||
|
|
92
|
+
!UUID.test(value['@refObjectId']) ||
|
|
93
|
+
!VERSION.test(value['@version'])) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return { id: value['@refObjectId'], version: value['@version'] };
|
|
97
|
+
}
|
|
98
|
+
function arrayOfObjects(value) {
|
|
99
|
+
const values = Array.isArray(value) ? value : isJsonObject(value) ? [value] : [];
|
|
100
|
+
if (!values.every(isJsonObject))
|
|
101
|
+
fail('A flow support collection is malformed.');
|
|
102
|
+
return values;
|
|
103
|
+
}
|
|
104
|
+
function flowPropertyClaim(payload) {
|
|
105
|
+
const root = isJsonObject(payload.flowDataSet) ? payload.flowDataSet : null;
|
|
106
|
+
const information = isJsonObject(root?.flowInformation) ? root.flowInformation : null;
|
|
107
|
+
const quantitative = isJsonObject(information?.quantitativeReference)
|
|
108
|
+
? information.quantitativeReference
|
|
109
|
+
: null;
|
|
110
|
+
const properties = isJsonObject(root?.flowProperties) ? root.flowProperties : null;
|
|
111
|
+
const referenceInternalId = quantitative?.referenceToReferenceFlowProperty;
|
|
112
|
+
const property = arrayOfObjects(properties?.flowProperty).find((entry) => entry['@dataSetInternalID'] === referenceInternalId);
|
|
113
|
+
const claim = referenceIdentity(property?.referenceToFlowPropertyDataSet);
|
|
114
|
+
if (!claim)
|
|
115
|
+
fail('A mapped flow lacks an exact reference flow-property identity.');
|
|
116
|
+
return claim;
|
|
117
|
+
}
|
|
118
|
+
function unitGroupClaim(payload) {
|
|
119
|
+
const root = isJsonObject(payload.flowPropertyDataSet) ? payload.flowPropertyDataSet : null;
|
|
120
|
+
const information = isJsonObject(root?.flowPropertiesInformation)
|
|
121
|
+
? root.flowPropertiesInformation
|
|
122
|
+
: null;
|
|
123
|
+
const quantitative = isJsonObject(information?.quantitativeReference)
|
|
124
|
+
? information.quantitativeReference
|
|
125
|
+
: null;
|
|
126
|
+
const claim = referenceIdentity(quantitative?.referenceToReferenceUnitGroup);
|
|
127
|
+
if (!claim)
|
|
128
|
+
fail('A captured flow property lacks an exact unit-group identity.');
|
|
129
|
+
return claim;
|
|
130
|
+
}
|
|
131
|
+
function processReferencesReviewedSource(row, reviewed) {
|
|
132
|
+
const root = isJsonObject(row.json_ordered?.processDataSet)
|
|
133
|
+
? row.json_ordered.processDataSet
|
|
134
|
+
: null;
|
|
135
|
+
const exchanges = isJsonObject(root?.exchanges) ? root.exchanges : null;
|
|
136
|
+
const values = Array.isArray(exchanges?.exchange)
|
|
137
|
+
? exchanges.exchange
|
|
138
|
+
: isJsonObject(exchanges?.exchange)
|
|
139
|
+
? [exchanges.exchange]
|
|
140
|
+
: [];
|
|
141
|
+
for (const value of values) {
|
|
142
|
+
if (!isJsonObject(value))
|
|
143
|
+
fail('A captured process has a malformed exchange collection.');
|
|
144
|
+
const reference = referenceIdentity(value.referenceToFlowDataSet);
|
|
145
|
+
if (!reference)
|
|
146
|
+
fail('A captured process exchange has a malformed flow reference.');
|
|
147
|
+
if (reviewed.has(identity(reference.id, reference.version)))
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
async function fetchUniqueRows(options) {
|
|
153
|
+
const rows = [];
|
|
154
|
+
for (let offset = 0; offset < options.identities.length; offset += options.concurrency) {
|
|
155
|
+
const chunk = options.identities.slice(offset, offset + options.concurrency);
|
|
156
|
+
const fetched = await Promise.all(chunk.map(async (claim) => {
|
|
157
|
+
const result = await options.dependencies.fetchExactRows({
|
|
158
|
+
context: options.context,
|
|
159
|
+
table: claim.table,
|
|
160
|
+
id: claim.id,
|
|
161
|
+
version: claim.version,
|
|
162
|
+
includeJson: true,
|
|
163
|
+
});
|
|
164
|
+
const visible = result.rows.filter((row) => options.publicOnly
|
|
165
|
+
? row.state_code === 100 && row.user_id !== options.actorUserId
|
|
166
|
+
: row.state_code === 100 ||
|
|
167
|
+
(row.state_code === 0 && row.user_id === options.actorUserId));
|
|
168
|
+
if (visible.length !== 1) {
|
|
169
|
+
fail('A capture target/support identity is missing, duplicated, or outside visibility.');
|
|
170
|
+
}
|
|
171
|
+
return snapshot(visible[0]);
|
|
172
|
+
}));
|
|
173
|
+
rows.push(...fetched);
|
|
174
|
+
}
|
|
175
|
+
return rows.sort((left, right) => `${left.table}\u0000${identity(left.id, left.version)}`.localeCompare(`${right.table}\u0000${identity(right.id, right.version)}`));
|
|
176
|
+
}
|
|
177
|
+
function validateInputs(options) {
|
|
178
|
+
const concurrency = options.readConcurrency ?? 5;
|
|
179
|
+
if (!UUID.test(options.requestId))
|
|
180
|
+
fail('capture request ID must be a canonical UUID.');
|
|
181
|
+
if (!options.operationId.trim() || Buffer.byteLength(options.operationId, 'utf8') > 512) {
|
|
182
|
+
fail('capture operation ID must be a non-empty token of at most 512 UTF-8 bytes.');
|
|
183
|
+
}
|
|
184
|
+
if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 5) {
|
|
185
|
+
fail('capture read concurrency must be an integer between 1 and 5.');
|
|
186
|
+
}
|
|
187
|
+
if (!options.sdkVersion.trim())
|
|
188
|
+
fail('capture SDK version is required.');
|
|
189
|
+
return { concurrency };
|
|
190
|
+
}
|
|
191
|
+
async function executeCapture(options, dependencies) {
|
|
192
|
+
const { concurrency } = validateInputs(options);
|
|
193
|
+
if (existsSync(path.resolve(options.outDir))) {
|
|
194
|
+
fail('Capture output directory already exists; protected capture artifacts are immutable.');
|
|
195
|
+
}
|
|
196
|
+
const policyArtifact = readCanonical(options.policyPath, 'Flow identity compatibility policy');
|
|
197
|
+
const reviewArtifact = readCanonical(options.reviewLedgerPath, 'Flow identity review ledger');
|
|
198
|
+
const prerequisitesArtifact = readCanonical(options.prerequisitesPath, 'Flow identity prerequisites');
|
|
199
|
+
const toolchainArtifact = readCanonical(options.toolchainEvidencePath, 'Protected toolchain evidence');
|
|
200
|
+
const policy = parseFlowIdentityPolicy(policyArtifact.value);
|
|
201
|
+
const review = parseFlowIdentityReviewLedger(reviewArtifact.value);
|
|
202
|
+
const prerequisites = parseFlowIdentityPrerequisites(prerequisitesArtifact.value);
|
|
203
|
+
if (policy.evidence_resolution_sha256 !== review.review_evidence_sha256) {
|
|
204
|
+
fail('Compatibility policy does not bind the review ledger.');
|
|
205
|
+
}
|
|
206
|
+
parseProtectedToolchainEvidence(toolchainArtifact.value, {
|
|
207
|
+
projectRef: options.expectedProjectRef,
|
|
208
|
+
cliVersion: options.cliVersion,
|
|
209
|
+
});
|
|
210
|
+
const capturedAt = options.now ?? new Date();
|
|
211
|
+
if (Date.parse(prerequisites.step2.completed_at_utc) > capturedAt.getTime() ||
|
|
212
|
+
Date.parse(prerequisites.issue29_target1.completed_at_utc) > capturedAt.getTime() ||
|
|
213
|
+
Date.parse(prerequisites.issue29_target2.completed_at_utc) > capturedAt.getTime()) {
|
|
214
|
+
fail('Capture time precedes a required passed prerequisite.');
|
|
215
|
+
}
|
|
216
|
+
const context = await dependencies.resolveContext({
|
|
217
|
+
env: options.env,
|
|
218
|
+
fetchImpl: options.fetchImpl,
|
|
219
|
+
timeoutMs: options.timeoutMs,
|
|
220
|
+
now: options.now,
|
|
221
|
+
});
|
|
222
|
+
if (context.project_ref !== options.expectedProjectRef ||
|
|
223
|
+
context.account.email.trim().toLowerCase() !== options.confirm.trim().toLowerCase()) {
|
|
224
|
+
fail('Authenticated production project/account does not match capture confirmation.');
|
|
225
|
+
}
|
|
226
|
+
const [flowCensus, processCensus] = await Promise.all([
|
|
227
|
+
dependencies.fetchAccountTableRows({
|
|
228
|
+
context,
|
|
229
|
+
userId: context.account.user_id,
|
|
230
|
+
table: 'flows',
|
|
231
|
+
stateCode: 0,
|
|
232
|
+
includeJson: true,
|
|
233
|
+
pageSize: options.pageSize,
|
|
234
|
+
}),
|
|
235
|
+
dependencies.fetchAccountTableRows({
|
|
236
|
+
context,
|
|
237
|
+
userId: context.account.user_id,
|
|
238
|
+
table: 'processes',
|
|
239
|
+
stateCode: 0,
|
|
240
|
+
includeJson: true,
|
|
241
|
+
pageSize: options.pageSize,
|
|
242
|
+
}),
|
|
243
|
+
]);
|
|
244
|
+
const reviewed = new Set(review.entries.map((entry) => identity(entry.source.id, entry.source.version)));
|
|
245
|
+
const elementary = flowCensus.rows.filter((row) => row.json_ordered && flowType(row.json_ordered) === 'Elementary flow');
|
|
246
|
+
const observed = new Set(elementary.map((row) => identity(row.id, row.version)));
|
|
247
|
+
if (elementary.length !== 305 ||
|
|
248
|
+
observed.size !== 305 ||
|
|
249
|
+
[...reviewed].some((key) => !observed.has(key)) ||
|
|
250
|
+
[...observed].some((key) => !reviewed.has(key))) {
|
|
251
|
+
fail('Authenticated owner-draft Elementary-flow census is not the exact 305-row review universe.');
|
|
252
|
+
}
|
|
253
|
+
const sourceRows = elementary
|
|
254
|
+
.map(snapshot)
|
|
255
|
+
.sort((left, right) => identity(left.id, left.version).localeCompare(identity(right.id, right.version)));
|
|
256
|
+
const fullProcessRows = processCensus.rows
|
|
257
|
+
.map(snapshot)
|
|
258
|
+
.sort((left, right) => identity(left.id, left.version).localeCompare(identity(right.id, right.version)));
|
|
259
|
+
const processRows = fullProcessRows.filter((row) => processReferencesReviewedSource(row, reviewed));
|
|
260
|
+
const processScanProof = {
|
|
261
|
+
...processCensus.completeness,
|
|
262
|
+
row_identity_set_sha256: sha256Json(fullProcessRows.map((row) => ({ id: row.id, version: row.version }))),
|
|
263
|
+
row_snapshot_set_sha256: sha256Json(fullProcessRows.map((row) => ({
|
|
264
|
+
id: row.id,
|
|
265
|
+
version: row.version,
|
|
266
|
+
row_sha256: row.row_sha256,
|
|
267
|
+
}))),
|
|
268
|
+
};
|
|
269
|
+
const targetClaims = [
|
|
270
|
+
...new Map(review.entries
|
|
271
|
+
.filter((entry) => entry.disposition === 'map_public')
|
|
272
|
+
.map((entry) => [
|
|
273
|
+
identity(entry.target.id, entry.target.version),
|
|
274
|
+
{
|
|
275
|
+
table: 'flows',
|
|
276
|
+
id: entry.target.id,
|
|
277
|
+
version: entry.target.version,
|
|
278
|
+
},
|
|
279
|
+
])).values(),
|
|
280
|
+
];
|
|
281
|
+
const targetRows = await fetchUniqueRows({
|
|
282
|
+
identities: targetClaims,
|
|
283
|
+
context,
|
|
284
|
+
actorUserId: context.account.user_id,
|
|
285
|
+
publicOnly: true,
|
|
286
|
+
concurrency,
|
|
287
|
+
dependencies,
|
|
288
|
+
});
|
|
289
|
+
const fpClaims = [
|
|
290
|
+
...new Map([
|
|
291
|
+
...sourceRows.filter((row) => review.entries.some((entry) => entry.disposition === 'map_public' &&
|
|
292
|
+
entry.source.id === row.id &&
|
|
293
|
+
entry.source.version === row.version)),
|
|
294
|
+
...targetRows,
|
|
295
|
+
].map((row) => {
|
|
296
|
+
const claim = flowPropertyClaim(row.json_ordered);
|
|
297
|
+
return [
|
|
298
|
+
identity(claim.id, claim.version),
|
|
299
|
+
{
|
|
300
|
+
table: 'flowproperties',
|
|
301
|
+
...claim,
|
|
302
|
+
},
|
|
303
|
+
];
|
|
304
|
+
})).values(),
|
|
305
|
+
];
|
|
306
|
+
const flowPropertyRows = await fetchUniqueRows({
|
|
307
|
+
identities: fpClaims,
|
|
308
|
+
context,
|
|
309
|
+
actorUserId: context.account.user_id,
|
|
310
|
+
publicOnly: false,
|
|
311
|
+
concurrency,
|
|
312
|
+
dependencies,
|
|
313
|
+
});
|
|
314
|
+
const unitGroupClaims = [
|
|
315
|
+
...new Map(flowPropertyRows.map((row) => {
|
|
316
|
+
const claim = unitGroupClaim(row.json_ordered);
|
|
317
|
+
return [identity(claim.id, claim.version), { table: 'unitgroups', ...claim }];
|
|
318
|
+
})).values(),
|
|
319
|
+
];
|
|
320
|
+
const unitGroupRows = await fetchUniqueRows({
|
|
321
|
+
identities: unitGroupClaims,
|
|
322
|
+
context,
|
|
323
|
+
actorUserId: context.account.user_id,
|
|
324
|
+
publicOnly: false,
|
|
325
|
+
concurrency,
|
|
326
|
+
dependencies,
|
|
327
|
+
});
|
|
328
|
+
const supportRows = [...flowPropertyRows, ...unitGroupRows].sort((left, right) => `${left.table}\u0000${identity(left.id, left.version)}`.localeCompare(`${right.table}\u0000${identity(right.id, right.version)}`));
|
|
329
|
+
const capture = {
|
|
330
|
+
schema_version: 'dataset-flow-identity-live-capture.v2',
|
|
331
|
+
captured_at_utc: capturedAt.toISOString(),
|
|
332
|
+
environment: 'production',
|
|
333
|
+
project_ref: context.project_ref,
|
|
334
|
+
account: {
|
|
335
|
+
user_id: context.account.user_id,
|
|
336
|
+
email: context.account.email.trim().toLowerCase(),
|
|
337
|
+
},
|
|
338
|
+
prerequisites: {
|
|
339
|
+
step2_readback_sha256: prerequisites.step2.readback_sha256,
|
|
340
|
+
step2_completed_at_utc: prerequisites.step2.completed_at_utc,
|
|
341
|
+
issue29_target1_readback_sha256: prerequisites.issue29_target1.readback_sha256,
|
|
342
|
+
issue29_target1_completed_at_utc: prerequisites.issue29_target1.completed_at_utc,
|
|
343
|
+
issue29_target2_readback_sha256: prerequisites.issue29_target2.readback_sha256,
|
|
344
|
+
issue29_target2_completed_at_utc: prerequisites.issue29_target2.completed_at_utc,
|
|
345
|
+
},
|
|
346
|
+
sdk: { package: '@tiangong-lca/tidas-sdk', version: options.sdkVersion.trim() },
|
|
347
|
+
artifact_evidence: {
|
|
348
|
+
review_ledger_sha256: review.ledger_sha256,
|
|
349
|
+
live_capture_artifact_sha256: '',
|
|
350
|
+
toolchain_evidence_sha256: toolchainArtifact.file_sha256,
|
|
351
|
+
},
|
|
352
|
+
completeness: {
|
|
353
|
+
schema_version: 'dataset-flow-identity-capture-completeness.v2',
|
|
354
|
+
source_count: 305,
|
|
355
|
+
target_count: targetRows.length,
|
|
356
|
+
support_count: supportRows.length,
|
|
357
|
+
owner_draft_process_count: fullProcessRows.length,
|
|
358
|
+
owner_draft_process_scan: processScanProof,
|
|
359
|
+
},
|
|
360
|
+
source_rows: sourceRows,
|
|
361
|
+
target_rows: targetRows,
|
|
362
|
+
support_rows: supportRows,
|
|
363
|
+
process_rows: processRows,
|
|
364
|
+
capture_request: {},
|
|
365
|
+
attestation: {},
|
|
366
|
+
capture_artifact_sha256: '',
|
|
367
|
+
};
|
|
368
|
+
capture.artifact_evidence.live_capture_artifact_sha256 =
|
|
369
|
+
computeFlowIdentityCaptureEvidenceSha256(capture);
|
|
370
|
+
const validation = {
|
|
371
|
+
validateFlow: options.validation?.validateFlow ?? validateFlowPayload,
|
|
372
|
+
validateProcess: options.validation?.validateProcess ?? validateProcessPayload,
|
|
373
|
+
};
|
|
374
|
+
const semantics = buildFlowIdentitySemantics({ policy, review, capture, validation });
|
|
375
|
+
capture.capture_request = buildFlowIdentityCaptureRequest({
|
|
376
|
+
requestId: options.requestId,
|
|
377
|
+
operationId: options.operationId,
|
|
378
|
+
policy,
|
|
379
|
+
capture,
|
|
380
|
+
mappings: semantics.mappings,
|
|
381
|
+
processTemplates: semantics.processTemplates,
|
|
382
|
+
protectedClosure: semantics.protectedClosure,
|
|
383
|
+
});
|
|
384
|
+
const requestSha256 = flowIdentityRestrictedSha256(capture.capture_request);
|
|
385
|
+
const materializeIndeterminate = (error, rawResponse) => {
|
|
386
|
+
const preferred = path.resolve(options.outDir);
|
|
387
|
+
const recoveryDir = existsSync(preferred)
|
|
388
|
+
? `${preferred}.indeterminate-${options.requestId}`
|
|
389
|
+
: preferred;
|
|
390
|
+
dependencies.materialize(recoveryDir, (staging) => {
|
|
391
|
+
writePrivateImmutableJson(path.join(staging, 'flow-identity-capture-request.json'), capture.capture_request);
|
|
392
|
+
if (rawResponse) {
|
|
393
|
+
writePrivateImmutableJson(path.join(staging, 'flow-identity-capture-raw-response.json'), rawResponse);
|
|
394
|
+
}
|
|
395
|
+
writePrivateImmutableJson(path.join(staging, 'flow-identity-capture-indeterminate.json'), {
|
|
396
|
+
schema_version: 'dataset-flow-identity-capture-indeterminate.v2',
|
|
397
|
+
status: 'indeterminate',
|
|
398
|
+
request_id: options.requestId,
|
|
399
|
+
capture_request_sha256: requestSha256,
|
|
400
|
+
post_attempt_count: 1,
|
|
401
|
+
automatic_retry: false,
|
|
402
|
+
normal_capture_rerun_allowed: false,
|
|
403
|
+
recovery: 'operator_exact_request_status_or_replay_required',
|
|
404
|
+
error: String(error),
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
};
|
|
408
|
+
const materializeRejected = (rawResponse) => {
|
|
409
|
+
dependencies.materialize(path.resolve(options.outDir), (staging) => {
|
|
410
|
+
writePrivateImmutableJson(path.join(staging, 'flow-identity-capture-request.json'), capture.capture_request);
|
|
411
|
+
writePrivateImmutableJson(path.join(staging, 'flow-identity-capture-domain-rejection.json'), {
|
|
412
|
+
schema_version: 'dataset-flow-identity-capture-domain-rejection.v1',
|
|
413
|
+
status: 'rejected',
|
|
414
|
+
request_id: options.requestId,
|
|
415
|
+
capture_request_sha256: requestSha256,
|
|
416
|
+
post_attempt_count: 1,
|
|
417
|
+
automatic_retry: false,
|
|
418
|
+
same_request_replay_allowed: false,
|
|
419
|
+
recovery: 'new_request_and_output_directory_required',
|
|
420
|
+
response: rawResponse,
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
};
|
|
424
|
+
let rawAttestation;
|
|
425
|
+
try {
|
|
426
|
+
rawAttestation = await dependencies.attest({
|
|
427
|
+
context,
|
|
428
|
+
request: capture.capture_request,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
materializeIndeterminate(error);
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
if (isMaintenanceRpcDomainFailure(rawAttestation)) {
|
|
436
|
+
materializeRejected(rawAttestation);
|
|
437
|
+
throw new CliError(typeof rawAttestation.message === 'string'
|
|
438
|
+
? rawAttestation.message
|
|
439
|
+
: 'Flow identity capture was rejected by the database.', {
|
|
440
|
+
code: rawAttestation.code,
|
|
441
|
+
exitCode: 1,
|
|
442
|
+
details: rawAttestation,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
try {
|
|
446
|
+
capture.attestation = rawAttestation;
|
|
447
|
+
capture.capture_artifact_sha256 = computeFlowIdentityCaptureSha256(capture);
|
|
448
|
+
const parsed = parseFlowIdentityCapture(capture);
|
|
449
|
+
const generatedAt = options.now ?? new Date();
|
|
450
|
+
const artifactPaths = {
|
|
451
|
+
request: path.join(path.resolve(options.outDir), 'flow-identity-capture-request.json'),
|
|
452
|
+
attestation: path.join(path.resolve(options.outDir), 'flow-identity-capture-attestation.json'),
|
|
453
|
+
live_capture: path.join(path.resolve(options.outDir), 'flow-identity-live-capture.json'),
|
|
454
|
+
report: path.join(path.resolve(options.outDir), 'flow-identity-capture-report.json'),
|
|
455
|
+
};
|
|
456
|
+
const report = {
|
|
457
|
+
schema_version: 'dataset-flow-identity-capture-report.v2',
|
|
458
|
+
generated_at_utc: generatedAt.toISOString(),
|
|
459
|
+
status: 'captured',
|
|
460
|
+
environment: 'production',
|
|
461
|
+
project_ref: parsed.project_ref,
|
|
462
|
+
actor: parsed.account,
|
|
463
|
+
operation_id: parsed.attestation.operation_id,
|
|
464
|
+
request_id: parsed.capture_request.request_id,
|
|
465
|
+
receipt_id: parsed.attestation.receipt_id,
|
|
466
|
+
receipt_proof_sha256: parsed.attestation.receipt_proof_sha256,
|
|
467
|
+
capture_request_sha256: parsed.attestation.capture_request_sha256,
|
|
468
|
+
live_capture_evidence_core_sha256: parsed.artifact_evidence.live_capture_artifact_sha256,
|
|
469
|
+
capture_artifact_sha256: parsed.capture_artifact_sha256,
|
|
470
|
+
counts: {
|
|
471
|
+
sources: 305,
|
|
472
|
+
targets: parsed.target_rows.length,
|
|
473
|
+
supports: parsed.support_rows.length,
|
|
474
|
+
owner_draft_processes: parsed.completeness.owner_draft_process_count,
|
|
475
|
+
mappings: semantics.mappings.length,
|
|
476
|
+
affected_processes: semantics.processTemplates.length,
|
|
477
|
+
rewrites: semantics.processTemplates.reduce((sum, template) => sum + template.process.rewrite_count, 0),
|
|
478
|
+
},
|
|
479
|
+
calls: {
|
|
480
|
+
capture_attest: 1,
|
|
481
|
+
scope_preflight: 0,
|
|
482
|
+
process_rewrite: 0,
|
|
483
|
+
scope_finalize: 0,
|
|
484
|
+
automatic_retry: false,
|
|
485
|
+
},
|
|
486
|
+
artifacts: artifactPaths,
|
|
487
|
+
};
|
|
488
|
+
dependencies.materialize(options.outDir, (staging) => {
|
|
489
|
+
writePrivateImmutableJson(path.join(staging, path.basename(artifactPaths.request)), parsed.capture_request);
|
|
490
|
+
writePrivateImmutableJson(path.join(staging, path.basename(artifactPaths.attestation)), parsed.attestation);
|
|
491
|
+
writePrivateImmutableJson(path.join(staging, path.basename(artifactPaths.live_capture)), parsed);
|
|
492
|
+
writePrivateImmutableJson(path.join(staging, path.basename(artifactPaths.report)), report);
|
|
493
|
+
});
|
|
494
|
+
return report;
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
materializeIndeterminate(error, rawAttestation);
|
|
498
|
+
throw error;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
export async function captureFlowIdentity(options) {
|
|
502
|
+
return executeCapture(options, DEFAULT_DEPENDENCIES);
|
|
503
|
+
}
|
|
504
|
+
export const __testInternals = {
|
|
505
|
+
executeCapture,
|
|
506
|
+
flowPropertyClaim,
|
|
507
|
+
parseFlowIdentityPrerequisites,
|
|
508
|
+
snapshot,
|
|
509
|
+
unitGroupClaim,
|
|
510
|
+
};
|
|
511
|
+
//# sourceMappingURL=dataset-maintenance-flow-identity-capture.js.map
|