@kungfu-tech/buildchain 4.1.3 → 4.1.4
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/AGENTS.md +11 -13
- package/CONTRIBUTING.md +32 -9
- package/README.md +56 -140
- package/actions/release/promotion/ref/README.md +1 -1
- package/architecture/action-taxonomy.json +1 -1
- package/architecture/agent-change-map.md +32 -45
- package/architecture/build-orchestration.json +1 -1
- package/architecture/capability-state-machine-manifest.json +5 -5
- package/architecture/ci-lane-change-budget.json +92 -92
- package/architecture/code-layout.json +2 -12
- package/architecture/core-mechanisms.json +38 -39
- package/architecture/cross-platform-adopter-qualification.json +1 -1
- package/architecture/dev-delivery-orchestration.json +2 -2
- package/architecture/floating-consumer-policy.json +3 -3
- package/architecture/internal-capabilities.json +27 -28
- package/architecture/maintainability-debt.json +60 -94
- package/architecture/maintainability-policy.json +7 -13
- package/architecture/minimal-consumer-migration.json +43 -5014
- package/architecture/platform-stage-checkpoints.json +1 -1
- package/architecture/release-tail-contract.json +2 -2
- package/architecture/release-topology.json +31 -25
- package/architecture/stage-capsule-qualification.json +1 -1
- package/architecture/universal-workflow-bootstrap.json +60 -60
- package/architecture/universal-workflow-capability-policy.json +3 -3
- package/architecture/universal-workflow-fault-campaign.json +7 -7
- package/architecture/workflow-taxonomy.json +59 -60
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/dist/site/buildchain-contract.json +138 -66
- package/dist/site/buildchain-site.json +257 -331
- package/dist/site/capability-registry.json +4 -4
- package/dist/site/cli-registry.json +6 -142
- package/dist/site/controller-registry.json +19 -19
- package/dist/site/kfd-claims.json +481 -529
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +22 -22
- package/dist/site/node-api-registry.json +82 -339
- package/dist/site/page-registry.json +229 -304
- package/dist/site/public-surface-audit.json +544 -754
- package/dist/site/publication-authority-registry.json +316 -316
- package/dist/site/publication-registry.json +1 -1
- package/dist/site/site-manifest.json +23 -23
- package/dist/site/workflow-registry.json +1071 -1027
- package/docs/MAP.md +28 -20
- package/docs/auditable-demo.md +17 -49
- package/docs/bootstrap-recovery.md +46 -37
- package/docs/cli-reference.md +2 -46
- package/docs/cli.md +97 -186
- package/docs/code-organization.md +7 -5
- package/docs/dev-alpha-candidate-patrol.md +19 -42
- package/docs/dev-delivery-orchestration.md +15 -11
- package/docs/dev-delivery-qualification-landing-adr.md +10 -9
- package/docs/dev-delivery-warrant.md +17 -16
- package/docs/dev-qualification-patrol.md +25 -20
- package/docs/getting-started.md +80 -139
- package/docs/github-artifact-attestation.md +31 -81
- package/docs/install.md +66 -30
- package/docs/lifecycle-protocol.md +19 -57
- package/docs/migration-inventory.md +17 -45
- package/docs/next-development-transition.md +13 -13
- package/docs/node-api-reference.md +122 -138
- package/docs/oci-publication.md +13 -10
- package/docs/publication-artifacts.md +128 -486
- package/docs/publication-authority.md +15 -45
- package/docs/publish-transaction.md +13 -11
- package/docs/release-candidate.md +126 -355
- package/docs/release-discussions.md +36 -67
- package/docs/release-flow.md +80 -339
- package/docs/release-governance.md +114 -962
- package/docs/release-promotion-request.md +45 -43
- package/docs/release-propagation.md +13 -27
- package/docs/release-tail-provider-plane.md +38 -65
- package/docs/reusable-build-surface.md +78 -196
- package/docs/runtime-train-validation.md +8 -6
- package/docs/stable-candidate-patrol.md +13 -66
- package/docs/v4-adopter-delivery.md +15 -15
- package/docs/v4-declarative-release-tail-migration.md +19 -17
- package/docs/v4-production-release.md +11 -6
- package/docs/v4-publication-rehearsal.md +12 -27
- package/docs/v4-stage-capsule.md +14 -20
- package/docs/v4-tail-reseal.md +34 -20
- package/docs/web-surface-deployments.md +26 -264
- package/docs/workflow-catalog.md +31 -31
- package/docs/workflow-path-migration.md +10 -4
- package/package.json +5 -4
- package/packages/core/adoption/buildchain-kfd-claims.js +7 -7
- package/packages/core/adoption/cli/output.mjs +1 -9
- package/packages/core/adoption/cli/product-gate.mjs +1 -15
- package/packages/core/adoption/cli/project.mjs +9 -10
- package/packages/core/adoption/cli/standards.mjs +1 -14
- package/packages/core/adoption/cli/support.mjs +0 -2
- package/packages/core/adoption/cli/surface-register.mjs +0 -2
- package/packages/core/adoption/commands/init-repo.mjs +77 -485
- package/packages/core/adoption/consumer-init.js +279 -0
- package/packages/core/adoption/qualification/admission.js +1 -1
- package/packages/core/build/cli/cache.mjs +0 -2
- package/packages/core/build/cli/facts.mjs +0 -3
- package/packages/core/build/cli/homebrew.mjs +0 -4
- package/packages/core/build/cli/lifecycle.mjs +0 -3
- package/packages/core/build/github-artifact-attestation.js +2 -2
- package/packages/core/build/platform-stage-checkpoints.js +1 -1
- package/packages/core/build/signing/dispatch.js +1 -1
- package/packages/core/consumer/buildchain-config.js +9 -0
- package/packages/core/consumer/contract/inspection.js +351 -9
- package/packages/core/consumer/contract/javascript-syntax.js +439 -0
- package/packages/core/consumer/contract/javascript-values.js +61 -0
- package/packages/core/consumer/contract/javascript.js +555 -0
- package/packages/core/consumer/contract/local-validation.js +84 -0
- package/packages/core/contracts/buildchain-contract.js +39 -8
- package/packages/core/contracts/cli/introspection.mjs +1 -9
- package/packages/core/contracts/public-surface-cli.js +0 -1
- package/packages/core/dev-delivery/dev-delivery-contract-surface.js +2 -2
- package/packages/core/governance/buildchain-publication-authority.js +24 -24
- package/packages/core/governance/cli/doctor.mjs +58 -42
- package/packages/core/governance/commands/architecture.mjs +6 -1
- package/packages/core/governance/commands/audit-publication-control-plane.mjs +1 -1
- package/packages/core/governance/commands/workflow-friction-report.mjs +1 -1
- package/packages/core/governance/github-governance-authority.js +2 -2
- package/packages/core/governance/publication/collection.js +1 -1
- package/packages/core/index.js +1 -3
- package/packages/core/observability/cli/candidate.mjs +0 -2
- package/packages/core/observability/cli/context.mjs +0 -5
- package/packages/core/observability/cli/diagnostics.mjs +0 -7
- package/packages/core/observability/cli/events.mjs +2 -10
- package/packages/core/paper/commands/paper-work-fleet-cli.mjs +1 -1
- package/packages/core/paper/commands/paper.mjs +33 -422
- package/packages/core/paper/consumer-status.js +72 -0
- package/packages/core/paper/operations/identity.js +0 -3
- package/packages/core/paper/operations/migration.js +460 -0
- package/packages/core/paper/operations/preflight-checks.js +8 -68
- package/packages/core/paper/operations/scaffold.js +16 -326
- package/packages/core/paper/operations/status.js +27 -53
- package/packages/core/paper/paper-agent-entry.js +36 -147
- package/packages/core/paper/paper-fleet.js +24 -39
- package/packages/core/paper/paper-repository.js +16 -0
- package/packages/core/paper/paper-runtime-channels.js +1 -1
- package/packages/core/paper/paper-scaffold-content.js +153 -162
- package/packages/core/paper/paper-work.js +3 -10
- package/packages/core/paper/paper.js +43 -5
- package/packages/core/providers/github/artifact-attestation.js +1 -1
- package/packages/core/providers/github/pipeline-context-artifacts.js +172 -0
- package/packages/core/publication/authority/control-plane.js +2 -2
- package/packages/core/publication/authority/seal.js +1 -1
- package/packages/core/publication/cli/dispatch.mjs +1 -12
- package/packages/core/publication/pipeline/actions.js +16 -9
- package/packages/core/publication/pipeline/apply.js +17 -9
- package/packages/core/publication/pipeline/context.js +29 -0
- package/packages/core/publication/pipeline/prepare.js +15 -6
- package/packages/core/publication/pipeline/recovery-materials.js +63 -1
- package/packages/core/publication/pipeline/recovery-prepare.js +21 -9
- package/packages/core/publication/pipeline/stable-entry.js +12 -7
- package/packages/core/publication/pipeline/stable-products.js +21 -19
- package/packages/core/publication/pipeline/version-actions.js +15 -6
- package/packages/core/publication/publication-rehearsal-projection.js +0 -9
- package/packages/core/release/cli/dispatch.mjs +1 -4
- package/packages/core/release/cli/inspection-handlers.mjs +1 -33
- package/packages/core/release/cli/release-handlers.mjs +2 -19
- package/packages/core/release/cli/verification-handlers.mjs +4 -25
- package/packages/core/release/next-development-projection.js +13 -13
- package/packages/core/release/passport/locations.js +13 -8
- package/packages/core/release/release-passport.js +70 -5
- package/packages/core/web/cli/badges.mjs +0 -4
- package/packages/core/web/publication-admission.js +1 -1
- package/packages/core/web/publication-capability.js +1 -1
- package/packages/core/web/readme-badges.js +1 -1
- package/packages/core/workflow/cli/main.mjs +1 -4
- package/packages/core/workflow/cli/process.mjs +1 -1
- package/packages/core/workflow/commands/buildchain-cli-help.mjs +2 -8
- package/packages/core/workflow/engine/conformance.js +1 -1
- package/packages/core/workflow/pipeline/build.js +21 -1
- package/packages/core/workflow/pipeline/delivery-observation.js +6 -2
- package/packages/core/workflow/workflow-taxonomy.mjs +61 -17
- package/scripts/check-code-layout.mjs +2 -0
- package/scripts/check-core-mechanism-inventory.mjs +29 -13
- package/scripts/check-floating-consumer-policy-contract.mjs +3 -3
- package/scripts/check-inventory.mjs +27 -27
- package/scripts/check-maintainability.mjs +28 -5
- package/scripts/check-public-dogfood-contract.mjs +3 -3
- package/scripts/check-release-topology.mjs +1 -1
- package/scripts/check-self-consumer-contract.mjs +7 -7
- package/scripts/check-universal-workflow-bootstrap.mjs +1 -1
- package/scripts/generate-channel-promotion-workflow.mjs +1 -1
- package/scripts/generate-minimal-consumer-contract.mjs +21 -1
- package/scripts/generate-site-bundle.mjs +2 -1
- package/scripts/generate-tail-reseal-fixture.mjs +3 -3
- package/scripts/inventory/promotion.mjs +1 -1
- package/scripts/next-development-self-dogfood-harness.mjs +1 -1
- package/scripts/next-development-self-dogfood.mjs +4 -4
- package/scripts/site-capability-metadata.mjs +2 -6
- package/scripts/source-metric-lineage.mjs +62 -7
- package/scripts/verify-golden-path.mjs +31 -35
- package/templates/native-dev-delivery.yml +1 -1
- package/templates/universal-buildchain-bootstrap.yml +1 -1
- package/packages/core/paper/operations/bootstrap.js +0 -75
- package/packages/core/paper/operations/plans.js +0 -213
- package/packages/core/paper/operations/provisioning.js +0 -113
- package/packages/core/paper/operations/scaffold-config.js +0 -77
- package/packages/core/paper/operations/scaffold-workflows.js +0 -116
- package/packages/core/paper/operations/workspace.js +0 -60
- package/packages/core/paper/paper-npm-bootstrap.js +0 -492
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import { parse } from "acorn";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { isBuiltin } from "node:module";
|
|
4
|
+
import { serialize } from "node:v8";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import {
|
|
7
|
+
UNKNOWN,
|
|
8
|
+
TAG,
|
|
9
|
+
tagged,
|
|
10
|
+
namespace,
|
|
11
|
+
concreteArguments,
|
|
12
|
+
processCalls,
|
|
13
|
+
inertModules,
|
|
14
|
+
inertGlobals,
|
|
15
|
+
} from "./javascript-values.js";
|
|
16
|
+
import { evaluateExpression, evaluateStatements } from "./javascript-syntax.js";
|
|
17
|
+
const moduleName = (value) => value.replace(/^node:/u, "");
|
|
18
|
+
class InvocationContext {
|
|
19
|
+
constructor({ file, cwd, maxNodes }) {
|
|
20
|
+
for (const key of Object.getOwnPropertyNames(InvocationContext.prototype))
|
|
21
|
+
if (key !== "constructor") this[key] = this[key].bind(this);
|
|
22
|
+
|
|
23
|
+
const edges = [],
|
|
24
|
+
problems = [],
|
|
25
|
+
exports = new Map(),
|
|
26
|
+
active = new Set();
|
|
27
|
+
const data = () => tagged("data", {});
|
|
28
|
+
this.remaining = maxNodes;
|
|
29
|
+
const report = (node, code, message) =>
|
|
30
|
+
problems.push({ file, line: node?.loc?.start.line || 1, code, message });
|
|
31
|
+
const edge = (node, value) => {
|
|
32
|
+
if (value.arguments && !concreteArguments(value.arguments)) {
|
|
33
|
+
report(
|
|
34
|
+
node,
|
|
35
|
+
"unresolved-edge-arguments",
|
|
36
|
+
"Cross-module or network arguments must resolve as data",
|
|
37
|
+
);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
edges.push({ file, line: node.loc.start.line, ...value });
|
|
41
|
+
};
|
|
42
|
+
const root = new Map([
|
|
43
|
+
["require", tagged("require", {})],
|
|
44
|
+
["undefined", undefined],
|
|
45
|
+
["process", namespace("process")],
|
|
46
|
+
]);
|
|
47
|
+
for (const name of inertGlobals) root.set(name, namespace(name));
|
|
48
|
+
root.set("fetch", tagged("network", { name: "fetch" }));
|
|
49
|
+
root.set("eval", tagged("dynamic-code", {}));
|
|
50
|
+
root.set("Function", tagged("dynamic-code", {}));
|
|
51
|
+
// Lexical scopes share binding cells. A copied block/function environment
|
|
52
|
+
// must observe assignments to an outer binding, while declarations shadow it.
|
|
53
|
+
for (const [name, value] of root) root.set(name, { value });
|
|
54
|
+
const get = (scope, name) =>
|
|
55
|
+
scope.has(name) ? scope.get(name).value : UNKNOWN;
|
|
56
|
+
const declare = (scope, name, value) => scope.set(name, { value });
|
|
57
|
+
|
|
58
|
+
Object.assign(this, {
|
|
59
|
+
edges,
|
|
60
|
+
problems,
|
|
61
|
+
exports,
|
|
62
|
+
active,
|
|
63
|
+
data,
|
|
64
|
+
report,
|
|
65
|
+
edge,
|
|
66
|
+
root,
|
|
67
|
+
get,
|
|
68
|
+
declare,
|
|
69
|
+
cwd,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
visit() {
|
|
73
|
+
if (--this.remaining < 0)
|
|
74
|
+
throw Error("JavaScript invocation analysis exceeded its node budget");
|
|
75
|
+
}
|
|
76
|
+
expression(node, scope) {
|
|
77
|
+
return evaluateExpression(this, node, scope);
|
|
78
|
+
}
|
|
79
|
+
statements(nodes, scope) {
|
|
80
|
+
return evaluateStatements(this, nodes, scope);
|
|
81
|
+
}
|
|
82
|
+
checkCoercion(node, values) {
|
|
83
|
+
const ctx = this;
|
|
84
|
+
const { report } = ctx;
|
|
85
|
+
|
|
86
|
+
if (
|
|
87
|
+
values.some(
|
|
88
|
+
(value) =>
|
|
89
|
+
value === UNKNOWN ||
|
|
90
|
+
(value !== null &&
|
|
91
|
+
typeof value === "object" &&
|
|
92
|
+
value[TAG] !== "data" &&
|
|
93
|
+
!(
|
|
94
|
+
value[TAG] === "data-method" && value.receiver?.[TAG] === "data"
|
|
95
|
+
)),
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
report(
|
|
99
|
+
node,
|
|
100
|
+
"unresolved-coercion",
|
|
101
|
+
"Object or unknown-value coercion may invoke executable code",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
bind(pattern, value, scope) {
|
|
105
|
+
const ctx = this;
|
|
106
|
+
const { bind, member, expression, report, declare } = ctx;
|
|
107
|
+
|
|
108
|
+
if (!pattern) return;
|
|
109
|
+
if (pattern.type === "Identifier") declare(scope, pattern.name, value);
|
|
110
|
+
else if (pattern.type === "AssignmentPattern")
|
|
111
|
+
bind(
|
|
112
|
+
pattern.left,
|
|
113
|
+
value === undefined ? expression(pattern.right, scope) : value,
|
|
114
|
+
scope,
|
|
115
|
+
);
|
|
116
|
+
else if (pattern.type === "ObjectPattern")
|
|
117
|
+
for (const property of pattern.properties) {
|
|
118
|
+
if (property.type === "RestElement")
|
|
119
|
+
bind(property.argument, UNKNOWN, scope);
|
|
120
|
+
else
|
|
121
|
+
bind(
|
|
122
|
+
property.value,
|
|
123
|
+
member(value, property.key.name ?? property.key.value),
|
|
124
|
+
scope,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
else if (pattern.type === "ArrayPattern")
|
|
128
|
+
pattern.elements.forEach((item, index) =>
|
|
129
|
+
bind(item, Array.isArray(value) ? value[index] : UNKNOWN, scope),
|
|
130
|
+
);
|
|
131
|
+
else
|
|
132
|
+
report(pattern, "unresolved-binding", "Unsupported executable binding");
|
|
133
|
+
}
|
|
134
|
+
member(value, key) {
|
|
135
|
+
const ctx = this;
|
|
136
|
+
|
|
137
|
+
if (typeof key !== "string" && typeof key !== "number") return UNKNOWN;
|
|
138
|
+
if (value?.[TAG] === "data")
|
|
139
|
+
return tagged("data-method", { receiver: value, name: String(key) });
|
|
140
|
+
if (value?.[TAG] === "namespace")
|
|
141
|
+
return tagged("member", {
|
|
142
|
+
specifier: value.specifier,
|
|
143
|
+
name: String(key),
|
|
144
|
+
});
|
|
145
|
+
if (value === UNKNOWN || value == null) return UNKNOWN;
|
|
146
|
+
if (typeof value === "string" || Array.isArray(value)) {
|
|
147
|
+
if (key === "length") return value.length;
|
|
148
|
+
if (/^[0-9]+$/u.test(String(key))) return value[Number(key)];
|
|
149
|
+
return tagged("data-method", { receiver: value, name: String(key) });
|
|
150
|
+
}
|
|
151
|
+
return Object.hasOwn(value, key) ? value[key] : UNKNOWN;
|
|
152
|
+
}
|
|
153
|
+
imported(node, specifier) {
|
|
154
|
+
const ctx = this;
|
|
155
|
+
const { report, edge } = ctx;
|
|
156
|
+
|
|
157
|
+
if (typeof specifier !== "string" || !specifier) {
|
|
158
|
+
report(
|
|
159
|
+
node,
|
|
160
|
+
"unresolved-module",
|
|
161
|
+
"Module specifier must resolve before execution",
|
|
162
|
+
);
|
|
163
|
+
return UNKNOWN;
|
|
164
|
+
}
|
|
165
|
+
edge(node, { kind: "module", specifier });
|
|
166
|
+
return namespace(specifier);
|
|
167
|
+
}
|
|
168
|
+
invokeFunction(node, callable, args) {
|
|
169
|
+
const ctx = this;
|
|
170
|
+
const { bind, expression, statements, report, active } = ctx;
|
|
171
|
+
|
|
172
|
+
if (active.has(callable.node) || active.size >= 32) {
|
|
173
|
+
report(
|
|
174
|
+
node,
|
|
175
|
+
"recursive-invocation",
|
|
176
|
+
"Invocation graph requires a bounded nonrecursive expansion",
|
|
177
|
+
);
|
|
178
|
+
return UNKNOWN;
|
|
179
|
+
}
|
|
180
|
+
active.add(callable.node);
|
|
181
|
+
try {
|
|
182
|
+
const scope = new Map(callable.scope);
|
|
183
|
+
callable.node.params.forEach((parameter, index) =>
|
|
184
|
+
bind(parameter, args[index], scope),
|
|
185
|
+
);
|
|
186
|
+
const value =
|
|
187
|
+
callable.node.body.type === "BlockStatement"
|
|
188
|
+
? statements(callable.node.body.body, scope).value
|
|
189
|
+
: expression(callable.node.body, scope);
|
|
190
|
+
if (callable.node.async || callable.node.generator) {
|
|
191
|
+
this.report(
|
|
192
|
+
node,
|
|
193
|
+
"unresolved-async-result",
|
|
194
|
+
"Async and generator return behavior requires explicit resolution",
|
|
195
|
+
);
|
|
196
|
+
return UNKNOWN;
|
|
197
|
+
}
|
|
198
|
+
return value;
|
|
199
|
+
} finally {
|
|
200
|
+
active.delete(callable.node);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
subprocess(node, name, args) {
|
|
204
|
+
const ctx = this;
|
|
205
|
+
const { report, edge, cwd } = ctx;
|
|
206
|
+
|
|
207
|
+
const shell = name === "exec" || name === "execSync";
|
|
208
|
+
const argv = Array.isArray(args[1]) ? args[1] : [];
|
|
209
|
+
const suppliedOptions = args[Array.isArray(args[1]) ? 2 : 1];
|
|
210
|
+
const options =
|
|
211
|
+
suppliedOptions?.[TAG] === "function" ? undefined : suppliedOptions;
|
|
212
|
+
if (
|
|
213
|
+
typeof args[0] !== "string" ||
|
|
214
|
+
!argv.every((item) => typeof item === "string") ||
|
|
215
|
+
options === UNKNOWN ||
|
|
216
|
+
(options !== undefined &&
|
|
217
|
+
(!options || typeof options !== "object" || options[TAG])) ||
|
|
218
|
+
(options?.cwd !== undefined && typeof options.cwd !== "string") ||
|
|
219
|
+
options?.env !== undefined ||
|
|
220
|
+
options?.shell
|
|
221
|
+
) {
|
|
222
|
+
report(
|
|
223
|
+
node,
|
|
224
|
+
"unresolved-process",
|
|
225
|
+
"Process command, arguments, directory and shell mode must resolve",
|
|
226
|
+
);
|
|
227
|
+
return UNKNOWN;
|
|
228
|
+
}
|
|
229
|
+
const directory =
|
|
230
|
+
options?.cwd === undefined
|
|
231
|
+
? cwd
|
|
232
|
+
: path.posix.isAbsolute(options.cwd)
|
|
233
|
+
? options.cwd
|
|
234
|
+
: path.posix.join(cwd, options.cwd);
|
|
235
|
+
if (shell) edge(node, { kind: "shell", script: args[0], cwd: directory });
|
|
236
|
+
else
|
|
237
|
+
edge(node, {
|
|
238
|
+
kind: "process",
|
|
239
|
+
executable: name === "fork" ? "node" : args[0],
|
|
240
|
+
argv: name === "fork" ? [args[0], ...argv] : argv,
|
|
241
|
+
cwd: directory,
|
|
242
|
+
});
|
|
243
|
+
return UNKNOWN;
|
|
244
|
+
}
|
|
245
|
+
inspectCallbacks(node, args) {
|
|
246
|
+
const seen = new Set();
|
|
247
|
+
const callbacks = (values) => {
|
|
248
|
+
this.visit();
|
|
249
|
+
for (const value of values) {
|
|
250
|
+
if (value?.[TAG] === "function")
|
|
251
|
+
this.invokeFunction(node, value, [UNKNOWN]);
|
|
252
|
+
else if (
|
|
253
|
+
value &&
|
|
254
|
+
typeof value === "object" &&
|
|
255
|
+
!value[TAG] &&
|
|
256
|
+
!seen.has(value)
|
|
257
|
+
) {
|
|
258
|
+
seen.add(value);
|
|
259
|
+
callbacks(Object.values(value));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
callbacks(args);
|
|
264
|
+
}
|
|
265
|
+
invokeData(node, callable, args) {
|
|
266
|
+
const { data, report } = this;
|
|
267
|
+
const callbacks = (values) => this.inspectCallbacks(node, values);
|
|
268
|
+
if (
|
|
269
|
+
callable.name === "slice" &&
|
|
270
|
+
args.every((value) => value === undefined || Number.isInteger(value))
|
|
271
|
+
)
|
|
272
|
+
return typeof callable.receiver === "string" ||
|
|
273
|
+
Array.isArray(callable.receiver)
|
|
274
|
+
? callable.receiver.slice(...args)
|
|
275
|
+
: data();
|
|
276
|
+
if (
|
|
277
|
+
callable.name === "join" &&
|
|
278
|
+
Array.isArray(callable.receiver) &&
|
|
279
|
+
callable.receiver.every((value) => typeof value === "string") &&
|
|
280
|
+
(args[0] === undefined || typeof args[0] === "string")
|
|
281
|
+
)
|
|
282
|
+
return callable.receiver.join(args[0]);
|
|
283
|
+
if (callable.name === "entries" && Array.isArray(callable.receiver))
|
|
284
|
+
return [...callable.receiver.entries()];
|
|
285
|
+
if (callable.name === "push" && Array.isArray(callable.receiver))
|
|
286
|
+
return callable.receiver.push(...args);
|
|
287
|
+
if (
|
|
288
|
+
[
|
|
289
|
+
"pop",
|
|
290
|
+
"shift",
|
|
291
|
+
"unshift",
|
|
292
|
+
"splice",
|
|
293
|
+
"reverse",
|
|
294
|
+
"sort",
|
|
295
|
+
"fill",
|
|
296
|
+
"copyWithin",
|
|
297
|
+
].includes(callable.name)
|
|
298
|
+
)
|
|
299
|
+
report(
|
|
300
|
+
node,
|
|
301
|
+
"unsupported-mutation",
|
|
302
|
+
`Unsupported data mutation: ${callable.name}`,
|
|
303
|
+
);
|
|
304
|
+
callbacks(args);
|
|
305
|
+
return data();
|
|
306
|
+
}
|
|
307
|
+
invokeMember(node, callable, args) {
|
|
308
|
+
const { data, report, edge, subprocess, cwd } = this;
|
|
309
|
+
const callbacks = (values) => this.inspectCallbacks(node, values);
|
|
310
|
+
const specifier = moduleName(callable.specifier),
|
|
311
|
+
name = callable.name;
|
|
312
|
+
if (specifier === "child_process") {
|
|
313
|
+
callbacks(args);
|
|
314
|
+
if (processCalls.has(name)) return subprocess(node, name, args);
|
|
315
|
+
report(
|
|
316
|
+
node,
|
|
317
|
+
"unresolved-process-api",
|
|
318
|
+
`Unsupported process API: ${name}`,
|
|
319
|
+
);
|
|
320
|
+
} else if (
|
|
321
|
+
["http", "https", "http2", "net", "tls", "dgram"].includes(specifier)
|
|
322
|
+
)
|
|
323
|
+
edge(node, {
|
|
324
|
+
kind: "network",
|
|
325
|
+
operation: `${specifier}.${name}`,
|
|
326
|
+
arguments: args,
|
|
327
|
+
});
|
|
328
|
+
else if (specifier === "module" && name === "createRequire")
|
|
329
|
+
return tagged("require", {});
|
|
330
|
+
else if (
|
|
331
|
+
specifier === "path" &&
|
|
332
|
+
["join", "dirname", "basename", "extname", "normalize"].includes(name) &&
|
|
333
|
+
args.every((value) => typeof value === "string")
|
|
334
|
+
)
|
|
335
|
+
return path.posix[name](...args);
|
|
336
|
+
else if (specifier === "process" && name === "cwd") return cwd;
|
|
337
|
+
else if (inertModules.has(specifier) || inertGlobals.has(specifier)) {
|
|
338
|
+
if (
|
|
339
|
+
specifier === "Object" &&
|
|
340
|
+
[
|
|
341
|
+
"defineProperty",
|
|
342
|
+
"defineProperties",
|
|
343
|
+
"setPrototypeOf",
|
|
344
|
+
"create",
|
|
345
|
+
].includes(name)
|
|
346
|
+
)
|
|
347
|
+
report(
|
|
348
|
+
node,
|
|
349
|
+
"unsupported-object-runtime",
|
|
350
|
+
`Unsupported object execution semantics: ${name}`,
|
|
351
|
+
);
|
|
352
|
+
callbacks(args);
|
|
353
|
+
return data();
|
|
354
|
+
} else if (
|
|
355
|
+
!specifier.startsWith("node:") &&
|
|
356
|
+
!["process", "vm", "module"].includes(specifier)
|
|
357
|
+
)
|
|
358
|
+
edge(node, {
|
|
359
|
+
kind: "module-call",
|
|
360
|
+
specifier: callable.specifier,
|
|
361
|
+
exportName: name,
|
|
362
|
+
arguments: args,
|
|
363
|
+
});
|
|
364
|
+
else
|
|
365
|
+
report(
|
|
366
|
+
node,
|
|
367
|
+
"unresolved-runtime-api",
|
|
368
|
+
`Unsupported executable API: ${specifier}.${name}`,
|
|
369
|
+
);
|
|
370
|
+
return UNKNOWN;
|
|
371
|
+
}
|
|
372
|
+
invoke(node, callable, args) {
|
|
373
|
+
switch (callable?.[TAG]) {
|
|
374
|
+
case "function":
|
|
375
|
+
return this.invokeFunction(node, callable, args);
|
|
376
|
+
case "require":
|
|
377
|
+
return this.imported(node, args[0]);
|
|
378
|
+
case "namespace":
|
|
379
|
+
if (inertGlobals.has(callable.specifier)) {
|
|
380
|
+
this.inspectCallbacks(node, args);
|
|
381
|
+
return this.data();
|
|
382
|
+
}
|
|
383
|
+
break;
|
|
384
|
+
case "data-method":
|
|
385
|
+
return this.invokeData(node, callable, args);
|
|
386
|
+
case "member":
|
|
387
|
+
return this.invokeMember(node, callable, args);
|
|
388
|
+
case "network":
|
|
389
|
+
this.edge(node, {
|
|
390
|
+
kind: "network",
|
|
391
|
+
operation: callable.name,
|
|
392
|
+
arguments: args,
|
|
393
|
+
});
|
|
394
|
+
return UNKNOWN;
|
|
395
|
+
case "dynamic-code":
|
|
396
|
+
this.report(
|
|
397
|
+
node,
|
|
398
|
+
"dynamic-code",
|
|
399
|
+
"Dynamic code cannot establish a closed invocation graph",
|
|
400
|
+
);
|
|
401
|
+
return UNKNOWN;
|
|
402
|
+
}
|
|
403
|
+
this.report(
|
|
404
|
+
node,
|
|
405
|
+
"unresolved-call",
|
|
406
|
+
"Call target cannot be resolved from source",
|
|
407
|
+
);
|
|
408
|
+
return UNKNOWN;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
// No consumer code is executed. Callers must expand every returned edge and
|
|
412
|
+
// reject problems before treating this local analysis as a complete graph.
|
|
413
|
+
export function inspectJavaScriptInvocations({
|
|
414
|
+
source,
|
|
415
|
+
file,
|
|
416
|
+
cwd = ".",
|
|
417
|
+
calledExports = [],
|
|
418
|
+
maxNodes = 50000,
|
|
419
|
+
}) {
|
|
420
|
+
const context = new InvocationContext({ file, cwd, maxNodes });
|
|
421
|
+
try {
|
|
422
|
+
if (!Number.isSafeInteger(maxNodes) || maxNodes < 1 || maxNodes > 1000000)
|
|
423
|
+
throw Error(
|
|
424
|
+
"JavaScript node budget must be an integer from 1 to 1000000",
|
|
425
|
+
);
|
|
426
|
+
if (typeof source !== "string" || source.length > 1024 * 1024)
|
|
427
|
+
throw Error("JavaScript source exceeds the bounded input size");
|
|
428
|
+
const program = parse(source, {
|
|
429
|
+
ecmaVersion: "latest",
|
|
430
|
+
sourceType: file.endsWith(".cjs") ? "commonjs" : "module",
|
|
431
|
+
locations: true,
|
|
432
|
+
});
|
|
433
|
+
context.statements(program.body, context.root);
|
|
434
|
+
for (const request of calledExports)
|
|
435
|
+
context.invoke(
|
|
436
|
+
program,
|
|
437
|
+
context.exports.get(request.name),
|
|
438
|
+
request.arguments || [],
|
|
439
|
+
);
|
|
440
|
+
} catch (error) {
|
|
441
|
+
context.report(null, "analysis-failed", error.message);
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
ok: context.problems.length === 0,
|
|
445
|
+
edges: context.edges,
|
|
446
|
+
problems: context.problems,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function resolveSourceModule(files, from, specifier) {
|
|
451
|
+
if (!specifier.startsWith("./") && !specifier.startsWith("../"))
|
|
452
|
+
throw Error(`Dependency requires an exact source resolution: ${specifier}`);
|
|
453
|
+
const target = path.posix.normalize(
|
|
454
|
+
path.posix.join(path.posix.dirname(from), specifier),
|
|
455
|
+
);
|
|
456
|
+
if (target === ".." || target.startsWith("../") || target.includes("\\"))
|
|
457
|
+
throw Error(`Module escapes the supplied source tree: ${specifier}`);
|
|
458
|
+
if (!Object.hasOwn(files, target) || typeof files[target] !== "string")
|
|
459
|
+
throw Error(`Module source is absent: ${target}`);
|
|
460
|
+
if (!/\.[cm]?js$/u.test(target))
|
|
461
|
+
throw Error(`Unsupported executable module: ${target}`);
|
|
462
|
+
return target;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// This walks a supplied source snapshot. Package resolution, shell commands,
|
|
466
|
+
// and generated executable files remain explicit boundaries for the caller;
|
|
467
|
+
// an unresolved dependency cannot be silently omitted from a passing graph.
|
|
468
|
+
export function inspectJavaScriptModuleClosure({
|
|
469
|
+
files,
|
|
470
|
+
entry,
|
|
471
|
+
cwd = ".",
|
|
472
|
+
maxInvocations = 256,
|
|
473
|
+
maxNodes = 50000,
|
|
474
|
+
}) {
|
|
475
|
+
const edges = [],
|
|
476
|
+
problems = [],
|
|
477
|
+
sources = new Map(),
|
|
478
|
+
visited = new Set();
|
|
479
|
+
const queue = [{ file: entry, calledExports: [], ancestry: [] }];
|
|
480
|
+
while (queue.length && visited.size < maxInvocations) {
|
|
481
|
+
const request = queue.shift();
|
|
482
|
+
const key = serialize([request.file, request.calledExports]).toString(
|
|
483
|
+
"base64",
|
|
484
|
+
);
|
|
485
|
+
if (request.ancestry.includes(key)) {
|
|
486
|
+
problems.push({
|
|
487
|
+
file: request.file,
|
|
488
|
+
code: "cyclic-module-invocation",
|
|
489
|
+
message: "Module calls require a bounded expansion",
|
|
490
|
+
});
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
if (visited.has(key)) continue;
|
|
494
|
+
visited.add(key);
|
|
495
|
+
const source = Object.hasOwn(files, request.file)
|
|
496
|
+
? files[request.file]
|
|
497
|
+
: undefined;
|
|
498
|
+
const report = inspectJavaScriptInvocations({
|
|
499
|
+
source,
|
|
500
|
+
file: request.file,
|
|
501
|
+
cwd,
|
|
502
|
+
calledExports: request.calledExports,
|
|
503
|
+
maxNodes,
|
|
504
|
+
});
|
|
505
|
+
problems.push(...report.problems);
|
|
506
|
+
if (typeof source === "string")
|
|
507
|
+
sources.set(
|
|
508
|
+
request.file,
|
|
509
|
+
createHash("sha256").update(source).digest("hex"),
|
|
510
|
+
);
|
|
511
|
+
for (const edge of report.edges) {
|
|
512
|
+
edges.push(edge);
|
|
513
|
+
if (!["module", "module-call"].includes(edge.kind)) continue;
|
|
514
|
+
if (isBuiltin(edge.specifier)) {
|
|
515
|
+
if (edge.kind === "module-call")
|
|
516
|
+
problems.push({
|
|
517
|
+
...edge,
|
|
518
|
+
code: "unresolved-builtin",
|
|
519
|
+
message: "Builtin execution was not resolved locally",
|
|
520
|
+
});
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
const target = resolveSourceModule(files, request.file, edge.specifier);
|
|
525
|
+
queue.push({
|
|
526
|
+
file: target,
|
|
527
|
+
calledExports:
|
|
528
|
+
edge.kind === "module-call"
|
|
529
|
+
? [{ name: edge.exportName, arguments: edge.arguments }]
|
|
530
|
+
: [],
|
|
531
|
+
ancestry: [...request.ancestry, key],
|
|
532
|
+
});
|
|
533
|
+
} catch (error) {
|
|
534
|
+
problems.push({
|
|
535
|
+
file: request.file,
|
|
536
|
+
line: edge.line,
|
|
537
|
+
code: "unresolved-module-source",
|
|
538
|
+
message: error.message,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (queue.length)
|
|
544
|
+
problems.push({
|
|
545
|
+
file: entry,
|
|
546
|
+
code: "invocation-budget",
|
|
547
|
+
message: "Module closure exceeded its invocation budget",
|
|
548
|
+
});
|
|
549
|
+
return {
|
|
550
|
+
ok: problems.length === 0,
|
|
551
|
+
sources: Object.fromEntries(sources),
|
|
552
|
+
edges,
|
|
553
|
+
problems,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { consumerWorkflows } from "./entries.js";
|
|
4
|
+
import { checkWorkflowTaxonomy } from "../../workflow/workflow-taxonomy.mjs";
|
|
5
|
+
|
|
6
|
+
export function validateConsumerWiring(cwd, configPath) {
|
|
7
|
+
const expected = consumerWorkflows("v4", configPath);
|
|
8
|
+
const alpha = consumerWorkflows("v4-alpha", configPath);
|
|
9
|
+
const directory = path.join(cwd, ".github/workflows");
|
|
10
|
+
const files = fs.existsSync(directory)
|
|
11
|
+
? fs.readdirSync(directory, { withFileTypes: true })
|
|
12
|
+
: [];
|
|
13
|
+
if (files.some((file) => file.isSymbolicLink() || file.isDirectory()))
|
|
14
|
+
throw new Error("Consumer workflows must be regular top-level files");
|
|
15
|
+
const names = files
|
|
16
|
+
.filter((file) => /\.ya?ml$/iu.test(file.name))
|
|
17
|
+
.map((file) => `.github/workflows/${file.name}`)
|
|
18
|
+
.sort();
|
|
19
|
+
if (names.some((file) => !Object.hasOwn(expected, file))) {
|
|
20
|
+
const inventory = checkWorkflowTaxonomy(cwd, {
|
|
21
|
+
integration: false,
|
|
22
|
+
documentation: false,
|
|
23
|
+
});
|
|
24
|
+
if (!inventory.ok)
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Invalid workflow implementation inventory: ${inventory.errors.join("; ")}`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
const callers = Object.keys(expected);
|
|
30
|
+
const actual = Object.fromEntries(
|
|
31
|
+
callers.map((file) => [
|
|
32
|
+
file,
|
|
33
|
+
fs.readFileSync(path.join(cwd, file), "utf8"),
|
|
34
|
+
]),
|
|
35
|
+
);
|
|
36
|
+
const matches = (templates) =>
|
|
37
|
+
callers.every((file) => templates[file] === actual[file]);
|
|
38
|
+
if (!matches(expected) && !matches(alpha))
|
|
39
|
+
throw new Error(
|
|
40
|
+
"Consumer workflow bytes differ from the shared normal/recovery template or mix channels",
|
|
41
|
+
);
|
|
42
|
+
return { channel: matches(expected) ? "v4" : "v4-alpha", workflows: callers };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function consumerConfigurationSummary(
|
|
46
|
+
loaded,
|
|
47
|
+
versionFiles,
|
|
48
|
+
derivedVersionMaterial,
|
|
49
|
+
requireLifecycleStages,
|
|
50
|
+
) {
|
|
51
|
+
const config = loaded.config;
|
|
52
|
+
for (const product of config.products)
|
|
53
|
+
for (const stage of requireLifecycleStages)
|
|
54
|
+
if (!Array.isArray(product[stage]) || !product[stage].length)
|
|
55
|
+
throw new Error(
|
|
56
|
+
`required lifecycle stage missing for product ${product.id}: ${stage}`,
|
|
57
|
+
);
|
|
58
|
+
const lifecycleStages = config.products.flatMap((product) =>
|
|
59
|
+
["install", "build", "verify"]
|
|
60
|
+
.filter((name) => product[name]?.length)
|
|
61
|
+
.map((name) => ({
|
|
62
|
+
name,
|
|
63
|
+
product: product.id,
|
|
64
|
+
mode: "commands",
|
|
65
|
+
commandCount: product[name].length,
|
|
66
|
+
})),
|
|
67
|
+
);
|
|
68
|
+
return {
|
|
69
|
+
config: { path: loaded.path, filePath: loaded.filePath, schema: 2 },
|
|
70
|
+
products: config.products,
|
|
71
|
+
channels: config.channels,
|
|
72
|
+
review: config.review,
|
|
73
|
+
version: { strategy: config.version.strategy, next: config.version.next },
|
|
74
|
+
versionFiles: versionFiles.map((file) => ({
|
|
75
|
+
path: file.path,
|
|
76
|
+
type: file.type,
|
|
77
|
+
key: file.key,
|
|
78
|
+
})),
|
|
79
|
+
derivedVersionMaterial: derivedVersionMaterial.map((file) => ({
|
|
80
|
+
path: file.path,
|
|
81
|
+
})),
|
|
82
|
+
lifecycleStages,
|
|
83
|
+
};
|
|
84
|
+
}
|