@tomflow/proflow-platform-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/conformance.json +1 -0
- package/dist/deployment/adapter.d.ts +57 -0
- package/dist/deployment/adapter.js +25 -0
- package/dist/deployment/descriptor.d.ts +39 -0
- package/dist/deployment/descriptor.js +41 -0
- package/dist/src/apply/apply.d.ts +24 -0
- package/dist/src/apply/apply.js +271 -0
- package/dist/src/apply/cleanup.d.ts +12 -0
- package/dist/src/apply/cleanup.js +37 -0
- package/dist/src/apply/current.d.ts +4 -0
- package/dist/src/apply/current.js +73 -0
- package/dist/src/apply/driver.d.ts +23 -0
- package/dist/src/apply/driver.js +154 -0
- package/dist/src/apply/execute.d.ts +36 -0
- package/dist/src/apply/execute.js +124 -0
- package/dist/src/apply/index.d.ts +8 -0
- package/dist/src/apply/index.js +4 -0
- package/dist/src/apply/reality.d.ts +20 -0
- package/dist/src/apply/reality.js +93 -0
- package/dist/src/binding/production-bindings.d.ts +45 -0
- package/dist/src/binding/production-bindings.js +102 -0
- package/dist/src/cli.d.ts +12 -0
- package/dist/src/cli.js +712 -0
- package/dist/src/contracts.d.ts +113 -0
- package/dist/src/contracts.js +1 -0
- package/dist/src/discovery/catalog.d.ts +10 -0
- package/dist/src/discovery/catalog.js +76 -0
- package/dist/src/discovery/discover.d.ts +19 -0
- package/dist/src/discovery/discover.js +124 -0
- package/dist/src/discovery/index.d.ts +5 -0
- package/dist/src/discovery/index.js +4 -0
- package/dist/src/discovery/installed.d.ts +21 -0
- package/dist/src/discovery/installed.js +130 -0
- package/dist/src/discovery/workspace.d.ts +10 -0
- package/dist/src/discovery/workspace.js +155 -0
- package/dist/src/docs/docs.d.ts +47 -0
- package/dist/src/docs/docs.js +129 -0
- package/dist/src/docs/index.d.ts +1 -0
- package/dist/src/docs/index.js +1 -0
- package/dist/src/doctor/doctor.d.ts +41 -0
- package/dist/src/doctor/doctor.js +80 -0
- package/dist/src/doctor/index.d.ts +2 -0
- package/dist/src/doctor/index.js +1 -0
- package/dist/src/errors.d.ts +6 -0
- package/dist/src/errors.js +35 -0
- package/dist/src/graph/graph.d.ts +23 -0
- package/dist/src/graph/graph.js +132 -0
- package/dist/src/graph/index.d.ts +2 -0
- package/dist/src/graph/index.js +1 -0
- package/dist/src/index.d.ts +28 -0
- package/dist/src/index.js +18 -0
- package/dist/src/install/bootstrap.d.ts +10 -0
- package/dist/src/install/bootstrap.js +79 -0
- package/dist/src/install/environment.d.ts +29 -0
- package/dist/src/install/environment.js +226 -0
- package/dist/src/install/index.d.ts +6 -0
- package/dist/src/install/index.js +3 -0
- package/dist/src/install/install.d.ts +19 -0
- package/dist/src/install/install.js +131 -0
- package/dist/src/install/package-manager.d.ts +11 -0
- package/dist/src/install/package-manager.js +58 -0
- package/dist/src/lifecycle/dispatch.d.ts +47 -0
- package/dist/src/lifecycle/dispatch.js +142 -0
- package/dist/src/lifecycle/index.d.ts +3 -0
- package/dist/src/lifecycle/index.js +2 -0
- package/dist/src/lifecycle/service-process.d.ts +11 -0
- package/dist/src/lifecycle/service-process.js +326 -0
- package/dist/src/manifest/index.d.ts +2 -0
- package/dist/src/manifest/index.js +1 -0
- package/dist/src/manifest/manifest.d.ts +61 -0
- package/dist/src/manifest/manifest.js +160 -0
- package/dist/src/modules.d.ts +11 -0
- package/dist/src/modules.js +55 -0
- package/dist/src/observer/deployment-summary.d.ts +31 -0
- package/dist/src/observer/deployment-summary.js +34 -0
- package/dist/src/paths.d.ts +21 -0
- package/dist/src/paths.js +56 -0
- package/dist/src/persistence/atomic.d.ts +2 -0
- package/dist/src/persistence/atomic.js +16 -0
- package/dist/src/persistence/config.d.ts +12 -0
- package/dist/src/persistence/config.js +43 -0
- package/dist/src/persistence/generated.d.ts +2 -0
- package/dist/src/persistence/generated.js +4 -0
- package/dist/src/persistence/guards.d.ts +7 -0
- package/dist/src/persistence/guards.js +120 -0
- package/dist/src/persistence/index.d.ts +6 -0
- package/dist/src/persistence/index.js +5 -0
- package/dist/src/persistence/plans.d.ts +5 -0
- package/dist/src/persistence/plans.js +34 -0
- package/dist/src/persistence/state.d.ts +7 -0
- package/dist/src/persistence/state.js +34 -0
- package/dist/src/persistence/verification.d.ts +5 -0
- package/dist/src/persistence/verification.js +47 -0
- package/dist/src/planner/assemble.d.ts +16 -0
- package/dist/src/planner/assemble.js +107 -0
- package/dist/src/planner/check.d.ts +20 -0
- package/dist/src/planner/check.js +96 -0
- package/dist/src/planner/fingerprint.d.ts +12 -0
- package/dist/src/planner/fingerprint.js +164 -0
- package/dist/src/planner/index.d.ts +13 -0
- package/dist/src/planner/index.js +7 -0
- package/dist/src/planner/plan.d.ts +14 -0
- package/dist/src/planner/plan.js +156 -0
- package/dist/src/planner/repair.d.ts +28 -0
- package/dist/src/planner/repair.js +131 -0
- package/dist/src/planner/stale.d.ts +7 -0
- package/dist/src/planner/stale.js +23 -0
- package/dist/src/planner/steps.d.ts +37 -0
- package/dist/src/planner/steps.js +165 -0
- package/dist/src/planner/target-catalog.d.ts +13 -0
- package/dist/src/planner/target-catalog.js +48 -0
- package/dist/src/planner/upgrade.d.ts +18 -0
- package/dist/src/planner/upgrade.js +105 -0
- package/dist/src/preflight/config.d.ts +10 -0
- package/dist/src/preflight/config.js +45 -0
- package/dist/src/preflight/findings.d.ts +26 -0
- package/dist/src/preflight/findings.js +1 -0
- package/dist/src/preflight/index.d.ts +7 -0
- package/dist/src/preflight/index.js +3 -0
- package/dist/src/preflight/preflight.d.ts +8 -0
- package/dist/src/preflight/preflight.js +174 -0
- package/dist/src/preflight/requirements.d.ts +11 -0
- package/dist/src/preflight/requirements.js +224 -0
- package/dist/src/ready/index.d.ts +2 -0
- package/dist/src/ready/index.js +1 -0
- package/dist/src/ready/ready.d.ts +40 -0
- package/dist/src/ready/ready.js +201 -0
- package/dist/src/registry/index.d.ts +2 -0
- package/dist/src/registry/index.js +1 -0
- package/dist/src/registry/npm-registry.d.ts +51 -0
- package/dist/src/registry/npm-registry.js +341 -0
- package/dist/src/security/index.d.ts +3 -0
- package/dist/src/security/index.js +2 -0
- package/dist/src/security/lock.d.ts +9 -0
- package/dist/src/security/lock.js +89 -0
- package/dist/src/security/redact.d.ts +6 -0
- package/dist/src/security/redact.js +48 -0
- package/dist/src/verification/index.d.ts +2 -0
- package/dist/src/verification/index.js +1 -0
- package/dist/src/verification/verify.d.ts +33 -0
- package/dist/src/verification/verify.js +114 -0
- package/package.json +49 -0
- package/proflow.module.json +45 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ModuleRequirement } from "@tomflow/proflow-module-contract";
|
|
2
|
+
import type { ResolvedModule } from "../contracts.ts";
|
|
3
|
+
export type ProbeStatus = "PASS" | "FAIL" | "ACTION_REQUIRED";
|
|
4
|
+
export interface RequirementProbe {
|
|
5
|
+
moduleRef: string;
|
|
6
|
+
requirement: ModuleRequirement;
|
|
7
|
+
status: ProbeStatus;
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function probeAllRequirements(modules: readonly ResolvedModule[]): Promise<RequirementProbe[]>;
|
|
11
|
+
export declare function probeRequirement(moduleRef: string, requirement: ModuleRequirement, modules: readonly ResolvedModule[]): Promise<RequirementProbe>;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { accessSync, constants } from "node:fs";
|
|
2
|
+
import { connect } from "node:net";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { versionSatisfies } from "../modules.js";
|
|
5
|
+
function compareRef(a, b) {
|
|
6
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
7
|
+
}
|
|
8
|
+
export async function probeAllRequirements(modules) {
|
|
9
|
+
const sorted = [...modules].sort((a, b) => compareRef(a.moduleRef, b.moduleRef));
|
|
10
|
+
const results = [];
|
|
11
|
+
for (const module of sorted) {
|
|
12
|
+
for (const requirement of module.requirements) {
|
|
13
|
+
results.push(await probeRequirement(module.moduleRef, requirement, modules));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return results;
|
|
17
|
+
}
|
|
18
|
+
export async function probeRequirement(moduleRef, requirement, modules) {
|
|
19
|
+
switch (requirement.kind) {
|
|
20
|
+
case "runtime":
|
|
21
|
+
return probeRuntime(moduleRef, requirement);
|
|
22
|
+
case "executable":
|
|
23
|
+
return probeExecutable(moduleRef, requirement);
|
|
24
|
+
case "filesystem":
|
|
25
|
+
return probeFilesystem(moduleRef, requirement);
|
|
26
|
+
case "port":
|
|
27
|
+
return probePort(moduleRef, requirement);
|
|
28
|
+
case "network":
|
|
29
|
+
return probeNetwork(moduleRef, requirement);
|
|
30
|
+
case "module-contract":
|
|
31
|
+
return probeModuleContract(moduleRef, requirement, modules);
|
|
32
|
+
case "human":
|
|
33
|
+
return {
|
|
34
|
+
moduleRef,
|
|
35
|
+
requirement,
|
|
36
|
+
status: "ACTION_REQUIRED",
|
|
37
|
+
message: requirement.action,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function probeRuntime(moduleRef, requirement) {
|
|
42
|
+
if (requirement.runtime !== "node") {
|
|
43
|
+
return {
|
|
44
|
+
moduleRef,
|
|
45
|
+
requirement,
|
|
46
|
+
status: "PASS",
|
|
47
|
+
message: `${requirement.runtime} runtime availability is not probed by platform-cli`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const version = process.version.startsWith("v")
|
|
51
|
+
? process.version.slice(1)
|
|
52
|
+
: process.version;
|
|
53
|
+
const satisfied = versionSatisfies(version, requirement.versionRange);
|
|
54
|
+
return satisfied
|
|
55
|
+
? {
|
|
56
|
+
moduleRef,
|
|
57
|
+
requirement,
|
|
58
|
+
status: "PASS",
|
|
59
|
+
message: `node ${version} satisfies ${requirement.versionRange}`,
|
|
60
|
+
}
|
|
61
|
+
: {
|
|
62
|
+
moduleRef,
|
|
63
|
+
requirement,
|
|
64
|
+
status: "FAIL",
|
|
65
|
+
message: `node ${version} does not satisfy ${requirement.versionRange}`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function probeExecutable(moduleRef, requirement) {
|
|
69
|
+
const found = findOnPath(requirement.command);
|
|
70
|
+
return found
|
|
71
|
+
? {
|
|
72
|
+
moduleRef,
|
|
73
|
+
requirement,
|
|
74
|
+
status: "PASS",
|
|
75
|
+
message: `executable ${requirement.command} found on PATH`,
|
|
76
|
+
}
|
|
77
|
+
: {
|
|
78
|
+
moduleRef,
|
|
79
|
+
requirement,
|
|
80
|
+
status: "FAIL",
|
|
81
|
+
message: `executable ${requirement.command} not found on PATH`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// Bounded by construction: a synchronous scan of the finite PATH directory
|
|
85
|
+
// list. It spawns no subprocess, so it cannot hang.
|
|
86
|
+
function findOnPath(command) {
|
|
87
|
+
const path = process.env.PATH ?? "";
|
|
88
|
+
const directories = path.split(":").filter((entry) => entry !== "");
|
|
89
|
+
for (const directory of directories) {
|
|
90
|
+
const candidate = join(directory, command);
|
|
91
|
+
try {
|
|
92
|
+
accessSync(candidate, constants.X_OK);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// keep searching
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
function probeFilesystem(moduleRef, requirement) {
|
|
102
|
+
const mode = requirement.access === "read"
|
|
103
|
+
? constants.R_OK
|
|
104
|
+
: requirement.access === "write"
|
|
105
|
+
? constants.W_OK
|
|
106
|
+
: constants.R_OK | constants.W_OK;
|
|
107
|
+
try {
|
|
108
|
+
accessSync(requirement.path, mode);
|
|
109
|
+
return {
|
|
110
|
+
moduleRef,
|
|
111
|
+
requirement,
|
|
112
|
+
status: "PASS",
|
|
113
|
+
message: `path ${requirement.path} is ${requirement.access} accessible`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return {
|
|
118
|
+
moduleRef,
|
|
119
|
+
requirement,
|
|
120
|
+
status: "FAIL",
|
|
121
|
+
message: `path ${requirement.path} is not ${requirement.access} accessible`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function probePort(moduleRef, requirement) {
|
|
126
|
+
if (requirement.protocol === "udp") {
|
|
127
|
+
return {
|
|
128
|
+
moduleRef,
|
|
129
|
+
requirement,
|
|
130
|
+
status: "PASS",
|
|
131
|
+
message: `udp port ${requirement.port} is not probed by platform-cli`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return new Promise((resolve) => {
|
|
135
|
+
let settled = false;
|
|
136
|
+
const socket = connect({ host: "127.0.0.1", port: requirement.port });
|
|
137
|
+
const finish = (result) => {
|
|
138
|
+
if (settled)
|
|
139
|
+
return;
|
|
140
|
+
settled = true;
|
|
141
|
+
socket.destroy();
|
|
142
|
+
resolve(result);
|
|
143
|
+
};
|
|
144
|
+
const timer = setTimeout(() => {
|
|
145
|
+
finish({
|
|
146
|
+
moduleRef,
|
|
147
|
+
requirement,
|
|
148
|
+
status: "FAIL",
|
|
149
|
+
message: `port ${requirement.port} probe timed out`,
|
|
150
|
+
});
|
|
151
|
+
}, 2_000);
|
|
152
|
+
socket.once("connect", () => {
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
finish({
|
|
155
|
+
moduleRef,
|
|
156
|
+
requirement,
|
|
157
|
+
status: "FAIL",
|
|
158
|
+
message: `port ${requirement.port} is already in use`,
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
socket.once("error", () => {
|
|
162
|
+
clearTimeout(timer);
|
|
163
|
+
finish({
|
|
164
|
+
moduleRef,
|
|
165
|
+
requirement,
|
|
166
|
+
status: "PASS",
|
|
167
|
+
message: `port ${requirement.port} is available`,
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
async function probeNetwork(moduleRef, requirement) {
|
|
173
|
+
try {
|
|
174
|
+
const response = await fetch(requirement.url, {
|
|
175
|
+
method: "GET",
|
|
176
|
+
signal: AbortSignal.timeout(3_000),
|
|
177
|
+
});
|
|
178
|
+
return {
|
|
179
|
+
moduleRef,
|
|
180
|
+
requirement,
|
|
181
|
+
status: "PASS",
|
|
182
|
+
message: `network ${requirement.url} reachable (HTTP ${response.status})`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return {
|
|
187
|
+
moduleRef,
|
|
188
|
+
requirement,
|
|
189
|
+
status: "FAIL",
|
|
190
|
+
message: `network ${requirement.url} unreachable`,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function probeModuleContract(moduleRef, requirement, modules) {
|
|
195
|
+
const providers = modules.filter((module) => module.provides.some((provide) => provide.contractRef === requirement.contractRef));
|
|
196
|
+
const matching = providers.filter((module) => module.provides.some((provide) => provide.contractRef === requirement.contractRef &&
|
|
197
|
+
versionSatisfies(provide.version, requirement.versionRange)));
|
|
198
|
+
if (matching.length > 0) {
|
|
199
|
+
const refs = matching
|
|
200
|
+
.map((module) => module.moduleRef)
|
|
201
|
+
.sort(compareRef)
|
|
202
|
+
.join(", ");
|
|
203
|
+
return {
|
|
204
|
+
moduleRef,
|
|
205
|
+
requirement,
|
|
206
|
+
status: "PASS",
|
|
207
|
+
message: `module-contract ${requirement.contractRef} satisfied by ${refs}`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (providers.length > 0) {
|
|
211
|
+
return {
|
|
212
|
+
moduleRef,
|
|
213
|
+
requirement,
|
|
214
|
+
status: "FAIL",
|
|
215
|
+
message: `module-contract ${requirement.contractRef} providers incompatible with ${requirement.versionRange}`,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
moduleRef,
|
|
220
|
+
requirement,
|
|
221
|
+
status: "FAIL",
|
|
222
|
+
message: `module-contract ${requirement.contractRef} has no provider`,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { assessPlatformReady } from "./ready.js";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { PlatformState, ResolvedModule, VerificationRecord } from "../contracts.ts";
|
|
2
|
+
import type { LifecycleRunResult } from "../lifecycle/index.ts";
|
|
3
|
+
export type ReadyFindingSeverity = "blocking" | "degraded";
|
|
4
|
+
export type ReadyFindingCode = "DEPENDENCY_UNRESOLVED" | "DEPENDENCY_INCOMPATIBLE" | "DEPENDENCY_CYCLE" | "MODULE_REF_UNRESOLVED" | "CONFIG_MISSING" | "RUNTIME_STATUS_UNAVAILABLE" | "RUNTIME_NOT_READY" | "RUNTIME_ACTION_REQUIRED" | "RUNTIME_CHECK_FAIL" | "RUNTIME_CHECK_WARN" | "VERIFICATION_MISSING" | "VERIFICATION_STALE" | "VERIFICATION_FAIL" | "BLOCKING_ACTION";
|
|
5
|
+
export interface ReadyFinding {
|
|
6
|
+
severity: ReadyFindingSeverity;
|
|
7
|
+
code: ReadyFindingCode;
|
|
8
|
+
moduleRef?: string;
|
|
9
|
+
message: string;
|
|
10
|
+
}
|
|
11
|
+
export interface BlockingAction {
|
|
12
|
+
moduleRef?: string;
|
|
13
|
+
action: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ResourceReality {
|
|
17
|
+
moduleRef: string;
|
|
18
|
+
resourceIdentity?: string;
|
|
19
|
+
resourceVersion?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PlatformReadyInput {
|
|
22
|
+
modules: readonly ResolvedModule[];
|
|
23
|
+
status: readonly LifecycleRunResult[];
|
|
24
|
+
verification: readonly VerificationRecord[];
|
|
25
|
+
config?: Record<string, Record<string, string>>;
|
|
26
|
+
blockingActions?: readonly BlockingAction[];
|
|
27
|
+
resources?: readonly ResourceReality[];
|
|
28
|
+
}
|
|
29
|
+
export interface PlatformReadyResult {
|
|
30
|
+
state: PlatformState;
|
|
31
|
+
findings: ReadyFinding[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Aggregates current reality into a single PlatformState. READY is derived only
|
|
35
|
+
* from live runtime status, current-version verification records, resolved
|
|
36
|
+
* dependencies, required config, and the absence of blocking human actions.
|
|
37
|
+
* Persisted PASS history, a bare SUCCEEDED operation, or a schema-valid but
|
|
38
|
+
* non-success structured result can never fabricate READY on their own.
|
|
39
|
+
*/
|
|
40
|
+
export declare function assessPlatformReady(input: PlatformReadyInput): PlatformReadyResult;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { PlatformError } from "../errors.js";
|
|
2
|
+
import { buildDependencyGraph, ModuleRefUnresolvedError, } from "../graph/index.js";
|
|
3
|
+
import { checkConfigReadiness } from "../preflight/index.js";
|
|
4
|
+
function compareRef(a, b) {
|
|
5
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
6
|
+
}
|
|
7
|
+
function toDependencyCode(code) {
|
|
8
|
+
switch (code) {
|
|
9
|
+
case "DEPENDENCY_UNRESOLVED":
|
|
10
|
+
case "DEPENDENCY_INCOMPATIBLE":
|
|
11
|
+
case "DEPENDENCY_CYCLE":
|
|
12
|
+
return code;
|
|
13
|
+
default:
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function isHumanAction(code) {
|
|
18
|
+
return code === "RUNTIME_ACTION_REQUIRED" || code === "BLOCKING_ACTION";
|
|
19
|
+
}
|
|
20
|
+
function deriveState(findings) {
|
|
21
|
+
let hardFailure = false;
|
|
22
|
+
let humanAction = false;
|
|
23
|
+
let degraded = false;
|
|
24
|
+
for (const finding of findings) {
|
|
25
|
+
if (finding.severity === "degraded") {
|
|
26
|
+
degraded = true;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (isHumanAction(finding.code))
|
|
30
|
+
humanAction = true;
|
|
31
|
+
else
|
|
32
|
+
hardFailure = true;
|
|
33
|
+
}
|
|
34
|
+
if (hardFailure)
|
|
35
|
+
return "NOT_READY";
|
|
36
|
+
if (humanAction)
|
|
37
|
+
return "ACTION_REQUIRED";
|
|
38
|
+
if (degraded)
|
|
39
|
+
return "DEGRADED";
|
|
40
|
+
return "READY";
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Aggregates current reality into a single PlatformState. READY is derived only
|
|
44
|
+
* from live runtime status, current-version verification records, resolved
|
|
45
|
+
* dependencies, required config, and the absence of blocking human actions.
|
|
46
|
+
* Persisted PASS history, a bare SUCCEEDED operation, or a schema-valid but
|
|
47
|
+
* non-success structured result can never fabricate READY on their own.
|
|
48
|
+
*/
|
|
49
|
+
export function assessPlatformReady(input) {
|
|
50
|
+
const findings = [];
|
|
51
|
+
const modules = [...input.modules].sort((a, b) => compareRef(a.moduleRef, b.moduleRef));
|
|
52
|
+
// logical dependencies, cross-module compatibility, and moduleRef bindings
|
|
53
|
+
try {
|
|
54
|
+
buildDependencyGraph(modules, input.config === undefined ? {} : { config: input.config });
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (error instanceof ModuleRefUnresolvedError) {
|
|
58
|
+
findings.push({
|
|
59
|
+
severity: "blocking",
|
|
60
|
+
code: "MODULE_REF_UNRESOLVED",
|
|
61
|
+
moduleRef: error.from,
|
|
62
|
+
message: error.message,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
else if (error instanceof PlatformError) {
|
|
66
|
+
const code = toDependencyCode(error.code);
|
|
67
|
+
if (code === undefined)
|
|
68
|
+
throw error;
|
|
69
|
+
findings.push({ severity: "blocking", code, message: error.message });
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// required config ready
|
|
76
|
+
for (const finding of checkConfigReadiness(modules, input.config)) {
|
|
77
|
+
findings.push({
|
|
78
|
+
severity: "blocking",
|
|
79
|
+
code: "CONFIG_MISSING",
|
|
80
|
+
...(finding.moduleRef !== undefined
|
|
81
|
+
? { moduleRef: finding.moduleRef }
|
|
82
|
+
: {}),
|
|
83
|
+
message: finding.message,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// required runtimes current READY (live status, never persisted)
|
|
87
|
+
const statusByRef = new Map(input.status.map((run) => [run.moduleRef, run]));
|
|
88
|
+
for (const module of modules) {
|
|
89
|
+
if (!module.lifecycle.includes("status"))
|
|
90
|
+
continue;
|
|
91
|
+
const run = statusByRef.get(module.moduleRef);
|
|
92
|
+
if (run === undefined ||
|
|
93
|
+
run.status !== "EXECUTED" ||
|
|
94
|
+
run.result === undefined) {
|
|
95
|
+
findings.push({
|
|
96
|
+
severity: "blocking",
|
|
97
|
+
code: "RUNTIME_STATUS_UNAVAILABLE",
|
|
98
|
+
moduleRef: module.moduleRef,
|
|
99
|
+
message: `no live status observed for ${module.moduleRef}`,
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const result = run.result;
|
|
104
|
+
if (result.status === "ACTION_REQUIRED") {
|
|
105
|
+
const action = result.actionRequired;
|
|
106
|
+
findings.push({
|
|
107
|
+
severity: "blocking",
|
|
108
|
+
code: "RUNTIME_ACTION_REQUIRED",
|
|
109
|
+
moduleRef: module.moduleRef,
|
|
110
|
+
message: action !== undefined
|
|
111
|
+
? `${module.moduleRef} requires action "${action.action}"`
|
|
112
|
+
: `${module.moduleRef} requires an unspecified human action`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
else if (result.status !== "SUCCEEDED") {
|
|
116
|
+
findings.push({
|
|
117
|
+
severity: "blocking",
|
|
118
|
+
code: "RUNTIME_NOT_READY",
|
|
119
|
+
moduleRef: module.moduleRef,
|
|
120
|
+
message: `runtime ${module.moduleRef} is ${result.status.toLowerCase()}`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
for (const runtimeCheck of result.checks ?? []) {
|
|
124
|
+
if (runtimeCheck.status === "FAIL") {
|
|
125
|
+
findings.push({
|
|
126
|
+
severity: "blocking",
|
|
127
|
+
code: "RUNTIME_CHECK_FAIL",
|
|
128
|
+
moduleRef: module.moduleRef,
|
|
129
|
+
message: `runtime check ${runtimeCheck.id} failed for ${module.moduleRef}`,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
else if (runtimeCheck.status === "WARN") {
|
|
133
|
+
findings.push({
|
|
134
|
+
severity: "degraded",
|
|
135
|
+
code: "RUNTIME_CHECK_WARN",
|
|
136
|
+
moduleRef: module.moduleRef,
|
|
137
|
+
message: `runtime check ${runtimeCheck.id} warns for ${module.moduleRef}`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// current-version verification PASS against current resource reality
|
|
143
|
+
const recordsByRef = new Map();
|
|
144
|
+
for (const record of input.verification) {
|
|
145
|
+
const list = recordsByRef.get(record.moduleRef) ?? [];
|
|
146
|
+
list.push(record);
|
|
147
|
+
recordsByRef.set(record.moduleRef, list);
|
|
148
|
+
}
|
|
149
|
+
const resourcesByRef = new Map((input.resources ?? []).map((resource) => [resource.moduleRef, resource]));
|
|
150
|
+
for (const module of modules) {
|
|
151
|
+
if (!module.lifecycle.includes("verify"))
|
|
152
|
+
continue;
|
|
153
|
+
const records = recordsByRef.get(module.moduleRef) ?? [];
|
|
154
|
+
const reality = resourcesByRef.get(module.moduleRef);
|
|
155
|
+
const current = records.filter((record) => {
|
|
156
|
+
if (record.moduleVersion !== module.moduleVersion)
|
|
157
|
+
return false;
|
|
158
|
+
if (reality === undefined)
|
|
159
|
+
return true;
|
|
160
|
+
if (reality.resourceIdentity !== undefined &&
|
|
161
|
+
record.resourceIdentity !== reality.resourceIdentity) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
if (reality.resourceVersion !== undefined &&
|
|
165
|
+
record.resourceVersion !== reality.resourceVersion) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
return true;
|
|
169
|
+
});
|
|
170
|
+
const latestCurrent = current[current.length - 1];
|
|
171
|
+
if (latestCurrent === undefined) {
|
|
172
|
+
const code = records.length === 0 ? "VERIFICATION_MISSING" : "VERIFICATION_STALE";
|
|
173
|
+
findings.push({
|
|
174
|
+
severity: "blocking",
|
|
175
|
+
code,
|
|
176
|
+
moduleRef: module.moduleRef,
|
|
177
|
+
message: `no current-version verification record for ${module.moduleRef}@${module.moduleVersion}`,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
else if (latestCurrent.result !== "PASS") {
|
|
181
|
+
findings.push({
|
|
182
|
+
severity: "blocking",
|
|
183
|
+
code: "VERIFICATION_FAIL",
|
|
184
|
+
moduleRef: module.moduleRef,
|
|
185
|
+
message: `current-version verification FAIL for ${module.moduleRef}@${module.moduleVersion}`,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// blocking ACTION_REQUIRED count must be zero
|
|
190
|
+
for (const action of input.blockingActions ?? []) {
|
|
191
|
+
findings.push({
|
|
192
|
+
severity: "blocking",
|
|
193
|
+
code: "BLOCKING_ACTION",
|
|
194
|
+
...(action.moduleRef !== undefined
|
|
195
|
+
? { moduleRef: action.moduleRef }
|
|
196
|
+
: {}),
|
|
197
|
+
message: action.description ?? action.action,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return { state: deriveState(findings), findings };
|
|
201
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export type { NpmCommandResult, NpmCommandRunner, RegistryDiscoveryResult, RegistryModuleCandidate, RegistryRejectedPackage, } from "./npm-registry.ts";
|
|
2
|
+
export { discoverRegistryInstallClosure, discoverRegistryModules, loadRegistryModuleDescriptor, PRO_FLOW_PACKAGE_PREFIX, PRO_FLOW_SCOPE, resolveScopeRegistry, systemNpmRunner, } from "./npm-registry.ts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { discoverRegistryInstallClosure, discoverRegistryModules, loadRegistryModuleDescriptor, PRO_FLOW_PACKAGE_PREFIX, PRO_FLOW_SCOPE, resolveScopeRegistry, systemNpmRunner, } from "./npm-registry.js";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type ModuleDescriptor, type ModuleInstallClass } from "@tomflow/proflow-module-contract";
|
|
2
|
+
export declare const PRO_FLOW_SCOPE = "@tomflow";
|
|
3
|
+
export declare const PRO_FLOW_PACKAGE_PREFIX = "@tomflow/proflow-";
|
|
4
|
+
export interface NpmCommandResult {
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
}
|
|
8
|
+
export interface NpmCommandRunner {
|
|
9
|
+
run(args: readonly string[], cwd: string): Promise<NpmCommandResult>;
|
|
10
|
+
}
|
|
11
|
+
export interface RegistryModuleCandidate {
|
|
12
|
+
packageName: string;
|
|
13
|
+
moduleVersion: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
installClass: ModuleInstallClass;
|
|
16
|
+
descriptor: string;
|
|
17
|
+
manifest: "./proflow.module.json";
|
|
18
|
+
installRequires: string[];
|
|
19
|
+
nodeVersionRange?: string;
|
|
20
|
+
registry: string;
|
|
21
|
+
}
|
|
22
|
+
export interface RegistryRejectedPackage {
|
|
23
|
+
packageName: string;
|
|
24
|
+
moduleVersion?: string;
|
|
25
|
+
reason: "NOT_PROFLOW_MODULE" | "DEPRECATED" | "NODE_INCOMPATIBLE" | "METADATA_INVALID";
|
|
26
|
+
message: string;
|
|
27
|
+
}
|
|
28
|
+
export interface RegistryDiscoveryResult {
|
|
29
|
+
registry: string;
|
|
30
|
+
candidates: RegistryModuleCandidate[];
|
|
31
|
+
rejected: RegistryRejectedPackage[];
|
|
32
|
+
}
|
|
33
|
+
export declare function systemNpmRunner(): NpmCommandRunner;
|
|
34
|
+
export declare function resolveScopeRegistry(workspaceRoot: string, runner?: NpmCommandRunner): Promise<string>;
|
|
35
|
+
export declare function discoverRegistryModules(options: {
|
|
36
|
+
workspaceRoot: string;
|
|
37
|
+
packageName?: string;
|
|
38
|
+
runner?: NpmCommandRunner;
|
|
39
|
+
nodeVersion?: string;
|
|
40
|
+
}): Promise<RegistryDiscoveryResult>;
|
|
41
|
+
export declare function loadRegistryModuleDescriptor(options: {
|
|
42
|
+
workspaceRoot: string;
|
|
43
|
+
candidate: RegistryModuleCandidate;
|
|
44
|
+
runner?: NpmCommandRunner;
|
|
45
|
+
}): Promise<ModuleDescriptor>;
|
|
46
|
+
export declare function discoverRegistryInstallClosure(options: {
|
|
47
|
+
workspaceRoot: string;
|
|
48
|
+
packageName: string;
|
|
49
|
+
runner?: NpmCommandRunner;
|
|
50
|
+
nodeVersion?: string;
|
|
51
|
+
}): Promise<RegistryDiscoveryResult>;
|