@empire-builder-kit/nx 1.0.0-rc.2 → 1.0.0-rc.21
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/executors.json +10 -0
- package/package.json +4 -4
- package/src/cli/output.js +1 -1
- package/src/developer-agent/model.js +4 -4
- package/src/developer-agent/model.js.map +1 -1
- package/src/developer-agent/operation-registry.js +67 -10
- package/src/developer-agent/operation-registry.js.map +1 -1
- package/src/developer-agent/planning.js +7 -9
- package/src/developer-agent/planning.js.map +1 -1
- package/src/developer-agent/workspace-inspection.js +12 -0
- package/src/developer-agent/workspace-inspection.js.map +1 -1
- package/src/executors/blueprint-trust/blueprint-trust.js +6 -0
- package/src/executors/blueprint-trust/blueprint-trust.js.map +1 -1
- package/src/executors/deployment-plan/deployment-plan.js +5 -0
- package/src/executors/deployment-plan/deployment-plan.js.map +1 -1
- package/src/executors/foundation-preflight/foundation-preflight.d.ts +2 -0
- package/src/executors/foundation-preflight/foundation-preflight.js +6 -2
- package/src/executors/foundation-preflight/foundation-preflight.js.map +1 -1
- package/src/executors/foundation-preflight/schema.json +10 -0
- package/src/executors/foundation-profile-preflight/foundation-profile-preflight.d.ts +2 -0
- package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js +2 -1
- package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js.map +1 -1
- package/src/executors/foundation-profile-preflight/schema.json +19 -3
- package/src/executors/github-bootstrap/github-bootstrap.js +1 -0
- package/src/executors/github-bootstrap/github-bootstrap.js.map +1 -1
- package/src/executors/infrastructure-plan/infrastructure-plan.d.ts +229 -5
- package/src/executors/infrastructure-plan/infrastructure-plan.js +2298 -175
- package/src/executors/infrastructure-plan/infrastructure-plan.js.map +1 -1
- package/src/executors/infrastructure-plan/schema.json +16 -0
- package/src/executors/local-deploy/fixtures/local-deploy-state-machine.mjs +53 -0
- package/src/executors/local-deploy/local-approval.d.ts +23 -0
- package/src/executors/local-deploy/local-approval.js +43 -0
- package/src/executors/local-deploy/local-approval.js.map +1 -0
- package/src/executors/local-deploy/local-deploy.d.ts +69 -0
- package/src/executors/local-deploy/local-deploy.js +911 -0
- package/src/executors/local-deploy/local-deploy.js.map +1 -0
- package/src/executors/local-deploy/schema.json +42 -0
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.d.ts +3 -0
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js +94 -0
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js.map +1 -0
- package/src/executors/router-waf-rehearsal/schema.json +9 -0
- package/src/executors/secret-metadata/schema.json +5 -0
- package/src/executors/secret-metadata/secret-metadata.d.ts +12 -1
- package/src/executors/secret-metadata/secret-metadata.js +68 -16
- package/src/executors/secret-metadata/secret-metadata.js.map +1 -1
- package/src/executors/sst-lifecycle/logging-preflight.d.ts +5 -0
- package/src/executors/sst-lifecycle/logging-preflight.js +23 -13
- package/src/executors/sst-lifecycle/logging-preflight.js.map +1 -1
- package/src/executors/sst-lifecycle/run-sst-command.d.ts +8 -1
- package/src/executors/sst-lifecycle/run-sst-command.js +144 -12
- package/src/executors/sst-lifecycle/run-sst-command.js.map +1 -1
- package/src/executors/sst-lifecycle/schema.json +13 -1
- package/src/executors/sst-lifecycle/sst-environment-source.d.ts +25 -0
- package/src/executors/sst-lifecycle/sst-environment-source.js +213 -0
- package/src/executors/sst-lifecycle/sst-environment-source.js.map +1 -0
- package/src/foundation/index.d.ts +2 -0
- package/src/foundation/index.js +2 -0
- package/src/foundation/index.js.map +1 -1
- package/src/foundation/logging-preflight.js +1 -1
- package/src/foundation/logging-preflight.js.map +1 -1
- package/src/foundation/logging.d.ts +3 -1
- package/src/foundation/logging.js +1 -1
- package/src/foundation/logging.js.map +1 -1
- package/src/foundation/partial-state.d.ts +10 -0
- package/src/foundation/partial-state.js +21 -0
- package/src/foundation/partial-state.js.map +1 -0
- package/src/foundation/router-waf.d.ts +98 -0
- package/src/foundation/router-waf.js +401 -0
- package/src/foundation/router-waf.js.map +1 -0
- package/src/generators/function/function.js +2 -1
- package/src/generators/function/function.js.map +1 -1
- package/src/generators/post-generation-install.d.ts +1 -0
- package/src/generators/post-generation-install.js +134 -67
- package/src/generators/post-generation-install.js.map +1 -1
- package/src/generators/preset/generator.js +23 -3
- package/src/generators/preset/generator.js.map +1 -1
- package/src/generators/preset/schema.d.ts +7 -0
- package/src/generators/preset/schema.json +47 -0
- package/src/generators/preset/tool-versions.d.ts +5 -5
- package/src/generators/preset/tool-versions.js +4 -4
- package/src/generators/preset/workspace-files-ci-acceptance.d.ts +1 -0
- package/src/generators/preset/workspace-files-ci-acceptance.js +791 -0
- package/src/generators/preset/workspace-files-ci-acceptance.js.map +1 -0
- package/src/generators/preset/workspace-files-developer-agent.d.ts +2 -0
- package/src/generators/preset/workspace-files-developer-agent.js +25 -11
- package/src/generators/preset/workspace-files-developer-agent.js.map +1 -1
- package/src/generators/preset/workspace-files-foundation-workload-roles.js +5 -2
- package/src/generators/preset/workspace-files-foundation-workload-roles.js.map +1 -1
- package/src/generators/preset/workspace-files-performance-load.js +2 -1
- package/src/generators/preset/workspace-files-performance-load.js.map +1 -1
- package/src/generators/preset/workspace-files-workload-role-baseline.js +254 -42
- package/src/generators/preset/workspace-files-workload-role-baseline.js.map +1 -1
- package/src/generators/preset/workspace-files.d.ts +8 -4
- package/src/generators/preset/workspace-files.js +1039 -68
- package/src/generators/preset/workspace-files.js.map +1 -1
- package/src/generators/repository-policy/repository-policy.js +65 -3
- package/src/generators/repository-policy/repository-policy.js.map +1 -1
- package/src/generators/repository-policy/schema.d.ts +6 -0
- package/src/generators/repository-policy/schema.json +46 -0
- package/src/generators/slice/built-in-plan-api-files.js +2 -1
- package/src/generators/slice/built-in-plan-api-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js +19 -1
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-data-files.js +16 -5
- package/src/generators/slice/built-in-plan-data-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-data-retention-job-files.js +10 -6
- package/src/generators/slice/built-in-plan-data-retention-job-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-database-lifecycle-files.js +25 -10
- package/src/generators/slice/built-in-plan-database-lifecycle-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-foundation-files.js +2 -3
- package/src/generators/slice/built-in-plan-foundation-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-infrastructure-files.js +364 -29
- package/src/generators/slice/built-in-plan-infrastructure-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-infrastructure-safety-files.js +67 -11
- package/src/generators/slice/built-in-plan-infrastructure-safety-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-metadata-files.js +1 -0
- package/src/generators/slice/built-in-plan-metadata-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-operations-files.js +442 -5
- package/src/generators/slice/built-in-plan-operations-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-performance-files.d.ts +2 -1
- package/src/generators/slice/built-in-plan-performance-files.js +41 -47
- package/src/generators/slice/built-in-plan-performance-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-project-files.js +14 -1
- package/src/generators/slice/built-in-plan-project-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-release-files.js +235 -56
- package/src/generators/slice/built-in-plan-release-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-testing-files.js +92 -5
- package/src/generators/slice/built-in-plan-testing-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-web-files.js +13 -0
- package/src/generators/slice/built-in-plan-web-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-workload-role-files.js +5 -2
- package/src/generators/slice/built-in-plan-workload-role-files.js.map +1 -1
- package/src/generators/slice/built-in-plan.d.ts +2 -1
- package/src/generators/slice/built-in-plan.js +8 -5
- package/src/generators/slice/built-in-plan.js.map +1 -1
- package/src/generators/slice/slice.js +11 -3
- package/src/generators/slice/slice.js.map +1 -1
- package/src/generators/upgrade/upgrade.js +35 -0
- package/src/generators/upgrade/upgrade.js.map +1 -1
- package/src/generators/workload/frozen-container.d.ts +3 -2
- package/src/generators/workload/frozen-container.js +6 -4
- package/src/generators/workload/frozen-container.js.map +1 -1
- package/src/generators/workload/workload.js +2 -2
- package/src/generators/workload/workload.js.map +1 -1
- package/src/git-policy/github-bootstrap.d.ts +3 -1
- package/src/git-policy/github-bootstrap.js +22 -5
- package/src/git-policy/github-bootstrap.js.map +1 -1
- package/src/git-policy/routing.d.ts +2 -0
- package/src/git-policy/routing.js +12 -1
- package/src/git-policy/routing.js.map +1 -1
- package/src/infrastructure-change/local-capability.d.ts +73 -0
- package/src/infrastructure-change/local-capability.js +114 -0
- package/src/infrastructure-change/local-capability.js.map +1 -0
- package/src/infrastructure-change/model.d.ts +25 -4
- package/src/infrastructure-change/model.js +1 -0
- package/src/infrastructure-change/model.js.map +1 -1
- package/src/infrastructure-change/normalize.d.ts +4 -0
- package/src/infrastructure-change/normalize.js +47 -7
- package/src/infrastructure-change/normalize.js.map +1 -1
- package/src/infrastructure-change/plan.d.ts +30 -3
- package/src/infrastructure-change/plan.js +323 -13
- package/src/infrastructure-change/plan.js.map +1 -1
- package/src/infrastructure-change/risk.d.ts +3 -1
- package/src/infrastructure-change/risk.js +96 -16
- package/src/infrastructure-change/risk.js.map +1 -1
- package/src/local-dev/environment-inspector.d.ts +1 -0
- package/src/local-dev/environment-inspector.js +21 -9
- package/src/local-dev/environment-inspector.js.map +1 -1
- package/src/local-dev/private-state.d.ts +2 -0
- package/src/local-dev/private-state.js +3 -1
- package/src/local-dev/private-state.js.map +1 -1
- package/src/performance/cost-controls.d.ts +5 -3
- package/src/performance/cost-controls.js +30 -24
- package/src/performance/cost-controls.js.map +1 -1
- package/src/performance/model.d.ts +1 -1
- package/src/performance/model.js +0 -2
- package/src/performance/model.js.map +1 -1
- package/src/performance/test-fixtures.js +0 -2
- package/src/performance/test-fixtures.js.map +1 -1
- package/src/release/source-authority.d.ts +1 -0
- package/src/release/source-authority.js +136 -9
- package/src/release/source-authority.js.map +1 -1
- package/src/repository-policy/deployment-plan.d.ts +4 -1
- package/src/repository-policy/deployment-plan.js +8 -0
- package/src/repository-policy/deployment-plan.js.map +1 -1
- package/src/repository-policy/foundation-profile.js +1 -2
- package/src/repository-policy/foundation-profile.js.map +1 -1
- package/src/repository-policy/policy.d.ts +25 -1
- package/src/repository-policy/policy.js +145 -4
- package/src/repository-policy/policy.js.map +1 -1
- package/src/schemas/performance-policy-v1.json +0 -2
- package/src/schemas/repository-policy-v1.json +41 -0
- package/src/secrets/operator.js +22 -4
- package/src/secrets/operator.js.map +1 -1
- package/src/secrets/verification.js +9 -1
- package/src/secrets/verification.js.map +1 -1
- package/src/sst-environment-source-format.d.ts +3 -0
- package/src/sst-environment-source-format.js +179 -0
- package/src/sst-environment-source-format.js.map +1 -0
- package/src/supply-chain/blueprint-trust-git.js +72 -21
- package/src/supply-chain/blueprint-trust-git.js.map +1 -1
- package/src/supply-chain/blueprint-verification-authority.js +55 -3
- package/src/supply-chain/blueprint-verification-authority.js.map +1 -1
- package/src/supply-chain/blueprint-verification-execution.js +17 -2
- package/src/supply-chain/blueprint-verification-execution.js.map +1 -1
- package/src/supply-chain/build-output.d.ts +7 -1
- package/src/supply-chain/build-output.js +160 -20
- package/src/supply-chain/build-output.js.map +1 -1
- package/src/supply-chain/generated-command.js +131 -43
- package/src/supply-chain/generated-command.js.map +1 -1
- package/src/supply-chain/generated-container-release-command.js +187 -38
- package/src/supply-chain/generated-container-release-command.js.map +1 -1
- package/src/supply-chain/generated-ecr-collector-command.js +12 -2
- package/src/supply-chain/generated-ecr-collector-command.js.map +1 -1
- package/src/supply-chain/generated-scanner-command.js +114 -54
- package/src/supply-chain/generated-scanner-command.js.map +1 -1
- package/src/supply-chain/inventory.d.ts +17 -0
- package/src/supply-chain/inventory.js +114 -3
- package/src/supply-chain/inventory.js.map +1 -1
- package/src/supply-chain/pnpm-toolchain-authority.d.ts +4 -3
- package/src/supply-chain/pnpm-toolchain-authority.js +20 -34
- package/src/supply-chain/pnpm-toolchain-authority.js.map +1 -1
- package/src/supply-chain/policy.js +4 -1
- package/src/supply-chain/policy.js.map +1 -1
- package/src/supply-chain/sbom.d.ts +1 -1
- package/src/supply-chain/sbom.js +30 -5
- package/src/supply-chain/sbom.js.map +1 -1
- package/src/supply-chain/scanner.js +10 -1
- package/src/supply-chain/scanner.js.map +1 -1
- package/src/workload-roles/account-baseline.d.ts +11 -0
- package/src/workload-roles/account-baseline.js +253 -16
- package/src/workload-roles/account-baseline.js.map +1 -1
- package/src/workload-roles/authority.d.ts +3 -1
- package/src/workload-roles/authority.js +48 -2
- package/src/workload-roles/authority.js.map +1 -1
- package/src/workload-roles/aws-s3-state.js +27 -0
- package/src/workload-roles/aws-s3-state.js.map +1 -1
- package/src/workload-roles/boundary.d.ts +2 -1
- package/src/workload-roles/boundary.js +9 -2
- package/src/workload-roles/boundary.js.map +1 -1
- package/src/workload-roles/handler.js +52 -24
- package/src/workload-roles/handler.js.map +1 -1
- package/src/workload-roles/injection.js +1 -1
- package/src/workload-roles/injection.js.map +1 -1
- package/src/workload-roles/provisioner.js +12 -4
- package/src/workload-roles/provisioner.js.map +1 -1
- package/src/workload-roles/registration.d.ts +1 -0
- package/src/workload-roles/registration.js +5 -0
- package/src/workload-roles/registration.js.map +1 -1
- package/src/workload-roles/stage-boundary.js +1 -1
- package/src/workload-roles/stage-boundary.js.map +1 -1
- package/src/workload-roles/standard-slice.js +1 -0
- package/src/workload-roles/standard-slice.js.map +1 -1
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderWorkspaceCiAcceptanceFiles = renderWorkspaceCiAcceptanceFiles;
|
|
4
|
+
function renderWorkspaceCiAcceptanceFiles() {
|
|
5
|
+
return {
|
|
6
|
+
'tools/ci/workspace-acceptance.mjs': `import {
|
|
7
|
+
appendFileSync,
|
|
8
|
+
existsSync,
|
|
9
|
+
lstatSync,
|
|
10
|
+
mkdirSync,
|
|
11
|
+
readFileSync,
|
|
12
|
+
realpathSync,
|
|
13
|
+
writeFileSync,
|
|
14
|
+
} from 'node:fs';
|
|
15
|
+
import { spawnSync } from 'node:child_process';
|
|
16
|
+
import { createHash } from 'node:crypto';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { pathToFileURL } from 'node:url';
|
|
19
|
+
import { inflateRawSync } from 'node:zlib';
|
|
20
|
+
|
|
21
|
+
const WORKFLOW = '.github/workflows/ci.yml';
|
|
22
|
+
const KIND = 'ebk-workspace-protected-route-acceptance';
|
|
23
|
+
const SCHEMA_VERSION = 2;
|
|
24
|
+
const MAX_ARCHIVE_BYTES = 1024 * 1024;
|
|
25
|
+
const MAX_MANIFEST_BYTES = 256 * 1024;
|
|
26
|
+
const ROUTES = Object.freeze({
|
|
27
|
+
'protected-develop-promotion': Object.freeze({
|
|
28
|
+
base: 'main',
|
|
29
|
+
head: 'develop',
|
|
30
|
+
historyOnly: false,
|
|
31
|
+
}),
|
|
32
|
+
'protected-main-sync': Object.freeze({
|
|
33
|
+
base: 'develop',
|
|
34
|
+
head: 'main',
|
|
35
|
+
historyOnly: true,
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
const INPUTS = Object.freeze([
|
|
39
|
+
WORKFLOW,
|
|
40
|
+
'.node-version',
|
|
41
|
+
'ebk.repository-policy.json',
|
|
42
|
+
'package.json',
|
|
43
|
+
'pnpm-lock.yaml',
|
|
44
|
+
'supply-chain/blueprint-trust-policy.json',
|
|
45
|
+
'supply-chain/blueprint-trust-snapshot.json',
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
function sha256(value) {
|
|
49
|
+
return createHash('sha256').update(value).digest('hex');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function artifactDigest(value) {
|
|
53
|
+
if (typeof value !== 'string' || !/^sha256:[a-f0-9]{64}$/u.test(value)) {
|
|
54
|
+
throw new Error('Workspace acceptance artifact digest is invalid.');
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function artifactSize(value) {
|
|
60
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > MAX_ARCHIVE_BYTES) {
|
|
61
|
+
throw new Error('Workspace acceptance artifact size is invalid.');
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function artifactId(value) {
|
|
67
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
68
|
+
throw new Error('Workspace acceptance artifact identity is invalid.');
|
|
69
|
+
}
|
|
70
|
+
return String(value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function file(root, relative, limit = 16 * 1024 * 1024) {
|
|
74
|
+
const absolute = path.join(root, relative);
|
|
75
|
+
const entry = lstatSync(absolute);
|
|
76
|
+
if (
|
|
77
|
+
entry.isSymbolicLink() ||
|
|
78
|
+
!entry.isFile() ||
|
|
79
|
+
entry.size < 1 ||
|
|
80
|
+
entry.size > limit
|
|
81
|
+
) {
|
|
82
|
+
throw new Error(\`Workspace acceptance input is invalid: \${relative}\`);
|
|
83
|
+
}
|
|
84
|
+
return readFileSync(absolute);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function git(root, args) {
|
|
88
|
+
const result = spawnSync('git', ['-C', root, ...args], {
|
|
89
|
+
encoding: 'utf8',
|
|
90
|
+
shell: false,
|
|
91
|
+
windowsHide: true,
|
|
92
|
+
});
|
|
93
|
+
if (result.error || result.status !== 0) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
\`Workspace acceptance git failed: \${result.stderr || result.error?.message}\`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return result.stdout.trim();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function rootIdentity(requested) {
|
|
102
|
+
const root = realpathSync(requested);
|
|
103
|
+
if (root !== path.resolve(requested) || !lstatSync(root).isDirectory()) {
|
|
104
|
+
throw new Error('Workspace acceptance root is not canonical.');
|
|
105
|
+
}
|
|
106
|
+
const commit = git(root, ['rev-parse', 'HEAD']);
|
|
107
|
+
const tree = git(root, ['rev-parse', 'HEAD^{tree}']);
|
|
108
|
+
if (
|
|
109
|
+
!/^[a-f0-9]{40}$/u.test(commit) ||
|
|
110
|
+
!/^[a-f0-9]{40}$/u.test(tree) ||
|
|
111
|
+
git(root, ['status', '--porcelain=v1', '--untracked-files=no'])
|
|
112
|
+
) {
|
|
113
|
+
throw new Error('Workspace acceptance source is invalid or dirty.');
|
|
114
|
+
}
|
|
115
|
+
return { root, source: { commit, tree } };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function digests(root) {
|
|
119
|
+
return Object.fromEntries(
|
|
120
|
+
INPUTS.map((relative) => [relative, sha256(file(root, relative))]),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function integer(value, label) {
|
|
125
|
+
if (typeof value !== 'string' || !/^[1-9]\\d{0,19}$/u.test(value)) {
|
|
126
|
+
throw new Error(\`Workspace acceptance \${label} is invalid.\`);
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function commit(value, label) {
|
|
132
|
+
if (typeof value !== 'string' || !/^[a-f0-9]{40}$/u.test(value)) {
|
|
133
|
+
throw new Error(\`Workspace acceptance \${label} commit is invalid.\`);
|
|
134
|
+
}
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function repository(value) {
|
|
139
|
+
if (
|
|
140
|
+
typeof value !== 'string' ||
|
|
141
|
+
value.length > 200 ||
|
|
142
|
+
!/^[A-Za-z0-9_.-]+\\/[A-Za-z0-9_.-]+$/u.test(value)
|
|
143
|
+
) {
|
|
144
|
+
throw new Error('Workspace acceptance repository is invalid.');
|
|
145
|
+
}
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// GitHub resolves pull_request workflows through an ephemeral merge ref. The
|
|
150
|
+
// workflow bytes and exact source tree are bound independently in the sealed
|
|
151
|
+
// manifest, while this check binds the observable GitHub route.
|
|
152
|
+
function isPullRequestWorkflowRef(value, expectedRepository) {
|
|
153
|
+
const prefix = \`\${expectedRepository}/\${WORKFLOW}@refs/pull/\`;
|
|
154
|
+
return (
|
|
155
|
+
typeof value === 'string' &&
|
|
156
|
+
value.startsWith(prefix) &&
|
|
157
|
+
/^[1-9]\\d{0,9}\\/merge$/u.test(value.slice(prefix.length))
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function routeDefinition(value) {
|
|
162
|
+
if (typeof value !== 'string' || !Object.hasOwn(ROUTES, value)) {
|
|
163
|
+
throw new Error('Workspace acceptance route is invalid.');
|
|
164
|
+
}
|
|
165
|
+
return ROUTES[value];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function routeForBranches(base, head) {
|
|
169
|
+
const matches = Object.entries(ROUTES).filter(
|
|
170
|
+
([, route]) => route.base === base && route.head === head,
|
|
171
|
+
);
|
|
172
|
+
if (matches.length !== 1) {
|
|
173
|
+
throw new Error('Workspace acceptance branch route is invalid.');
|
|
174
|
+
}
|
|
175
|
+
return { name: matches[0][0], ...matches[0][1] };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function routeForProtectedPush(ref) {
|
|
179
|
+
if (ref === 'refs/heads/main') {
|
|
180
|
+
return {
|
|
181
|
+
name: 'protected-develop-promotion',
|
|
182
|
+
...ROUTES['protected-develop-promotion'],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
if (ref === 'refs/heads/develop') {
|
|
186
|
+
return {
|
|
187
|
+
name: 'protected-main-sync',
|
|
188
|
+
...ROUTES['protected-main-sync'],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
throw new Error('Workspace merge lookup lacks protected branch authority.');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function canonical(value) {
|
|
195
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
196
|
+
if (value !== null && typeof value === 'object') {
|
|
197
|
+
return Object.fromEntries(
|
|
198
|
+
Object.keys(value)
|
|
199
|
+
.sort()
|
|
200
|
+
.map((key) => [key, canonical(value[key])]),
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
return value;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function sealWorkspaceAcceptance({
|
|
207
|
+
environment,
|
|
208
|
+
output,
|
|
209
|
+
root: requested,
|
|
210
|
+
}) {
|
|
211
|
+
const { root, source } = rootIdentity(requested);
|
|
212
|
+
const expectedRepository = repository(environment.GITHUB_REPOSITORY);
|
|
213
|
+
const runId = integer(environment.GITHUB_RUN_ID, 'run id');
|
|
214
|
+
const runAttempt = integer(environment.GITHUB_RUN_ATTEMPT, 'run attempt');
|
|
215
|
+
const route = routeForBranches(
|
|
216
|
+
environment.GITHUB_BASE_REF,
|
|
217
|
+
environment.GITHUB_HEAD_REF,
|
|
218
|
+
);
|
|
219
|
+
const baseCommit = commit(environment.EBK_BASE_COMMIT, 'base');
|
|
220
|
+
const baseTree = git(root, ['rev-parse', \`\${baseCommit}^{tree}\`]);
|
|
221
|
+
if (
|
|
222
|
+
environment.GITHUB_EVENT_NAME !== 'pull_request' ||
|
|
223
|
+
environment.EBK_HEAD_REPOSITORY !== expectedRepository ||
|
|
224
|
+
!isPullRequestWorkflowRef(
|
|
225
|
+
environment.GITHUB_WORKFLOW_REF,
|
|
226
|
+
expectedRepository,
|
|
227
|
+
) ||
|
|
228
|
+
environment.EBK_CI_CANDIDATE_SHA !== source.commit
|
|
229
|
+
) {
|
|
230
|
+
throw new Error('Workspace acceptance is not an exact protected route.');
|
|
231
|
+
}
|
|
232
|
+
if (existsSync(output)) {
|
|
233
|
+
throw new Error('Workspace acceptance output already exists.');
|
|
234
|
+
}
|
|
235
|
+
if (route.historyOnly && baseTree !== source.tree) {
|
|
236
|
+
return Object.freeze({
|
|
237
|
+
base: route.base,
|
|
238
|
+
head: route.head,
|
|
239
|
+
historyOnly: true,
|
|
240
|
+
reason: 'protected main sync changes protected source',
|
|
241
|
+
route: route.name,
|
|
242
|
+
sealed: false,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
const manifest = {
|
|
246
|
+
base: route.base,
|
|
247
|
+
baseSource: { commit: baseCommit, tree: baseTree },
|
|
248
|
+
eventName: 'pull_request',
|
|
249
|
+
head: route.head,
|
|
250
|
+
headRepository: expectedRepository,
|
|
251
|
+
historyOnly: route.historyOnly,
|
|
252
|
+
inputs: digests(root),
|
|
253
|
+
kind: KIND,
|
|
254
|
+
repository: expectedRepository,
|
|
255
|
+
route: route.name,
|
|
256
|
+
runAttempt,
|
|
257
|
+
runId,
|
|
258
|
+
schemaVersion: SCHEMA_VERSION,
|
|
259
|
+
source,
|
|
260
|
+
workflow: WORKFLOW,
|
|
261
|
+
};
|
|
262
|
+
mkdirSync(path.dirname(output), { recursive: true, mode: 0o700 });
|
|
263
|
+
writeFileSync(output, \`\${JSON.stringify(manifest, null, 2)}\\n\`, {
|
|
264
|
+
encoding: 'utf8',
|
|
265
|
+
mode: 0o600,
|
|
266
|
+
});
|
|
267
|
+
return manifest;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function verifyWorkspaceAcceptance({
|
|
271
|
+
baseCommit: requestedBaseCommit,
|
|
272
|
+
headCommit,
|
|
273
|
+
manifestPath,
|
|
274
|
+
repository: requestedRepository,
|
|
275
|
+
root: requested,
|
|
276
|
+
route: requestedRoute,
|
|
277
|
+
runAttempt,
|
|
278
|
+
runId,
|
|
279
|
+
}) {
|
|
280
|
+
const { root, source } = rootIdentity(requested);
|
|
281
|
+
const expectedRepository = repository(requestedRepository);
|
|
282
|
+
const route = {
|
|
283
|
+
name: requestedRoute,
|
|
284
|
+
...routeDefinition(requestedRoute),
|
|
285
|
+
};
|
|
286
|
+
runId = integer(runId, 'run id');
|
|
287
|
+
runAttempt = integer(runAttempt, 'run attempt');
|
|
288
|
+
const baseCommit = commit(requestedBaseCommit, 'base');
|
|
289
|
+
headCommit = commit(headCommit, 'head');
|
|
290
|
+
const baseTree = git(root, ['rev-parse', \`\${baseCommit}^{tree}\`]);
|
|
291
|
+
const headTree = git(root, ['rev-parse', \`\${headCommit}^{tree}\`]);
|
|
292
|
+
if (headTree !== source.tree) {
|
|
293
|
+
throw new Error(
|
|
294
|
+
'Workspace acceptance head tree differs from the merged tree.',
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
if (route.historyOnly && baseTree !== headTree) {
|
|
298
|
+
throw new Error('Workspace acceptance sync changes protected source.');
|
|
299
|
+
}
|
|
300
|
+
const manifest = JSON.parse(
|
|
301
|
+
file(
|
|
302
|
+
path.dirname(manifestPath),
|
|
303
|
+
path.basename(manifestPath),
|
|
304
|
+
256 * 1024,
|
|
305
|
+
).toString('utf8'),
|
|
306
|
+
);
|
|
307
|
+
if (manifest?.kind !== KIND) {
|
|
308
|
+
throw new Error('Workspace acceptance kind is unsupported.');
|
|
309
|
+
}
|
|
310
|
+
if (manifest.schemaVersion !== SCHEMA_VERSION) {
|
|
311
|
+
throw new Error(
|
|
312
|
+
\`Workspace acceptance schema is unsupported; expected schema v\${SCHEMA_VERSION}.\`,
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
if (manifest.route !== route.name) {
|
|
316
|
+
throw new Error('Workspace acceptance route differs from the merged route.');
|
|
317
|
+
}
|
|
318
|
+
const expected = {
|
|
319
|
+
base: route.base,
|
|
320
|
+
baseSource: { commit: baseCommit, tree: baseTree },
|
|
321
|
+
eventName: 'pull_request',
|
|
322
|
+
head: route.head,
|
|
323
|
+
headRepository: expectedRepository,
|
|
324
|
+
historyOnly: route.historyOnly,
|
|
325
|
+
inputs: digests(root),
|
|
326
|
+
kind: KIND,
|
|
327
|
+
repository: expectedRepository,
|
|
328
|
+
route: route.name,
|
|
329
|
+
runAttempt,
|
|
330
|
+
runId,
|
|
331
|
+
schemaVersion: SCHEMA_VERSION,
|
|
332
|
+
source: { commit: headCommit, tree: headTree },
|
|
333
|
+
workflow: WORKFLOW,
|
|
334
|
+
};
|
|
335
|
+
if (
|
|
336
|
+
JSON.stringify(canonical(manifest)) !== JSON.stringify(canonical(expected))
|
|
337
|
+
) {
|
|
338
|
+
throw new Error('Workspace acceptance differs from the merged tree.');
|
|
339
|
+
}
|
|
340
|
+
return manifest;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function json(url, token, fetchImpl) {
|
|
344
|
+
const response = await fetchImpl(url, {
|
|
345
|
+
headers: {
|
|
346
|
+
Accept: 'application/vnd.github+json',
|
|
347
|
+
Authorization: \`Bearer \${token}\`,
|
|
348
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
const text = await response.text();
|
|
352
|
+
if (!response.ok || text.length > 2 * 1024 * 1024) {
|
|
353
|
+
throw new Error(\`GitHub acceptance lookup failed (\${response.status}).\`);
|
|
354
|
+
}
|
|
355
|
+
return JSON.parse(text);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function boundedResponseBytes(response, limit) {
|
|
359
|
+
if (!response.ok || !response.body) {
|
|
360
|
+
throw new Error(
|
|
361
|
+
\`Workspace acceptance artifact download failed (\${response.status}).\`,
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
const reader = response.body.getReader();
|
|
365
|
+
const chunks = [];
|
|
366
|
+
let total = 0;
|
|
367
|
+
while (true) {
|
|
368
|
+
const { done, value } = await reader.read();
|
|
369
|
+
if (done) break;
|
|
370
|
+
if (!(value instanceof Uint8Array)) {
|
|
371
|
+
throw new Error('Workspace acceptance artifact stream is invalid.');
|
|
372
|
+
}
|
|
373
|
+
total += value.byteLength;
|
|
374
|
+
if (total > limit) {
|
|
375
|
+
await reader.cancel();
|
|
376
|
+
throw new Error('Workspace acceptance artifact exceeds its byte bound.');
|
|
377
|
+
}
|
|
378
|
+
chunks.push(Buffer.from(value));
|
|
379
|
+
}
|
|
380
|
+
if (total < 1) {
|
|
381
|
+
throw new Error('Workspace acceptance artifact is empty.');
|
|
382
|
+
}
|
|
383
|
+
return Buffer.concat(chunks, total);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function crc32(value) {
|
|
387
|
+
let crc = 0xffffffff;
|
|
388
|
+
for (const byte of value) {
|
|
389
|
+
crc ^= byte;
|
|
390
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
391
|
+
crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// The acceptance upload contains exactly one manifest. Parse that bounded ZIP
|
|
398
|
+
// directly so no unverified archive bytes reach a filesystem extractor.
|
|
399
|
+
function acceptanceManifestFromArchive(archive) {
|
|
400
|
+
const minimumEocd = 22;
|
|
401
|
+
const minimumOffset = Math.max(0, archive.length - 65_557);
|
|
402
|
+
let eocd = -1;
|
|
403
|
+
for (
|
|
404
|
+
let offset = archive.length - minimumEocd;
|
|
405
|
+
offset >= minimumOffset;
|
|
406
|
+
offset -= 1
|
|
407
|
+
) {
|
|
408
|
+
if (
|
|
409
|
+
archive.readUInt32LE(offset) === 0x06054b50 &&
|
|
410
|
+
offset + minimumEocd + archive.readUInt16LE(offset + 20) ===
|
|
411
|
+
archive.length
|
|
412
|
+
) {
|
|
413
|
+
eocd = offset;
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (
|
|
418
|
+
eocd < 0 ||
|
|
419
|
+
archive.readUInt16LE(eocd + 4) !== 0 ||
|
|
420
|
+
archive.readUInt16LE(eocd + 6) !== 0 ||
|
|
421
|
+
archive.readUInt16LE(eocd + 8) !== 1 ||
|
|
422
|
+
archive.readUInt16LE(eocd + 10) !== 1
|
|
423
|
+
) {
|
|
424
|
+
throw new Error('Workspace acceptance archive shape is invalid.');
|
|
425
|
+
}
|
|
426
|
+
const centralSize = archive.readUInt32LE(eocd + 12);
|
|
427
|
+
const centralOffset = archive.readUInt32LE(eocd + 16);
|
|
428
|
+
if (
|
|
429
|
+
centralOffset + centralSize !== eocd ||
|
|
430
|
+
centralSize < 46 ||
|
|
431
|
+
archive.readUInt32LE(centralOffset) !== 0x02014b50
|
|
432
|
+
) {
|
|
433
|
+
throw new Error('Workspace acceptance central directory is invalid.');
|
|
434
|
+
}
|
|
435
|
+
const flags = archive.readUInt16LE(centralOffset + 8);
|
|
436
|
+
const method = archive.readUInt16LE(centralOffset + 10);
|
|
437
|
+
const expectedCrc = archive.readUInt32LE(centralOffset + 16);
|
|
438
|
+
const compressedSize = archive.readUInt32LE(centralOffset + 20);
|
|
439
|
+
const manifestSize = archive.readUInt32LE(centralOffset + 24);
|
|
440
|
+
const nameSize = archive.readUInt16LE(centralOffset + 28);
|
|
441
|
+
const extraSize = archive.readUInt16LE(centralOffset + 30);
|
|
442
|
+
const commentSize = archive.readUInt16LE(centralOffset + 32);
|
|
443
|
+
const disk = archive.readUInt16LE(centralOffset + 34);
|
|
444
|
+
const madeBy = archive.readUInt16LE(centralOffset + 4);
|
|
445
|
+
const externalAttributes = archive.readUInt32LE(centralOffset + 38);
|
|
446
|
+
const localOffset = archive.readUInt32LE(centralOffset + 42);
|
|
447
|
+
const centralEnd = centralOffset + 46 + nameSize + extraSize + commentSize;
|
|
448
|
+
const name = archive
|
|
449
|
+
.subarray(centralOffset + 46, centralOffset + 46 + nameSize)
|
|
450
|
+
.toString('utf8');
|
|
451
|
+
const unixMode = madeBy >>> 8 === 3 ? externalAttributes >>> 16 : 0;
|
|
452
|
+
if (
|
|
453
|
+
centralEnd !== eocd ||
|
|
454
|
+
name !== 'manifest.json' ||
|
|
455
|
+
disk !== 0 ||
|
|
456
|
+
(flags & ~0x0808) !== 0 ||
|
|
457
|
+
(method !== 0 && method !== 8) ||
|
|
458
|
+
compressedSize < 1 ||
|
|
459
|
+
manifestSize < 1 ||
|
|
460
|
+
manifestSize > MAX_MANIFEST_BYTES ||
|
|
461
|
+
(unixMode !== 0 && (unixMode & 0o170000) !== 0o100000) ||
|
|
462
|
+
localOffset + 30 > centralOffset ||
|
|
463
|
+
archive.readUInt32LE(localOffset) !== 0x04034b50 ||
|
|
464
|
+
archive.readUInt16LE(localOffset + 6) !== flags ||
|
|
465
|
+
archive.readUInt16LE(localOffset + 8) !== method
|
|
466
|
+
) {
|
|
467
|
+
throw new Error('Workspace acceptance archive entry is invalid.');
|
|
468
|
+
}
|
|
469
|
+
const localNameSize = archive.readUInt16LE(localOffset + 26);
|
|
470
|
+
const localExtraSize = archive.readUInt16LE(localOffset + 28);
|
|
471
|
+
const localNameStart = localOffset + 30;
|
|
472
|
+
const localNameEnd = localNameStart + localNameSize;
|
|
473
|
+
const dataStart = localNameEnd + localExtraSize;
|
|
474
|
+
const dataEnd = dataStart + compressedSize;
|
|
475
|
+
if (
|
|
476
|
+
localNameSize !== nameSize ||
|
|
477
|
+
archive.subarray(localNameStart, localNameEnd).toString('utf8') !== name ||
|
|
478
|
+
dataEnd > centralOffset
|
|
479
|
+
) {
|
|
480
|
+
throw new Error('Workspace acceptance local archive entry is invalid.');
|
|
481
|
+
}
|
|
482
|
+
const compressed = archive.subarray(dataStart, dataEnd);
|
|
483
|
+
const manifest =
|
|
484
|
+
method === 0
|
|
485
|
+
? Buffer.from(compressed)
|
|
486
|
+
: inflateRawSync(compressed, {
|
|
487
|
+
maxOutputLength: MAX_MANIFEST_BYTES,
|
|
488
|
+
});
|
|
489
|
+
if (manifest.length !== manifestSize || crc32(manifest) !== expectedCrc) {
|
|
490
|
+
throw new Error('Workspace acceptance manifest archive authority differs.');
|
|
491
|
+
}
|
|
492
|
+
return manifest;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export async function downloadWorkspaceAcceptance({
|
|
496
|
+
artifactDigest: requestedDigest,
|
|
497
|
+
artifactId: requestedId,
|
|
498
|
+
artifactSize: requestedSize,
|
|
499
|
+
environment,
|
|
500
|
+
fetchImpl = fetch,
|
|
501
|
+
output,
|
|
502
|
+
}) {
|
|
503
|
+
const expectedRepository = repository(environment.GITHUB_REPOSITORY);
|
|
504
|
+
const token = environment.GH_TOKEN;
|
|
505
|
+
const id = integer(requestedId, 'artifact id');
|
|
506
|
+
const digest = artifactDigest(requestedDigest);
|
|
507
|
+
const size = Number(integer(requestedSize, 'artifact size'));
|
|
508
|
+
artifactSize(size);
|
|
509
|
+
if (typeof token !== 'string' || token.length < 1) {
|
|
510
|
+
throw new Error('Workspace acceptance download token is missing.');
|
|
511
|
+
}
|
|
512
|
+
const api = environment.GITHUB_API_URL ?? 'https://api.github.com';
|
|
513
|
+
const response = await fetchImpl(
|
|
514
|
+
\`\${api}/repos/\${expectedRepository}/actions/artifacts/\${id}/zip\`,
|
|
515
|
+
{
|
|
516
|
+
headers: {
|
|
517
|
+
Accept: 'application/vnd.github+json',
|
|
518
|
+
Authorization: \`Bearer \${token}\`,
|
|
519
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
520
|
+
},
|
|
521
|
+
redirect: 'follow',
|
|
522
|
+
},
|
|
523
|
+
);
|
|
524
|
+
const archive = await boundedResponseBytes(response, MAX_ARCHIVE_BYTES);
|
|
525
|
+
if (archive.length !== size || \`sha256:\${sha256(archive)}\` !== digest) {
|
|
526
|
+
throw new Error(
|
|
527
|
+
'Workspace acceptance archive differs from GitHub authority.',
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
const manifest = acceptanceManifestFromArchive(archive);
|
|
531
|
+
if (existsSync(output)) {
|
|
532
|
+
throw new Error('Workspace acceptance restore output already exists.');
|
|
533
|
+
}
|
|
534
|
+
mkdirSync(output, { recursive: true, mode: 0o700 });
|
|
535
|
+
writeFileSync(path.join(output, 'manifest.json'), manifest, {
|
|
536
|
+
mode: 0o600,
|
|
537
|
+
});
|
|
538
|
+
return { digest, id, size };
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Declining reuse is always safe: the complete verification lane runs. Reuse
|
|
542
|
+
// is claimed only when one exact protected route, the identical reviewed tree,
|
|
543
|
+
// one successful workflow run, and one retained artifact all agree.
|
|
544
|
+
export async function resolveWorkspaceAcceptance({
|
|
545
|
+
environment,
|
|
546
|
+
fetchImpl = fetch,
|
|
547
|
+
root: requested,
|
|
548
|
+
}) {
|
|
549
|
+
const { root, source } = rootIdentity(requested);
|
|
550
|
+
const expectedRepository = repository(environment.GITHUB_REPOSITORY);
|
|
551
|
+
const token = environment.GH_TOKEN;
|
|
552
|
+
const route = routeForProtectedPush(environment.GITHUB_REF);
|
|
553
|
+
if (
|
|
554
|
+
typeof token !== 'string' ||
|
|
555
|
+
token.length < 1 ||
|
|
556
|
+
environment.GITHUB_EVENT_NAME !== 'push' ||
|
|
557
|
+
environment.GITHUB_SHA !== source.commit
|
|
558
|
+
) {
|
|
559
|
+
throw new Error('Workspace merge lookup lacks protected-push authority.');
|
|
560
|
+
}
|
|
561
|
+
const api = environment.GITHUB_API_URL ?? 'https://api.github.com';
|
|
562
|
+
try {
|
|
563
|
+
const pulls = await json(
|
|
564
|
+
\`\${api}/repos/\${expectedRepository}/commits/\${source.commit}/pulls\`,
|
|
565
|
+
token,
|
|
566
|
+
fetchImpl,
|
|
567
|
+
);
|
|
568
|
+
const eligible = pulls.filter(
|
|
569
|
+
(pull) =>
|
|
570
|
+
pull?.merged_at &&
|
|
571
|
+
pull?.merge_commit_sha === source.commit &&
|
|
572
|
+
pull?.base?.ref === route.base &&
|
|
573
|
+
pull?.head?.ref === route.head &&
|
|
574
|
+
pull?.head?.repo?.full_name === expectedRepository,
|
|
575
|
+
);
|
|
576
|
+
if (eligible.length !== 1) {
|
|
577
|
+
return {
|
|
578
|
+
reason: \`eligible \${route.name} merges: \${eligible.length}\`,
|
|
579
|
+
reused: false,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
const pull = eligible[0];
|
|
583
|
+
const headCommit = pull.head?.sha;
|
|
584
|
+
if (
|
|
585
|
+
typeof headCommit !== 'string' ||
|
|
586
|
+
!/^[a-f0-9]{40}$/u.test(headCommit)
|
|
587
|
+
) {
|
|
588
|
+
return {
|
|
589
|
+
reason: 'protected-route head commit is not exact',
|
|
590
|
+
reused: false,
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
const mergeParents = git(root, [
|
|
594
|
+
'rev-list',
|
|
595
|
+
'--parents',
|
|
596
|
+
'-n',
|
|
597
|
+
'1',
|
|
598
|
+
source.commit,
|
|
599
|
+
]).split(' ');
|
|
600
|
+
if (
|
|
601
|
+
mergeParents.length !== 3 ||
|
|
602
|
+
mergeParents[0] !== source.commit ||
|
|
603
|
+
mergeParents[2] !== headCommit
|
|
604
|
+
) {
|
|
605
|
+
return {
|
|
606
|
+
reason: 'protected-route merge parents are not exact',
|
|
607
|
+
reused: false,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
const baseCommit = mergeParents[1];
|
|
611
|
+
const baseTree = git(root, ['rev-parse', \`\${baseCommit}^{tree}\`]);
|
|
612
|
+
const headTree = git(root, ['rev-parse', \`\${headCommit}^{tree}\`]);
|
|
613
|
+
if (headTree !== source.tree) {
|
|
614
|
+
return {
|
|
615
|
+
reason: 'merged tree differs from the reviewed protected-route tree',
|
|
616
|
+
reused: false,
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
if (route.historyOnly && baseTree !== headTree) {
|
|
620
|
+
return {
|
|
621
|
+
reason: 'protected-main-sync changes protected source',
|
|
622
|
+
reused: false,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
const query = new URL(
|
|
626
|
+
\`\${api}/repos/\${expectedRepository}/actions/workflows/ci.yml/runs\`,
|
|
627
|
+
);
|
|
628
|
+
query.searchParams.set('event', 'pull_request');
|
|
629
|
+
query.searchParams.set('head_sha', headCommit);
|
|
630
|
+
query.searchParams.set('status', 'success');
|
|
631
|
+
query.searchParams.set('per_page', '100');
|
|
632
|
+
const observed = await json(query.href, token, fetchImpl);
|
|
633
|
+
// GitHub may clear a completed run's associated pull_requests array after
|
|
634
|
+
// the pull request merges. The exact merged PR was already established by
|
|
635
|
+
// the commit lookup above; bind its reviewed head to the protected workflow
|
|
636
|
+
// run directly instead of relying on that unstable, redundant association.
|
|
637
|
+
const runs = observed.workflow_runs.filter(
|
|
638
|
+
(run) =>
|
|
639
|
+
run?.conclusion === 'success' &&
|
|
640
|
+
run?.event === 'pull_request' &&
|
|
641
|
+
run?.head_sha === headCommit &&
|
|
642
|
+
run?.head_branch === route.head &&
|
|
643
|
+
run?.head_repository?.full_name === expectedRepository &&
|
|
644
|
+
run?.path === WORKFLOW &&
|
|
645
|
+
Number.isSafeInteger(run.id) &&
|
|
646
|
+
Number.isSafeInteger(run.run_attempt),
|
|
647
|
+
);
|
|
648
|
+
runs.sort(
|
|
649
|
+
(left, right) =>
|
|
650
|
+
right.run_attempt - left.run_attempt || right.id - left.id,
|
|
651
|
+
);
|
|
652
|
+
for (const run of runs) {
|
|
653
|
+
const artifactName = \`workspace-acceptance-\${headCommit}-\${run.id}-\${run.run_attempt}\`;
|
|
654
|
+
const retained = await json(
|
|
655
|
+
\`\${api}/repos/\${expectedRepository}/actions/runs/\${run.id}/artifacts?per_page=100\`,
|
|
656
|
+
token,
|
|
657
|
+
fetchImpl,
|
|
658
|
+
);
|
|
659
|
+
const artifacts = Array.isArray(retained?.artifacts)
|
|
660
|
+
? retained.artifacts.filter(
|
|
661
|
+
(artifact) =>
|
|
662
|
+
artifact?.name === artifactName && artifact?.expired === false,
|
|
663
|
+
)
|
|
664
|
+
: [];
|
|
665
|
+
if (artifacts.length === 1) {
|
|
666
|
+
const [artifact] = artifacts;
|
|
667
|
+
const id = artifactId(artifact.id);
|
|
668
|
+
const digest = artifactDigest(artifact.digest);
|
|
669
|
+
const size = String(artifactSize(artifact.size_in_bytes));
|
|
670
|
+
if (
|
|
671
|
+
artifact.archive_download_url !==
|
|
672
|
+
\`\${api}/repos/\${expectedRepository}/actions/artifacts/\${id}/zip\` ||
|
|
673
|
+
artifact.workflow_run?.id !== run.id ||
|
|
674
|
+
artifact.workflow_run?.head_sha !== headCommit
|
|
675
|
+
) {
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
return {
|
|
679
|
+
artifactDigest: digest,
|
|
680
|
+
artifactId: id,
|
|
681
|
+
artifactName,
|
|
682
|
+
artifactSize: size,
|
|
683
|
+
baseCommit,
|
|
684
|
+
headCommit,
|
|
685
|
+
historyOnly: route.historyOnly,
|
|
686
|
+
reused: true,
|
|
687
|
+
route: route.name,
|
|
688
|
+
runAttempt: String(run.run_attempt),
|
|
689
|
+
runId: String(run.id),
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return {
|
|
694
|
+
reason:
|
|
695
|
+
'no successful protected-route run retains the acceptance artifact',
|
|
696
|
+
reused: false,
|
|
697
|
+
};
|
|
698
|
+
} catch (error) {
|
|
699
|
+
return {
|
|
700
|
+
reason: \`acceptance resolution failed: \${error?.message ?? error}\`,
|
|
701
|
+
reused: false,
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
async function main(argv) {
|
|
707
|
+
const [operation, ...args] = argv;
|
|
708
|
+
if (operation === 'seal' && args.length === 1) {
|
|
709
|
+
const result = sealWorkspaceAcceptance({
|
|
710
|
+
environment: process.env,
|
|
711
|
+
output: path.resolve(args[0]),
|
|
712
|
+
root: process.cwd(),
|
|
713
|
+
});
|
|
714
|
+
const output = process.env.GITHUB_OUTPUT;
|
|
715
|
+
if (typeof output !== 'string' || !path.isAbsolute(output)) {
|
|
716
|
+
throw new Error('Workspace acceptance output authority is missing.');
|
|
717
|
+
}
|
|
718
|
+
appendFileSync(output, \`sealed=\${String(result.sealed !== false)}\\n\`, 'utf8');
|
|
719
|
+
if (result.sealed === false) {
|
|
720
|
+
console.log(
|
|
721
|
+
\`::warning title=Protected sync evidence declined::\${result.reason}; no reusable acceptance artifact was sealed.\`,
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (operation === 'verify' && args.length === 6) {
|
|
727
|
+
verifyWorkspaceAcceptance({
|
|
728
|
+
baseCommit: args[2],
|
|
729
|
+
headCommit: args[1],
|
|
730
|
+
manifestPath: path.resolve(args[0]),
|
|
731
|
+
repository: process.env.GITHUB_REPOSITORY,
|
|
732
|
+
root: process.cwd(),
|
|
733
|
+
route: args[3],
|
|
734
|
+
runAttempt: args[5],
|
|
735
|
+
runId: args[4],
|
|
736
|
+
});
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (operation === 'download' && args.length === 4) {
|
|
740
|
+
await downloadWorkspaceAcceptance({
|
|
741
|
+
artifactDigest: args[2],
|
|
742
|
+
artifactId: args[1],
|
|
743
|
+
artifactSize: args[3],
|
|
744
|
+
environment: process.env,
|
|
745
|
+
output: path.resolve(args[0]),
|
|
746
|
+
});
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (operation === 'resolve' && args.length === 0) {
|
|
750
|
+
const result = await resolveWorkspaceAcceptance({
|
|
751
|
+
environment: process.env,
|
|
752
|
+
root: process.cwd(),
|
|
753
|
+
});
|
|
754
|
+
const output = process.env.GITHUB_OUTPUT;
|
|
755
|
+
if (typeof output !== 'string' || !path.isAbsolute(output)) {
|
|
756
|
+
throw new Error('Workspace acceptance output authority is missing.');
|
|
757
|
+
}
|
|
758
|
+
if (result.reused !== true && result.reason) {
|
|
759
|
+
console.warn(
|
|
760
|
+
\`Workspace acceptance reuse declined (\${result.reason}); the complete verification lane runs instead.\`,
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
for (const [name, value] of Object.entries({
|
|
764
|
+
artifact_digest: result.artifactDigest ?? '',
|
|
765
|
+
artifact_id: result.artifactId ?? '',
|
|
766
|
+
artifact_name: result.artifactName ?? '',
|
|
767
|
+
artifact_size: result.artifactSize ?? '',
|
|
768
|
+
base_commit: result.baseCommit ?? '',
|
|
769
|
+
head_commit: result.headCommit ?? '',
|
|
770
|
+
history_only: String(result.historyOnly === true),
|
|
771
|
+
reused: String(result.reused),
|
|
772
|
+
route: result.route ?? '',
|
|
773
|
+
run_attempt: result.runAttempt ?? '',
|
|
774
|
+
run_id: result.runId ?? '',
|
|
775
|
+
})) {
|
|
776
|
+
appendFileSync(output, \`\${name}=\${value}\\n\`, 'utf8');
|
|
777
|
+
}
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
throw new Error(
|
|
781
|
+
'Usage: workspace-acceptance.mjs seal <manifest> | download <output> <artifact-id> <artifact-digest> <artifact-size> | verify <manifest> <head> <base> <route> <run> <attempt> | resolve',
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
786
|
+
await main(process.argv.slice(2));
|
|
787
|
+
}
|
|
788
|
+
`,
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
//# sourceMappingURL=workspace-files-ci-acceptance.js.map
|