@tomflow/proflow-platform-cli 0.1.14 → 0.1.15
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 +48 -43
- package/dist/deployment/adapter.d.ts +5 -5
- package/dist/deployment/adapter.js +2 -1
- package/dist/deployment/descriptor.d.ts +2 -3
- package/dist/deployment/descriptor.js +2 -3
- package/dist/src/binding/production-bindings.d.ts +17 -15
- package/dist/src/binding/production-bindings.js +18 -59
- package/dist/src/cli.d.ts +7 -9
- package/dist/src/cli.js +327 -1316
- package/dist/src/contracts.d.ts +4 -97
- package/dist/src/discovery/discover.js +0 -1
- package/dist/src/docs/aggregate.d.ts +18 -0
- package/dist/src/docs/aggregate.js +44 -0
- package/dist/src/docs/docs.d.ts +0 -31
- package/dist/src/docs/docs.js +0 -61
- package/dist/src/docs/index.d.ts +2 -1
- package/dist/src/docs/index.js +1 -1
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js +0 -18
- package/dist/src/graph/graph.d.ts +1 -4
- package/dist/src/graph/graph.js +1 -13
- package/dist/src/graph/index.d.ts +2 -2
- package/dist/src/graph/index.js +1 -1
- package/dist/src/index.d.ts +4 -24
- package/dist/src/index.js +2 -16
- package/dist/src/install/package-manager.d.ts +21 -0
- package/dist/src/install/package-manager.js +153 -0
- package/dist/src/lifecycle/dispatch.d.ts +0 -31
- package/dist/src/lifecycle/dispatch.js +0 -125
- package/dist/src/lifecycle/index.d.ts +4 -3
- package/dist/src/lifecycle/index.js +2 -2
- package/dist/src/lifecycle/thin.d.ts +11 -0
- package/dist/src/lifecycle/thin.js +68 -0
- package/dist/src/paths.d.ts +0 -14
- package/dist/src/paths.js +0 -39
- package/dist/src/persistence/config.d.ts +0 -6
- package/dist/src/persistence/config.js +0 -14
- package/dist/src/persistence/guards.d.ts +0 -6
- package/dist/src/persistence/guards.js +0 -114
- package/dist/src/persistence/index.d.ts +4 -6
- package/dist/src/persistence/index.js +2 -5
- package/dist/src/persistence/workspace-metadata.d.ts +7 -0
- package/dist/src/persistence/workspace-metadata.js +39 -0
- package/dist/src/registry/index.d.ts +1 -1
- package/dist/src/registry/index.js +1 -1
- package/dist/src/registry/npm-registry.d.ts +0 -14
- package/dist/src/registry/npm-registry.js +1 -132
- package/package.json +2 -6
- package/proflow.module.json +2 -3
- package/CHANGELOG.md +0 -85
- package/dist/src/apply/apply.d.ts +0 -26
- package/dist/src/apply/apply.js +0 -314
- package/dist/src/apply/cleanup.d.ts +0 -12
- package/dist/src/apply/cleanup.js +0 -37
- package/dist/src/apply/current.d.ts +0 -5
- package/dist/src/apply/current.js +0 -95
- package/dist/src/apply/driver.d.ts +0 -28
- package/dist/src/apply/driver.js +0 -239
- package/dist/src/apply/execute.d.ts +0 -36
- package/dist/src/apply/execute.js +0 -152
- package/dist/src/apply/index.d.ts +0 -8
- package/dist/src/apply/index.js +0 -4
- package/dist/src/apply/reality.d.ts +0 -20
- package/dist/src/apply/reality.js +0 -105
- package/dist/src/binding/global-binding.d.ts +0 -69
- package/dist/src/binding/global-binding.js +0 -349
- package/dist/src/doctor/doctor.d.ts +0 -41
- package/dist/src/doctor/doctor.js +0 -80
- package/dist/src/doctor/index.d.ts +0 -2
- package/dist/src/doctor/index.js +0 -1
- package/dist/src/install/bootstrap.d.ts +0 -10
- package/dist/src/install/bootstrap.js +0 -79
- package/dist/src/install/environment.d.ts +0 -30
- package/dist/src/install/environment.js +0 -230
- package/dist/src/install/index.d.ts +0 -6
- package/dist/src/install/index.js +0 -3
- package/dist/src/install/install.d.ts +0 -19
- package/dist/src/install/install.js +0 -172
- package/dist/src/lifecycle/service-process.d.ts +0 -11
- package/dist/src/lifecycle/service-process.js +0 -356
- package/dist/src/manifest/index.d.ts +0 -2
- package/dist/src/manifest/index.js +0 -1
- package/dist/src/manifest/manifest.d.ts +0 -61
- package/dist/src/manifest/manifest.js +0 -167
- package/dist/src/observer/deployment-summary.d.ts +0 -31
- package/dist/src/observer/deployment-summary.js +0 -34
- package/dist/src/persistence/atomic.d.ts +0 -2
- package/dist/src/persistence/atomic.js +0 -16
- package/dist/src/persistence/effective-config.d.ts +0 -3
- package/dist/src/persistence/effective-config.js +0 -20
- package/dist/src/persistence/generated.d.ts +0 -2
- package/dist/src/persistence/generated.js +0 -4
- package/dist/src/persistence/plans.d.ts +0 -5
- package/dist/src/persistence/plans.js +0 -34
- package/dist/src/persistence/state.d.ts +0 -7
- package/dist/src/persistence/state.js +0 -34
- package/dist/src/persistence/verification.d.ts +0 -5
- package/dist/src/persistence/verification.js +0 -47
- package/dist/src/planner/assemble.d.ts +0 -16
- package/dist/src/planner/assemble.js +0 -107
- package/dist/src/planner/check.d.ts +0 -20
- package/dist/src/planner/check.js +0 -107
- package/dist/src/planner/fingerprint.d.ts +0 -12
- package/dist/src/planner/fingerprint.js +0 -164
- package/dist/src/planner/index.d.ts +0 -13
- package/dist/src/planner/index.js +0 -7
- package/dist/src/planner/plan.d.ts +0 -15
- package/dist/src/planner/plan.js +0 -144
- package/dist/src/planner/repair.d.ts +0 -28
- package/dist/src/planner/repair.js +0 -131
- package/dist/src/planner/stale.d.ts +0 -7
- package/dist/src/planner/stale.js +0 -23
- package/dist/src/planner/steps.d.ts +0 -37
- package/dist/src/planner/steps.js +0 -165
- package/dist/src/planner/target-catalog.d.ts +0 -13
- package/dist/src/planner/target-catalog.js +0 -48
- package/dist/src/planner/upgrade.d.ts +0 -18
- package/dist/src/planner/upgrade.js +0 -114
- package/dist/src/preflight/config.d.ts +0 -11
- package/dist/src/preflight/config.js +0 -88
- package/dist/src/preflight/findings.d.ts +0 -26
- package/dist/src/preflight/findings.js +0 -1
- package/dist/src/preflight/index.d.ts +0 -7
- package/dist/src/preflight/index.js +0 -3
- package/dist/src/preflight/preflight.d.ts +0 -10
- package/dist/src/preflight/preflight.js +0 -190
- package/dist/src/preflight/requirements.d.ts +0 -11
- package/dist/src/preflight/requirements.js +0 -231
- package/dist/src/ready/index.d.ts +0 -2
- package/dist/src/ready/index.js +0 -1
- package/dist/src/ready/ready.d.ts +0 -40
- package/dist/src/ready/ready.js +0 -201
- package/dist/src/security/index.d.ts +0 -3
- package/dist/src/security/index.js +0 -2
- package/dist/src/security/lock.d.ts +0 -9
- package/dist/src/security/lock.js +0 -127
- package/dist/src/security/redact.d.ts +0 -6
- package/dist/src/security/redact.js +0 -48
- package/dist/src/verification/index.d.ts +0 -2
- package/dist/src/verification/index.js +0 -1
- package/dist/src/verification/verify.d.ts +0 -33
- package/dist/src/verification/verify.js +0 -114
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { dispatchLifecycle } from "../lifecycle/index.js";
|
|
2
|
-
import { loadConfig, loadLatestVerification } from "../persistence/index.js";
|
|
3
|
-
export function createRealityObserver(deps) {
|
|
4
|
-
return {
|
|
5
|
-
observe(step, plan) {
|
|
6
|
-
return observeStep(deps, step, plan);
|
|
7
|
-
},
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
function moduleOf(plan, step) {
|
|
11
|
-
return plan.resolvedModules.find((module) => module.moduleRef === step.moduleRef);
|
|
12
|
-
}
|
|
13
|
-
function isRecord(value) {
|
|
14
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
|
-
}
|
|
16
|
-
function isStringMap(value) {
|
|
17
|
-
if (!isRecord(value))
|
|
18
|
-
return false;
|
|
19
|
-
return Object.values(value).every((item) => typeof item === "string");
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Runtime-validates an adapter `status` data payload into a `StepReality` at the
|
|
23
|
-
* boundary. Only recognized fields survive; anything else is ignored so an
|
|
24
|
-
* arbitrary payload can never be mistaken for observed reality. A service
|
|
25
|
-
* `status` payload carrying `state: "RUNNING" | "STOPPED"` maps onto the
|
|
26
|
-
* boolean `processRunning` reality.
|
|
27
|
-
*/
|
|
28
|
-
function parseStatusReality(value) {
|
|
29
|
-
if (!isRecord(value))
|
|
30
|
-
return {};
|
|
31
|
-
const reality = {};
|
|
32
|
-
if (typeof value.processRunning === "boolean") {
|
|
33
|
-
reality.processRunning = value.processRunning;
|
|
34
|
-
}
|
|
35
|
-
else if (value.state === "RUNNING") {
|
|
36
|
-
reality.processRunning = true;
|
|
37
|
-
}
|
|
38
|
-
else if (value.state === "STOPPED") {
|
|
39
|
-
reality.processRunning = false;
|
|
40
|
-
}
|
|
41
|
-
if (typeof value.resourceConfigured === "boolean") {
|
|
42
|
-
reality.resourceConfigured = value.resourceConfigured;
|
|
43
|
-
}
|
|
44
|
-
if (typeof value.humanActionVerified === "boolean") {
|
|
45
|
-
reality.humanActionVerified = value.humanActionVerified;
|
|
46
|
-
}
|
|
47
|
-
if (typeof value.migrated === "boolean") {
|
|
48
|
-
reality.migrated = value.migrated;
|
|
49
|
-
}
|
|
50
|
-
if (typeof value.installedVersion === "string") {
|
|
51
|
-
reality.installedVersion = value.installedVersion;
|
|
52
|
-
}
|
|
53
|
-
if (isStringMap(value.configValues)) {
|
|
54
|
-
reality.configValues = value.configValues;
|
|
55
|
-
}
|
|
56
|
-
return reality;
|
|
57
|
-
}
|
|
58
|
-
async function observeViaStatus(catalog, module) {
|
|
59
|
-
if (!module.lifecycle.includes("status"))
|
|
60
|
-
return {};
|
|
61
|
-
try {
|
|
62
|
-
const dispatched = await dispatchLifecycle(catalog, module, "status");
|
|
63
|
-
return parseStatusReality(dispatched.result.data);
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
// Observation failed (transport/timeout/malformed): UNKNOWN, never a
|
|
67
|
-
// fabricated NOT_SATISFIED that would replay a lifecycle/external effect.
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
async function observeStep(deps, step, plan) {
|
|
72
|
-
const module = moduleOf(plan, step);
|
|
73
|
-
if (module === undefined)
|
|
74
|
-
return {};
|
|
75
|
-
switch (step.kind) {
|
|
76
|
-
case "package": {
|
|
77
|
-
const installedVersion = await deps.driver.observeInstalledVersion(module);
|
|
78
|
-
return installedVersion === undefined ? {} : { installedVersion };
|
|
79
|
-
}
|
|
80
|
-
case "config": {
|
|
81
|
-
const config = await loadConfig(deps.paths, step.moduleRef);
|
|
82
|
-
if (config === undefined)
|
|
83
|
-
return {};
|
|
84
|
-
return {
|
|
85
|
-
configValues: { ...config.publicValues, ...config.secretValues },
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
case "lifecycle":
|
|
89
|
-
case "external-resource":
|
|
90
|
-
return observeViaStatus(deps.catalog, module);
|
|
91
|
-
case "human": {
|
|
92
|
-
const statusReality = await observeViaStatus(deps.catalog, module);
|
|
93
|
-
if (statusReality === undefined)
|
|
94
|
-
return undefined;
|
|
95
|
-
if (statusReality.humanActionVerified === true)
|
|
96
|
-
return statusReality;
|
|
97
|
-
const latest = await loadLatestVerification(deps.paths, module.moduleRef);
|
|
98
|
-
return {
|
|
99
|
-
...statusReality,
|
|
100
|
-
humanActionVerified: latest?.result === "PASS" &&
|
|
101
|
-
latest.moduleVersion === module.moduleVersion,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
export declare const GLOBAL_BINDING_CONTRACT: "deployment.global-binding.v1";
|
|
2
|
-
export declare const WORKSPACE_INSTANCE_CONTRACT: "deployment.workspace-instance.v1";
|
|
3
|
-
export type GlobalBindingState = "INSTALLING" | "INSTALLED" | "UNINSTALLING" | "BROKEN";
|
|
4
|
-
export interface GlobalWorkspaceBinding {
|
|
5
|
-
contract: typeof GLOBAL_BINDING_CONTRACT;
|
|
6
|
-
state: GlobalBindingState;
|
|
7
|
-
workspacePath: string;
|
|
8
|
-
workspaceRealPath: string;
|
|
9
|
-
workspaceInstanceId: string;
|
|
10
|
-
createdAt: string;
|
|
11
|
-
updatedAt: string;
|
|
12
|
-
failure?: {
|
|
13
|
-
code: string;
|
|
14
|
-
message: string;
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
export interface WorkspaceInstanceMarker {
|
|
18
|
-
contract: typeof WORKSPACE_INSTANCE_CONTRACT;
|
|
19
|
-
workspaceInstanceId: string;
|
|
20
|
-
workspaceRealPath: string;
|
|
21
|
-
createdAt: string;
|
|
22
|
-
}
|
|
23
|
-
export interface GlobalPlatformPaths {
|
|
24
|
-
root: string;
|
|
25
|
-
bindingJson: string;
|
|
26
|
-
bindingLock: string;
|
|
27
|
-
operationLock: string;
|
|
28
|
-
}
|
|
29
|
-
export interface BoundWorkspaceObservation {
|
|
30
|
-
binding: GlobalWorkspaceBinding;
|
|
31
|
-
workspaceExists: boolean;
|
|
32
|
-
}
|
|
33
|
-
export interface GlobalBindingLockHandle {
|
|
34
|
-
release(): Promise<void>;
|
|
35
|
-
}
|
|
36
|
-
export declare function globalPlatformPaths(globalRoot?: string): GlobalPlatformPaths;
|
|
37
|
-
export declare function canonicalizeWorkspace(workspace: string): Promise<{
|
|
38
|
-
workspacePath: string;
|
|
39
|
-
workspaceRealPath: string;
|
|
40
|
-
}>;
|
|
41
|
-
export declare function loadGlobalBinding(globalRoot?: string): Promise<GlobalWorkspaceBinding | undefined>;
|
|
42
|
-
export declare function observeBoundWorkspace(globalRoot?: string): Promise<BoundWorkspaceObservation | undefined>;
|
|
43
|
-
export declare function requireBoundWorkspace(globalRoot?: string): Promise<GlobalWorkspaceBinding>;
|
|
44
|
-
export declare function claimWorkspaceBinding(options: {
|
|
45
|
-
workspace: string;
|
|
46
|
-
globalRoot?: string | undefined;
|
|
47
|
-
}): Promise<{
|
|
48
|
-
binding: GlobalWorkspaceBinding;
|
|
49
|
-
alreadyBound: boolean;
|
|
50
|
-
}>;
|
|
51
|
-
export declare function updateGlobalBindingState(options: {
|
|
52
|
-
workspaceInstanceId: string;
|
|
53
|
-
state: GlobalBindingState;
|
|
54
|
-
globalRoot?: string | undefined;
|
|
55
|
-
failure?: {
|
|
56
|
-
code: string;
|
|
57
|
-
message: string;
|
|
58
|
-
};
|
|
59
|
-
}): Promise<GlobalWorkspaceBinding>;
|
|
60
|
-
export declare function clearGlobalBinding(options: {
|
|
61
|
-
workspaceInstanceId: string;
|
|
62
|
-
globalRoot?: string | undefined;
|
|
63
|
-
removeWorkspaceMarker?: boolean;
|
|
64
|
-
}): Promise<void>;
|
|
65
|
-
export declare function forgetMissingWorkspaceBinding(options: {
|
|
66
|
-
globalRoot?: string | undefined;
|
|
67
|
-
}): Promise<GlobalWorkspaceBinding>;
|
|
68
|
-
export declare function acquireGlobalBindingLock(paths: GlobalPlatformPaths): Promise<GlobalBindingLockHandle>;
|
|
69
|
-
export declare function acquireGlobalOperationLock(globalRoot?: string): Promise<GlobalBindingLockHandle>;
|
|
@@ -1,349 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { constants } from "node:fs";
|
|
3
|
-
import { access, chmod, mkdir, open, readFile, realpath, rm, stat, unlink, } from "node:fs/promises";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
|
-
import { dirname, join, resolve } from "node:path";
|
|
6
|
-
import { PlatformError } from "../errors.js";
|
|
7
|
-
import { atomicWrite } from "../paths.js";
|
|
8
|
-
export const GLOBAL_BINDING_CONTRACT = "deployment.global-binding.v1";
|
|
9
|
-
export const WORKSPACE_INSTANCE_CONTRACT = "deployment.workspace-instance.v1";
|
|
10
|
-
export function globalPlatformPaths(globalRoot) {
|
|
11
|
-
const root = globalRoot ??
|
|
12
|
-
process.env.PROFLOW_GLOBAL_HOME ??
|
|
13
|
-
join(homedir(), ".proflow", "platform");
|
|
14
|
-
return {
|
|
15
|
-
root,
|
|
16
|
-
bindingJson: join(root, "binding.json"),
|
|
17
|
-
bindingLock: join(root, "binding.lock"),
|
|
18
|
-
operationLock: join(root, "operation.lock"),
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
export async function canonicalizeWorkspace(workspace) {
|
|
22
|
-
const workspacePath = resolve(workspace);
|
|
23
|
-
let info;
|
|
24
|
-
try {
|
|
25
|
-
info = await stat(workspacePath);
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
throw new PlatformError("WORKSPACE_NOT_FOUND", `workspace does not exist: ${workspacePath}`);
|
|
29
|
-
}
|
|
30
|
-
if (!info.isDirectory()) {
|
|
31
|
-
throw new PlatformError("WORKSPACE_NOT_FOUND", `workspace is not a directory: ${workspacePath}`);
|
|
32
|
-
}
|
|
33
|
-
try {
|
|
34
|
-
await access(workspacePath, constants.R_OK | constants.W_OK);
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
throw new PlatformError("WORKSPACE_NOT_WRITABLE", `workspace is not readable and writable: ${workspacePath}`);
|
|
38
|
-
}
|
|
39
|
-
return { workspacePath, workspaceRealPath: await realpath(workspacePath) };
|
|
40
|
-
}
|
|
41
|
-
export async function loadGlobalBinding(globalRoot) {
|
|
42
|
-
const paths = globalPlatformPaths(globalRoot);
|
|
43
|
-
try {
|
|
44
|
-
const raw = await readFile(paths.bindingJson, "utf8");
|
|
45
|
-
const parsed = JSON.parse(raw);
|
|
46
|
-
if (!isGlobalWorkspaceBinding(parsed)) {
|
|
47
|
-
throw new PlatformError("GLOBAL_BINDING_INVALID", `global binding is invalid: ${paths.bindingJson}`);
|
|
48
|
-
}
|
|
49
|
-
return parsed;
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
if (isMissingFile(error))
|
|
53
|
-
return undefined;
|
|
54
|
-
if (error instanceof PlatformError)
|
|
55
|
-
throw error;
|
|
56
|
-
throw new PlatformError("GLOBAL_BINDING_INVALID", `cannot read global binding ${paths.bindingJson}: ${errorMessage(error)}`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
export async function observeBoundWorkspace(globalRoot) {
|
|
60
|
-
const binding = await loadGlobalBinding(globalRoot);
|
|
61
|
-
if (binding === undefined)
|
|
62
|
-
return undefined;
|
|
63
|
-
return {
|
|
64
|
-
binding,
|
|
65
|
-
workspaceExists: await directoryExists(binding.workspaceRealPath),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
export async function requireBoundWorkspace(globalRoot) {
|
|
69
|
-
const observation = await observeBoundWorkspace(globalRoot);
|
|
70
|
-
if (observation === undefined) {
|
|
71
|
-
throw new PlatformError("WORKSPACE_NOT_BOUND", "no ProFlow Workspace is currently installed/bound");
|
|
72
|
-
}
|
|
73
|
-
if (!observation.workspaceExists) {
|
|
74
|
-
throw new PlatformError("BOUND_WORKSPACE_MISSING", `bound Workspace no longer exists: ${observation.binding.workspaceRealPath}`);
|
|
75
|
-
}
|
|
76
|
-
return observation.binding;
|
|
77
|
-
}
|
|
78
|
-
export async function claimWorkspaceBinding(options) {
|
|
79
|
-
const requested = await canonicalizeWorkspace(options.workspace);
|
|
80
|
-
const paths = globalPlatformPaths(options.globalRoot);
|
|
81
|
-
const lock = await acquireGlobalBindingLock(paths);
|
|
82
|
-
try {
|
|
83
|
-
const current = await loadGlobalBinding(options.globalRoot);
|
|
84
|
-
if (current !== undefined) {
|
|
85
|
-
if (current.workspaceRealPath !== requested.workspaceRealPath) {
|
|
86
|
-
throw new PlatformError("WORKSPACE_ALREADY_BOUND", `ProFlow is already bound to ${current.workspaceRealPath}; uninstall it before installing ${requested.workspaceRealPath}`);
|
|
87
|
-
}
|
|
88
|
-
return { binding: current, alreadyBound: true };
|
|
89
|
-
}
|
|
90
|
-
const existingMarker = await loadWorkspaceInstanceMarker(requested.workspaceRealPath);
|
|
91
|
-
const now = new Date().toISOString();
|
|
92
|
-
const binding = {
|
|
93
|
-
contract: GLOBAL_BINDING_CONTRACT,
|
|
94
|
-
state: "INSTALLING",
|
|
95
|
-
workspacePath: requested.workspacePath,
|
|
96
|
-
workspaceRealPath: requested.workspaceRealPath,
|
|
97
|
-
workspaceInstanceId: existingMarker?.workspaceInstanceId ?? randomUUID(),
|
|
98
|
-
createdAt: existingMarker?.createdAt ?? now,
|
|
99
|
-
updatedAt: now,
|
|
100
|
-
};
|
|
101
|
-
await saveGlobalBinding(paths, binding);
|
|
102
|
-
if (existingMarker === undefined) {
|
|
103
|
-
try {
|
|
104
|
-
await writeWorkspaceInstanceMarker(binding);
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
const broken = withBindingState(binding, "BROKEN", {
|
|
108
|
-
code: "WORKSPACE_INSTANCE_WRITE_FAILED",
|
|
109
|
-
message: errorMessage(error),
|
|
110
|
-
});
|
|
111
|
-
await saveGlobalBinding(paths, broken);
|
|
112
|
-
throw new PlatformError("GLOBAL_BINDING_INVALID", `failed to materialize Workspace instance identity: ${errorMessage(error)}`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return { binding, alreadyBound: false };
|
|
116
|
-
}
|
|
117
|
-
finally {
|
|
118
|
-
await lock.release();
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
export async function updateGlobalBindingState(options) {
|
|
122
|
-
const paths = globalPlatformPaths(options.globalRoot);
|
|
123
|
-
const lock = await acquireGlobalBindingLock(paths);
|
|
124
|
-
try {
|
|
125
|
-
const current = await loadGlobalBinding(options.globalRoot);
|
|
126
|
-
if (current === undefined) {
|
|
127
|
-
throw new PlatformError("WORKSPACE_NOT_BOUND", "cannot update global binding because no Workspace is bound");
|
|
128
|
-
}
|
|
129
|
-
if (current.workspaceInstanceId !== options.workspaceInstanceId) {
|
|
130
|
-
throw new PlatformError("GLOBAL_BINDING_INVALID", "global binding instance identity changed during the operation");
|
|
131
|
-
}
|
|
132
|
-
const next = withBindingState(current, options.state, options.failure);
|
|
133
|
-
await saveGlobalBinding(paths, next);
|
|
134
|
-
return next;
|
|
135
|
-
}
|
|
136
|
-
finally {
|
|
137
|
-
await lock.release();
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
export async function clearGlobalBinding(options) {
|
|
141
|
-
const paths = globalPlatformPaths(options.globalRoot);
|
|
142
|
-
const lock = await acquireGlobalBindingLock(paths);
|
|
143
|
-
try {
|
|
144
|
-
const current = await loadGlobalBinding(options.globalRoot);
|
|
145
|
-
if (current === undefined)
|
|
146
|
-
return;
|
|
147
|
-
if (current.workspaceInstanceId !== options.workspaceInstanceId) {
|
|
148
|
-
throw new PlatformError("GLOBAL_BINDING_INVALID", "refusing to clear a different Workspace binding");
|
|
149
|
-
}
|
|
150
|
-
await rm(paths.bindingJson, { force: true });
|
|
151
|
-
if (options.removeWorkspaceMarker === true) {
|
|
152
|
-
await rm(workspaceInstanceMarkerPath(current.workspaceRealPath), {
|
|
153
|
-
force: true,
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
finally {
|
|
158
|
-
await lock.release();
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
export async function forgetMissingWorkspaceBinding(options) {
|
|
162
|
-
const paths = globalPlatformPaths(options.globalRoot);
|
|
163
|
-
const lock = await acquireGlobalBindingLock(paths);
|
|
164
|
-
try {
|
|
165
|
-
const current = await loadGlobalBinding(options.globalRoot);
|
|
166
|
-
if (current === undefined) {
|
|
167
|
-
throw new PlatformError("WORKSPACE_NOT_BOUND", "no ProFlow Workspace binding exists to forget");
|
|
168
|
-
}
|
|
169
|
-
if (await directoryExists(current.workspaceRealPath)) {
|
|
170
|
-
throw new PlatformError("INVALID_REQUEST", "bound Workspace still exists; use normal platform uninstall instead of forget");
|
|
171
|
-
}
|
|
172
|
-
await rm(paths.bindingJson, { force: true });
|
|
173
|
-
return current;
|
|
174
|
-
}
|
|
175
|
-
finally {
|
|
176
|
-
await lock.release();
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
export async function acquireGlobalBindingLock(paths) {
|
|
180
|
-
return acquireExclusiveGlobalLock(paths.root, paths.bindingLock, "GLOBAL_BINDING_LOCKED", "another process is mutating the global Workspace binding");
|
|
181
|
-
}
|
|
182
|
-
export async function acquireGlobalOperationLock(globalRoot) {
|
|
183
|
-
const paths = globalPlatformPaths(globalRoot);
|
|
184
|
-
return acquireExclusiveGlobalLock(paths.root, paths.operationLock, "GLOBAL_OPERATION_LOCKED", "another Platform install/uninstall operation is already running");
|
|
185
|
-
}
|
|
186
|
-
async function acquireExclusiveGlobalLock(root, file, code, message) {
|
|
187
|
-
await mkdir(root, { recursive: true });
|
|
188
|
-
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
189
|
-
try {
|
|
190
|
-
const handle = await open(file, "wx", 0o600);
|
|
191
|
-
try {
|
|
192
|
-
await handle.writeFile(`${JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() })}\n`, "utf8");
|
|
193
|
-
}
|
|
194
|
-
finally {
|
|
195
|
-
await handle.close();
|
|
196
|
-
}
|
|
197
|
-
return {
|
|
198
|
-
async release() {
|
|
199
|
-
try {
|
|
200
|
-
await unlink(file);
|
|
201
|
-
}
|
|
202
|
-
catch (error) {
|
|
203
|
-
if (!isMissingFile(error))
|
|
204
|
-
throw error;
|
|
205
|
-
}
|
|
206
|
-
},
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
catch (error) {
|
|
210
|
-
if (!isAlreadyExists(error))
|
|
211
|
-
throw error;
|
|
212
|
-
const record = await readLockRecord(file);
|
|
213
|
-
if (record !== undefined && !pidIsAlive(record.pid)) {
|
|
214
|
-
await rm(file, { force: true });
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
throw new PlatformError(code, `${message}: ${file}`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
throw new PlatformError(code, `${message}: ${file}`);
|
|
221
|
-
}
|
|
222
|
-
function workspaceInstanceMarkerPath(workspaceRoot) {
|
|
223
|
-
return join(workspaceRoot, ".proflow", "deployment", "instance.json");
|
|
224
|
-
}
|
|
225
|
-
async function loadWorkspaceInstanceMarker(workspaceRoot) {
|
|
226
|
-
const path = workspaceInstanceMarkerPath(workspaceRoot);
|
|
227
|
-
try {
|
|
228
|
-
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
229
|
-
if (!isWorkspaceInstanceMarker(parsed)) {
|
|
230
|
-
throw new PlatformError("WORKSPACE_INSTANCE_INVALID", `Workspace instance marker is invalid: ${path}`);
|
|
231
|
-
}
|
|
232
|
-
if (parsed.workspaceRealPath !== workspaceRoot) {
|
|
233
|
-
throw new PlatformError("WORKSPACE_INSTANCE_INVALID", `Workspace instance marker belongs to ${parsed.workspaceRealPath}, not ${workspaceRoot}`);
|
|
234
|
-
}
|
|
235
|
-
return parsed;
|
|
236
|
-
}
|
|
237
|
-
catch (error) {
|
|
238
|
-
if (isMissingFile(error))
|
|
239
|
-
return undefined;
|
|
240
|
-
if (error instanceof PlatformError)
|
|
241
|
-
throw error;
|
|
242
|
-
throw new PlatformError("WORKSPACE_INSTANCE_INVALID", `cannot read Workspace instance marker ${path}: ${errorMessage(error)}`);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
function isWorkspaceInstanceMarker(value) {
|
|
246
|
-
if (!isRecord(value))
|
|
247
|
-
return false;
|
|
248
|
-
return (value.contract === WORKSPACE_INSTANCE_CONTRACT &&
|
|
249
|
-
typeof value.workspaceInstanceId === "string" &&
|
|
250
|
-
value.workspaceInstanceId.length > 0 &&
|
|
251
|
-
typeof value.workspaceRealPath === "string" &&
|
|
252
|
-
typeof value.createdAt === "string");
|
|
253
|
-
}
|
|
254
|
-
async function writeWorkspaceInstanceMarker(binding) {
|
|
255
|
-
const path = workspaceInstanceMarkerPath(binding.workspaceRealPath);
|
|
256
|
-
const marker = {
|
|
257
|
-
contract: WORKSPACE_INSTANCE_CONTRACT,
|
|
258
|
-
workspaceInstanceId: binding.workspaceInstanceId,
|
|
259
|
-
workspaceRealPath: binding.workspaceRealPath,
|
|
260
|
-
createdAt: binding.createdAt,
|
|
261
|
-
};
|
|
262
|
-
await mkdir(dirname(path), { recursive: true });
|
|
263
|
-
await atomicWrite(path, `${JSON.stringify(marker, null, 2)}\n`);
|
|
264
|
-
await chmod(path, 0o600);
|
|
265
|
-
}
|
|
266
|
-
async function saveGlobalBinding(paths, binding) {
|
|
267
|
-
await mkdir(paths.root, { recursive: true });
|
|
268
|
-
await atomicWrite(paths.bindingJson, `${JSON.stringify(binding, null, 2)}\n`);
|
|
269
|
-
await chmod(paths.bindingJson, 0o600);
|
|
270
|
-
}
|
|
271
|
-
function withBindingState(binding, state, failure) {
|
|
272
|
-
const { failure: _previousFailure, ...base } = binding;
|
|
273
|
-
return {
|
|
274
|
-
...base,
|
|
275
|
-
state,
|
|
276
|
-
updatedAt: new Date().toISOString(),
|
|
277
|
-
...(failure === undefined ? {} : { failure }),
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
function isGlobalWorkspaceBinding(value) {
|
|
281
|
-
if (!isRecord(value))
|
|
282
|
-
return false;
|
|
283
|
-
if (value.contract !== GLOBAL_BINDING_CONTRACT)
|
|
284
|
-
return false;
|
|
285
|
-
if (!isBindingState(value.state))
|
|
286
|
-
return false;
|
|
287
|
-
return (typeof value.workspacePath === "string" &&
|
|
288
|
-
typeof value.workspaceRealPath === "string" &&
|
|
289
|
-
typeof value.workspaceInstanceId === "string" &&
|
|
290
|
-
typeof value.createdAt === "string" &&
|
|
291
|
-
typeof value.updatedAt === "string" &&
|
|
292
|
-
(value.failure === undefined ||
|
|
293
|
-
(isRecord(value.failure) &&
|
|
294
|
-
typeof value.failure.code === "string" &&
|
|
295
|
-
typeof value.failure.message === "string")));
|
|
296
|
-
}
|
|
297
|
-
function isBindingState(value) {
|
|
298
|
-
return (value === "INSTALLING" ||
|
|
299
|
-
value === "INSTALLED" ||
|
|
300
|
-
value === "UNINSTALLING" ||
|
|
301
|
-
value === "BROKEN");
|
|
302
|
-
}
|
|
303
|
-
async function directoryExists(path) {
|
|
304
|
-
try {
|
|
305
|
-
return (await stat(path)).isDirectory();
|
|
306
|
-
}
|
|
307
|
-
catch {
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
async function readLockRecord(path) {
|
|
312
|
-
try {
|
|
313
|
-
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
314
|
-
if (isRecord(parsed) &&
|
|
315
|
-
typeof parsed.pid === "number" &&
|
|
316
|
-
Number.isInteger(parsed.pid) &&
|
|
317
|
-
parsed.pid > 0) {
|
|
318
|
-
return { pid: parsed.pid };
|
|
319
|
-
}
|
|
320
|
-
return undefined;
|
|
321
|
-
}
|
|
322
|
-
catch {
|
|
323
|
-
return undefined;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
function pidIsAlive(pid) {
|
|
327
|
-
try {
|
|
328
|
-
process.kill(pid, 0);
|
|
329
|
-
return true;
|
|
330
|
-
}
|
|
331
|
-
catch (error) {
|
|
332
|
-
return isPermissionError(error);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
function isRecord(value) {
|
|
336
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
337
|
-
}
|
|
338
|
-
function isMissingFile(error) {
|
|
339
|
-
return isRecord(error) && error.code === "ENOENT";
|
|
340
|
-
}
|
|
341
|
-
function isAlreadyExists(error) {
|
|
342
|
-
return isRecord(error) && error.code === "EEXIST";
|
|
343
|
-
}
|
|
344
|
-
function isPermissionError(error) {
|
|
345
|
-
return isRecord(error) && error.code === "EPERM";
|
|
346
|
-
}
|
|
347
|
-
function errorMessage(error) {
|
|
348
|
-
return error instanceof Error ? error.message : String(error);
|
|
349
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { DeploymentCheck, DeploymentError, ModuleOperationResult } from "@tomflow/proflow-module-contract";
|
|
2
|
-
import type { ResolvedModule } from "../contracts.ts";
|
|
3
|
-
import type { ModuleCatalog } from "../modules.ts";
|
|
4
|
-
export type DoctorNextAction = {
|
|
5
|
-
kind: "none";
|
|
6
|
-
} | {
|
|
7
|
-
kind: "human-action";
|
|
8
|
-
action: string;
|
|
9
|
-
description: string;
|
|
10
|
-
} | {
|
|
11
|
-
kind: "repair-plan";
|
|
12
|
-
summary: string;
|
|
13
|
-
};
|
|
14
|
-
export interface DoctorReport {
|
|
15
|
-
moduleRef: string;
|
|
16
|
-
moduleVersion: string;
|
|
17
|
-
status: ModuleOperationResult["status"];
|
|
18
|
-
checks: DeploymentCheck[];
|
|
19
|
-
errors: DeploymentError[];
|
|
20
|
-
evidenceRefs: string[];
|
|
21
|
-
nextAction: DoctorNextAction;
|
|
22
|
-
observedEffects: string[];
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Maps a doctor result to the recommended next step. Doctor only recommends:
|
|
26
|
-
* it never performs a repair. A failing or blocked module yields a `repair-plan`
|
|
27
|
-
* recommendation (to be followed by `plan --intent repair` → confirm → apply);
|
|
28
|
-
* an ACTION_REQUIRED result yields the recoverable human action as-is.
|
|
29
|
-
*/
|
|
30
|
-
export declare function nextActionOf(result: ModuleOperationResult): DoctorNextAction;
|
|
31
|
-
/**
|
|
32
|
-
* Diagnoses a single module by dispatching its public `doctor` primitive. The
|
|
33
|
-
* report carries live checks, typed errors, evidence refs, and a recommended
|
|
34
|
-
* next action — but performs no side effects and never auto-repairs.
|
|
35
|
-
*/
|
|
36
|
-
export declare function doctorModule(catalog: ModuleCatalog, module: ResolvedModule): Promise<DoctorReport>;
|
|
37
|
-
/**
|
|
38
|
-
* Diagnoses every module that declares the `doctor` primitive; modules without
|
|
39
|
-
* it are skipped.
|
|
40
|
-
*/
|
|
41
|
-
export declare function doctorModules(catalog: ModuleCatalog, modules: readonly ResolvedModule[]): Promise<DoctorReport[]>;
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { dispatchLifecycle } from "../lifecycle/index.js";
|
|
2
|
-
/**
|
|
3
|
-
* Maps a doctor result to the recommended next step. Doctor only recommends:
|
|
4
|
-
* it never performs a repair. A failing or blocked module yields a `repair-plan`
|
|
5
|
-
* recommendation (to be followed by `plan --intent repair` → confirm → apply);
|
|
6
|
-
* an ACTION_REQUIRED result yields the recoverable human action as-is.
|
|
7
|
-
*/
|
|
8
|
-
export function nextActionOf(result) {
|
|
9
|
-
switch (result.status) {
|
|
10
|
-
case "SUCCEEDED":
|
|
11
|
-
return { kind: "none" };
|
|
12
|
-
case "ACTION_REQUIRED": {
|
|
13
|
-
const action = result.actionRequired;
|
|
14
|
-
if (action !== undefined) {
|
|
15
|
-
return {
|
|
16
|
-
kind: "human-action",
|
|
17
|
-
action: action.action,
|
|
18
|
-
description: action.description,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
return {
|
|
22
|
-
kind: "repair-plan",
|
|
23
|
-
summary: "action required but unspecified",
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
case "BLOCKED":
|
|
27
|
-
case "FAILED": {
|
|
28
|
-
const summary = result.error !== undefined
|
|
29
|
-
? `${result.error.code}: ${result.error.message}`
|
|
30
|
-
: `module ${result.moduleRef} is ${result.status.toLowerCase()}`;
|
|
31
|
-
return { kind: "repair-plan", summary };
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Diagnoses a single module by dispatching its public `doctor` primitive. The
|
|
37
|
-
* report carries live checks, typed errors, evidence refs, and a recommended
|
|
38
|
-
* next action — but performs no side effects and never auto-repairs.
|
|
39
|
-
*/
|
|
40
|
-
export async function doctorModule(catalog, module) {
|
|
41
|
-
const primitives = ["status", "verify", "doctor"].filter((primitive) => module.lifecycle.includes(primitive));
|
|
42
|
-
const dispatched = [];
|
|
43
|
-
for (const primitive of primitives) {
|
|
44
|
-
dispatched.push(await dispatchLifecycle(catalog, module, primitive));
|
|
45
|
-
}
|
|
46
|
-
const severity = {
|
|
47
|
-
SUCCEEDED: 0,
|
|
48
|
-
ACTION_REQUIRED: 1,
|
|
49
|
-
BLOCKED: 2,
|
|
50
|
-
FAILED: 3,
|
|
51
|
-
};
|
|
52
|
-
const effective = dispatched
|
|
53
|
-
.map((item) => item.result)
|
|
54
|
-
.reduce((worst, current) => severity[current.status] > severity[worst.status] ? current : worst);
|
|
55
|
-
const checks = dispatched.flatMap((item) => item.result.checks ?? []);
|
|
56
|
-
const errors = dispatched.flatMap((item) => item.result.error === undefined ? [] : [item.result.error]);
|
|
57
|
-
return {
|
|
58
|
-
moduleRef: module.moduleRef,
|
|
59
|
-
moduleVersion: module.moduleVersion,
|
|
60
|
-
status: effective.status,
|
|
61
|
-
checks,
|
|
62
|
-
errors,
|
|
63
|
-
evidenceRefs: checks.map((check) => `check:${check.id}:${check.status}`),
|
|
64
|
-
nextAction: nextActionOf(effective),
|
|
65
|
-
observedEffects: dispatched.flatMap((item) => item.observedEffects),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Diagnoses every module that declares the `doctor` primitive; modules without
|
|
70
|
-
* it are skipped.
|
|
71
|
-
*/
|
|
72
|
-
export async function doctorModules(catalog, modules) {
|
|
73
|
-
const reports = [];
|
|
74
|
-
for (const module of modules) {
|
|
75
|
-
if (!module.lifecycle.includes("doctor"))
|
|
76
|
-
continue;
|
|
77
|
-
reports.push(await doctorModule(catalog, module));
|
|
78
|
-
}
|
|
79
|
-
return reports;
|
|
80
|
-
}
|
package/dist/src/doctor/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { doctorModule, doctorModules, nextActionOf } from "./doctor.js";
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { ResolvedModule } from "../contracts.ts";
|
|
2
|
-
import { type RegistryModuleCandidate } from "../registry/index.ts";
|
|
3
|
-
/**
|
|
4
|
-
* Registry bootstrap targets intentionally carry only facts available before
|
|
5
|
-
* installation. They are package-plan placeholders, never substitutes for the
|
|
6
|
-
* package-owned Module Descriptor that is loaded after installation.
|
|
7
|
-
*/
|
|
8
|
-
export declare function registryCandidateToBootstrapModule(candidate: RegistryModuleCandidate): ResolvedModule;
|
|
9
|
-
export declare function selectBootstrapModules(candidates: readonly RegistryModuleCandidate[], explicitPackageName?: string): ResolvedModule[];
|
|
10
|
-
export declare function selectUpgradeBootstrapModules(currentModules: readonly ResolvedModule[], candidates: readonly RegistryModuleCandidate[]): ResolvedModule[];
|