@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.
Files changed (142) hide show
  1. package/README.md +48 -43
  2. package/dist/deployment/adapter.d.ts +5 -5
  3. package/dist/deployment/adapter.js +2 -1
  4. package/dist/deployment/descriptor.d.ts +2 -3
  5. package/dist/deployment/descriptor.js +2 -3
  6. package/dist/src/binding/production-bindings.d.ts +17 -15
  7. package/dist/src/binding/production-bindings.js +18 -59
  8. package/dist/src/cli.d.ts +7 -9
  9. package/dist/src/cli.js +327 -1316
  10. package/dist/src/contracts.d.ts +4 -97
  11. package/dist/src/discovery/discover.js +0 -1
  12. package/dist/src/docs/aggregate.d.ts +18 -0
  13. package/dist/src/docs/aggregate.js +44 -0
  14. package/dist/src/docs/docs.d.ts +0 -31
  15. package/dist/src/docs/docs.js +0 -61
  16. package/dist/src/docs/index.d.ts +2 -1
  17. package/dist/src/docs/index.js +1 -1
  18. package/dist/src/errors.d.ts +1 -1
  19. package/dist/src/errors.js +0 -18
  20. package/dist/src/graph/graph.d.ts +1 -4
  21. package/dist/src/graph/graph.js +1 -13
  22. package/dist/src/graph/index.d.ts +2 -2
  23. package/dist/src/graph/index.js +1 -1
  24. package/dist/src/index.d.ts +4 -24
  25. package/dist/src/index.js +2 -16
  26. package/dist/src/install/package-manager.d.ts +21 -0
  27. package/dist/src/install/package-manager.js +153 -0
  28. package/dist/src/lifecycle/dispatch.d.ts +0 -31
  29. package/dist/src/lifecycle/dispatch.js +0 -125
  30. package/dist/src/lifecycle/index.d.ts +4 -3
  31. package/dist/src/lifecycle/index.js +2 -2
  32. package/dist/src/lifecycle/thin.d.ts +11 -0
  33. package/dist/src/lifecycle/thin.js +68 -0
  34. package/dist/src/paths.d.ts +0 -14
  35. package/dist/src/paths.js +0 -39
  36. package/dist/src/persistence/config.d.ts +0 -6
  37. package/dist/src/persistence/config.js +0 -14
  38. package/dist/src/persistence/guards.d.ts +0 -6
  39. package/dist/src/persistence/guards.js +0 -114
  40. package/dist/src/persistence/index.d.ts +4 -6
  41. package/dist/src/persistence/index.js +2 -5
  42. package/dist/src/persistence/workspace-metadata.d.ts +7 -0
  43. package/dist/src/persistence/workspace-metadata.js +39 -0
  44. package/dist/src/registry/index.d.ts +1 -1
  45. package/dist/src/registry/index.js +1 -1
  46. package/dist/src/registry/npm-registry.d.ts +0 -14
  47. package/dist/src/registry/npm-registry.js +1 -132
  48. package/package.json +2 -6
  49. package/proflow.module.json +2 -3
  50. package/CHANGELOG.md +0 -85
  51. package/dist/src/apply/apply.d.ts +0 -26
  52. package/dist/src/apply/apply.js +0 -314
  53. package/dist/src/apply/cleanup.d.ts +0 -12
  54. package/dist/src/apply/cleanup.js +0 -37
  55. package/dist/src/apply/current.d.ts +0 -5
  56. package/dist/src/apply/current.js +0 -95
  57. package/dist/src/apply/driver.d.ts +0 -28
  58. package/dist/src/apply/driver.js +0 -239
  59. package/dist/src/apply/execute.d.ts +0 -36
  60. package/dist/src/apply/execute.js +0 -152
  61. package/dist/src/apply/index.d.ts +0 -8
  62. package/dist/src/apply/index.js +0 -4
  63. package/dist/src/apply/reality.d.ts +0 -20
  64. package/dist/src/apply/reality.js +0 -105
  65. package/dist/src/binding/global-binding.d.ts +0 -69
  66. package/dist/src/binding/global-binding.js +0 -349
  67. package/dist/src/doctor/doctor.d.ts +0 -41
  68. package/dist/src/doctor/doctor.js +0 -80
  69. package/dist/src/doctor/index.d.ts +0 -2
  70. package/dist/src/doctor/index.js +0 -1
  71. package/dist/src/install/bootstrap.d.ts +0 -10
  72. package/dist/src/install/bootstrap.js +0 -79
  73. package/dist/src/install/environment.d.ts +0 -30
  74. package/dist/src/install/environment.js +0 -230
  75. package/dist/src/install/index.d.ts +0 -6
  76. package/dist/src/install/index.js +0 -3
  77. package/dist/src/install/install.d.ts +0 -19
  78. package/dist/src/install/install.js +0 -172
  79. package/dist/src/lifecycle/service-process.d.ts +0 -11
  80. package/dist/src/lifecycle/service-process.js +0 -356
  81. package/dist/src/manifest/index.d.ts +0 -2
  82. package/dist/src/manifest/index.js +0 -1
  83. package/dist/src/manifest/manifest.d.ts +0 -61
  84. package/dist/src/manifest/manifest.js +0 -167
  85. package/dist/src/observer/deployment-summary.d.ts +0 -31
  86. package/dist/src/observer/deployment-summary.js +0 -34
  87. package/dist/src/persistence/atomic.d.ts +0 -2
  88. package/dist/src/persistence/atomic.js +0 -16
  89. package/dist/src/persistence/effective-config.d.ts +0 -3
  90. package/dist/src/persistence/effective-config.js +0 -20
  91. package/dist/src/persistence/generated.d.ts +0 -2
  92. package/dist/src/persistence/generated.js +0 -4
  93. package/dist/src/persistence/plans.d.ts +0 -5
  94. package/dist/src/persistence/plans.js +0 -34
  95. package/dist/src/persistence/state.d.ts +0 -7
  96. package/dist/src/persistence/state.js +0 -34
  97. package/dist/src/persistence/verification.d.ts +0 -5
  98. package/dist/src/persistence/verification.js +0 -47
  99. package/dist/src/planner/assemble.d.ts +0 -16
  100. package/dist/src/planner/assemble.js +0 -107
  101. package/dist/src/planner/check.d.ts +0 -20
  102. package/dist/src/planner/check.js +0 -107
  103. package/dist/src/planner/fingerprint.d.ts +0 -12
  104. package/dist/src/planner/fingerprint.js +0 -164
  105. package/dist/src/planner/index.d.ts +0 -13
  106. package/dist/src/planner/index.js +0 -7
  107. package/dist/src/planner/plan.d.ts +0 -15
  108. package/dist/src/planner/plan.js +0 -144
  109. package/dist/src/planner/repair.d.ts +0 -28
  110. package/dist/src/planner/repair.js +0 -131
  111. package/dist/src/planner/stale.d.ts +0 -7
  112. package/dist/src/planner/stale.js +0 -23
  113. package/dist/src/planner/steps.d.ts +0 -37
  114. package/dist/src/planner/steps.js +0 -165
  115. package/dist/src/planner/target-catalog.d.ts +0 -13
  116. package/dist/src/planner/target-catalog.js +0 -48
  117. package/dist/src/planner/upgrade.d.ts +0 -18
  118. package/dist/src/planner/upgrade.js +0 -114
  119. package/dist/src/preflight/config.d.ts +0 -11
  120. package/dist/src/preflight/config.js +0 -88
  121. package/dist/src/preflight/findings.d.ts +0 -26
  122. package/dist/src/preflight/findings.js +0 -1
  123. package/dist/src/preflight/index.d.ts +0 -7
  124. package/dist/src/preflight/index.js +0 -3
  125. package/dist/src/preflight/preflight.d.ts +0 -10
  126. package/dist/src/preflight/preflight.js +0 -190
  127. package/dist/src/preflight/requirements.d.ts +0 -11
  128. package/dist/src/preflight/requirements.js +0 -231
  129. package/dist/src/ready/index.d.ts +0 -2
  130. package/dist/src/ready/index.js +0 -1
  131. package/dist/src/ready/ready.d.ts +0 -40
  132. package/dist/src/ready/ready.js +0 -201
  133. package/dist/src/security/index.d.ts +0 -3
  134. package/dist/src/security/index.js +0 -2
  135. package/dist/src/security/lock.d.ts +0 -9
  136. package/dist/src/security/lock.js +0 -127
  137. package/dist/src/security/redact.d.ts +0 -6
  138. package/dist/src/security/redact.js +0 -48
  139. package/dist/src/verification/index.d.ts +0 -2
  140. package/dist/src/verification/index.js +0 -1
  141. package/dist/src/verification/verify.d.ts +0 -33
  142. package/dist/src/verification/verify.js +0 -114
@@ -1,356 +0,0 @@
1
- import { execFile, spawn } from "node:child_process";
2
- import { mkdir, open, readFile, rm } from "node:fs/promises";
3
- import { createRequire } from "node:module";
4
- import { dirname, join, resolve } from "node:path";
5
- import { pathToFileURL } from "node:url";
6
- import { promisify } from "node:util";
7
- import { serviceProcessBindingSchema, } from "@tomflow/proflow-module-contract";
8
- import { writeJsonAtomic } from "../persistence/atomic.js";
9
- const execFileAsync = promisify(execFile);
10
- function base(module) {
11
- return {
12
- contract: "deployment.result.v1",
13
- ok: true,
14
- status: "SUCCEEDED",
15
- moduleRef: module.moduleRef,
16
- moduleVersion: module.moduleVersion,
17
- };
18
- }
19
- function actionRequired(module, action, description, checks = []) {
20
- return {
21
- ...base(module),
22
- ok: false,
23
- status: "ACTION_REQUIRED",
24
- actionRequired: { action, description },
25
- checks,
26
- };
27
- }
28
- function failed(module, message) {
29
- return {
30
- ...base(module),
31
- ok: false,
32
- status: "FAILED",
33
- error: { code: "COMMAND_FAILED", message, retryable: true },
34
- };
35
- }
36
- function serviceDir(paths, moduleRef) {
37
- return join(paths.runtime, "services", moduleRef);
38
- }
39
- function recordPath(paths, moduleRef) {
40
- return join(serviceDir(paths, moduleRef), "process.json");
41
- }
42
- async function readRecord(paths, moduleRef) {
43
- try {
44
- const raw = JSON.parse(await readFile(recordPath(paths, moduleRef), "utf8"));
45
- if (typeof raw !== "object" || raw === null)
46
- return undefined;
47
- const value = raw;
48
- if (value.contract !== "deployment.service-process-state.v1" ||
49
- typeof value.pid !== "number" ||
50
- typeof value.moduleRef !== "string" ||
51
- typeof value.packageName !== "string" ||
52
- typeof value.moduleVersion !== "string" ||
53
- typeof value.binPath !== "string" ||
54
- typeof value.configPath !== "string" ||
55
- typeof value.stdoutPath !== "string" ||
56
- typeof value.stderrPath !== "string" ||
57
- typeof value.startedAt !== "string")
58
- return undefined;
59
- return value;
60
- }
61
- catch {
62
- return undefined;
63
- }
64
- }
65
- function recordIdentityMatchesModule(record, module) {
66
- return (record.moduleRef === module.moduleRef &&
67
- record.packageName === module.packageName);
68
- }
69
- function recordVersionMatchesModule(record, module) {
70
- return record.moduleVersion === module.moduleVersion;
71
- }
72
- function isAlive(pid) {
73
- try {
74
- process.kill(pid, 0);
75
- return true;
76
- }
77
- catch {
78
- return false;
79
- }
80
- }
81
- async function isOwnedProcess(record) {
82
- if (!isAlive(record.pid))
83
- return false;
84
- try {
85
- let commandLine;
86
- if (process.platform === "win32") {
87
- const result = await execFileAsync("powershell.exe", [
88
- "-NoProfile",
89
- "-Command",
90
- `(Get-CimInstance Win32_Process -Filter "ProcessId = ${record.pid}").CommandLine`,
91
- ], { encoding: "utf8" });
92
- commandLine = result.stdout;
93
- }
94
- else {
95
- const result = await execFileAsync("ps", ["-p", String(record.pid), "-o", "command="], { encoding: "utf8" });
96
- commandLine = result.stdout;
97
- }
98
- return (commandLine.includes(record.binPath) &&
99
- commandLine.includes(record.configPath));
100
- }
101
- catch {
102
- return false;
103
- }
104
- }
105
- async function removeRecord(paths, moduleRef) {
106
- await rm(recordPath(paths, moduleRef), { force: true });
107
- }
108
- async function resolvePackageRoot(workspaceRoot, module) {
109
- if (module.source.type === "workspace") {
110
- if (!module.source.path)
111
- throw new Error(`workspace source missing path for ${module.packageName}`);
112
- return module.source.path;
113
- }
114
- if (module.source.type !== "installed") {
115
- throw new Error(`registry bootstrap target ${module.packageName} has no local package root`);
116
- }
117
- const require = createRequire(pathToFileURL(join(workspaceRoot, "package.json")));
118
- const artifact = require.resolve(`${module.packageName}/deployment/descriptor`);
119
- let current = dirname(artifact);
120
- for (;;) {
121
- try {
122
- const parsed = JSON.parse(await readFile(join(current, "package.json"), "utf8"));
123
- if (parsed.name === module.packageName)
124
- return current;
125
- }
126
- catch {
127
- // keep walking upward
128
- }
129
- const parent = dirname(current);
130
- if (parent === current)
131
- throw new Error(`package root not found for ${module.packageName}`);
132
- current = parent;
133
- }
134
- }
135
- async function resolveOwnedBin(workspaceRoot, module, binding) {
136
- const packageRoot = await resolvePackageRoot(workspaceRoot, module);
137
- const manifest = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8"));
138
- const relative = typeof manifest.bin === "string"
139
- ? manifest.bin
140
- : manifest.bin?.[binding.bin];
141
- if (typeof relative !== "string")
142
- throw new Error(`package ${module.packageName} does not own bin ${binding.bin}`);
143
- const target = resolve(packageRoot, relative);
144
- const root = resolve(packageRoot);
145
- if (target !== root && !target.startsWith(`${root}/`))
146
- throw new Error(`service bin escapes package root: ${relative}`);
147
- return target;
148
- }
149
- export async function managedServiceStatus(paths, module) {
150
- const record = await readRecord(paths, module.moduleRef);
151
- if (!record || !(await isOwnedProcess(record))) {
152
- if (record)
153
- await removeRecord(paths, module.moduleRef);
154
- return {
155
- result: actionRequired(module, "start-service", "Service process is not running", [
156
- {
157
- id: "service-process-live",
158
- status: "FAIL",
159
- message: "No live managed service process",
160
- },
161
- ]),
162
- observedEffects: [],
163
- };
164
- }
165
- if (!recordIdentityMatchesModule(record, module)) {
166
- return {
167
- result: failed(module, "managed service state identity does not match the current module"),
168
- observedEffects: [],
169
- };
170
- }
171
- if (!recordVersionMatchesModule(record, module)) {
172
- return {
173
- result: actionRequired(module, "restart-service", `Managed service process is still running package version ${record.moduleVersion}; restart is required for ${module.moduleVersion}`, [
174
- {
175
- id: "service-process-version",
176
- status: "FAIL",
177
- message: `Running ${record.moduleVersion}, installed ${module.moduleVersion}`,
178
- },
179
- ]),
180
- observedEffects: [],
181
- };
182
- }
183
- return {
184
- result: {
185
- ...base(module),
186
- data: { state: "RUNNING", pid: record.pid, startedAt: record.startedAt },
187
- checks: [
188
- {
189
- id: "service-process-live",
190
- status: "PASS",
191
- message: `Managed service process ${record.pid} is alive`,
192
- },
193
- ],
194
- },
195
- observedEffects: [],
196
- };
197
- }
198
- async function waitForStartupStability(pid, milliseconds = 500) {
199
- const deadline = Date.now() + milliseconds;
200
- while (Date.now() < deadline) {
201
- if (!isAlive(pid))
202
- return false;
203
- await new Promise((resolveWait) => setTimeout(resolveWait, 50));
204
- }
205
- return isAlive(pid);
206
- }
207
- export async function startManagedService(paths, module, rawBinding) {
208
- const existing = await readRecord(paths, module.moduleRef);
209
- if (existing && (await isOwnedProcess(existing))) {
210
- if (!recordIdentityMatchesModule(existing, module)) {
211
- return {
212
- result: failed(module, "managed service state identity does not match the current module"),
213
- observedEffects: [],
214
- };
215
- }
216
- return managedServiceStatus(paths, module);
217
- }
218
- if (existing)
219
- await removeRecord(paths, module.moduleRef);
220
- const binding = serviceProcessBindingSchema.parse(rawBinding);
221
- let binPath;
222
- try {
223
- binPath = await resolveOwnedBin(paths.root, module, binding);
224
- }
225
- catch (error) {
226
- return {
227
- result: failed(module, error instanceof Error ? error.message : String(error)),
228
- observedEffects: [],
229
- };
230
- }
231
- const dir = serviceDir(paths, module.moduleRef);
232
- const configPath = join(dir, "config.json");
233
- const stdoutPath = join(paths.logsDeployment, `${module.moduleRef}.stdout.log`);
234
- const stderrPath = join(paths.logsDeployment, `${module.moduleRef}.stderr.log`);
235
- await writeJsonAtomic(configPath, binding.config, 0o600);
236
- await mkdir(paths.logsDeployment, { recursive: true });
237
- const stdout = await open(stdoutPath, "a");
238
- const stderr = await open(stderrPath, "a");
239
- try {
240
- const child = spawn(process.execPath, [binPath, binding.startCommand, configPath], {
241
- cwd: paths.root,
242
- detached: true,
243
- env: process.env,
244
- stdio: ["ignore", stdout.fd, stderr.fd],
245
- });
246
- await new Promise((resolveSpawn, rejectSpawn) => {
247
- child.once("spawn", resolveSpawn);
248
- child.once("error", rejectSpawn);
249
- });
250
- if (child.pid === undefined)
251
- return {
252
- result: failed(module, "service process spawned without pid"),
253
- observedEffects: [],
254
- };
255
- const pid = child.pid;
256
- if (!(await waitForStartupStability(pid))) {
257
- return {
258
- result: failed(module, `service process ${pid} exited during startup stabilization`),
259
- observedEffects: [],
260
- };
261
- }
262
- child.unref();
263
- const record = {
264
- contract: "deployment.service-process-state.v1",
265
- moduleRef: module.moduleRef,
266
- packageName: module.packageName,
267
- moduleVersion: module.moduleVersion,
268
- pid,
269
- startedAt: new Date().toISOString(),
270
- binPath,
271
- configPath,
272
- stdoutPath,
273
- stderrPath,
274
- };
275
- await writeJsonAtomic(recordPath(paths, module.moduleRef), record, 0o600);
276
- const observed = await managedServiceStatus(paths, module);
277
- if (observed.result.status !== "SUCCEEDED") {
278
- await removeRecord(paths, module.moduleRef);
279
- try {
280
- process.kill(pid, "SIGTERM");
281
- }
282
- catch {
283
- // process already exited between observations
284
- }
285
- return {
286
- result: failed(module, `service process ${pid} could not be observed as the owned RUNNING process after spawn`),
287
- observedEffects: [],
288
- };
289
- }
290
- return {
291
- ...observed,
292
- observedEffects: ["Manage the declared service process"],
293
- };
294
- }
295
- catch (error) {
296
- return {
297
- result: failed(module, error instanceof Error ? error.message : String(error)),
298
- observedEffects: [],
299
- };
300
- }
301
- finally {
302
- await Promise.all([stdout.close(), stderr.close()]);
303
- }
304
- }
305
- async function waitForExit(pid, timeoutMs = 2_000) {
306
- const deadline = Date.now() + timeoutMs;
307
- while (Date.now() < deadline) {
308
- if (!isAlive(pid))
309
- return true;
310
- await new Promise((resolveWait) => setTimeout(resolveWait, 50));
311
- }
312
- return !isAlive(pid);
313
- }
314
- export async function stopManagedService(paths, module) {
315
- const record = await readRecord(paths, module.moduleRef);
316
- if (!record || !(await isOwnedProcess(record))) {
317
- if (record)
318
- await removeRecord(paths, module.moduleRef);
319
- return {
320
- result: { ...base(module), data: { state: "STOPPED" } },
321
- observedEffects: [],
322
- };
323
- }
324
- if (!recordIdentityMatchesModule(record, module)) {
325
- return {
326
- result: failed(module, "managed service state identity does not match the current module"),
327
- observedEffects: [],
328
- };
329
- }
330
- try {
331
- process.kill(record.pid, "SIGTERM");
332
- if (!(await waitForExit(record.pid))) {
333
- return {
334
- result: failed(module, `service process ${record.pid} did not stop after SIGTERM`),
335
- observedEffects: [],
336
- };
337
- }
338
- await removeRecord(paths, module.moduleRef);
339
- return {
340
- result: { ...base(module), data: { state: "STOPPED", pid: record.pid } },
341
- observedEffects: ["Manage the declared service process"],
342
- };
343
- }
344
- catch (error) {
345
- return {
346
- result: failed(module, error instanceof Error ? error.message : String(error)),
347
- observedEffects: [],
348
- };
349
- }
350
- }
351
- export async function restartManagedService(paths, module, binding) {
352
- const stopped = await stopManagedService(paths, module);
353
- if (stopped.result.status === "FAILED")
354
- return stopped;
355
- return startManagedService(paths, module, binding);
356
- }
@@ -1,2 +0,0 @@
1
- export type { ManifestConfig, ManifestDeps, ManifestModule, ManifestPendingAction, ManifestVerification, PlatformManifest, } from "./manifest.ts";
2
- export { buildManifest, MANIFEST_CONTRACT } from "./manifest.ts";
@@ -1 +0,0 @@
1
- export { buildManifest, MANIFEST_CONTRACT } from "./manifest.js";
@@ -1,61 +0,0 @@
1
- import type { ModuleOperationResult } from "@tomflow/proflow-module-contract";
2
- import type { PlatformState, ResolvedModule, VerificationRecord } from "../contracts.ts";
3
- import type { ModuleCatalog } from "../modules.ts";
4
- import type { WorkspacePaths } from "../paths.ts";
5
- export declare const MANIFEST_CONTRACT = "proflow.manifest.v1";
6
- export interface ManifestModule {
7
- moduleRef: string;
8
- packageName: string;
9
- moduleVersion: string;
10
- kind: ResolvedModule["kind"];
11
- source: ResolvedModule["source"];
12
- provides: ResolvedModule["provides"];
13
- requires: ResolvedModule["requires"];
14
- runtimeObserved: boolean;
15
- runtimeStatus?: ModuleOperationResult["status"];
16
- runtimeResourceVersion?: string;
17
- resourceIdentity?: string;
18
- }
19
- export interface ManifestVerification {
20
- moduleRef: string;
21
- historyCount: number;
22
- latest?: VerificationRecord;
23
- lastPassAt?: string;
24
- lastFailAt?: string;
25
- }
26
- export interface ManifestConfig {
27
- moduleRef: string;
28
- values: Record<string, string>;
29
- secretRefs: string[];
30
- missing: string[];
31
- }
32
- export interface ManifestPendingAction {
33
- moduleRef?: string;
34
- action: string;
35
- description?: string;
36
- }
37
- export interface PlatformManifest {
38
- contract: typeof MANIFEST_CONTRACT;
39
- observedAt: string;
40
- status: PlatformState;
41
- modules: ManifestModule[];
42
- verification: ManifestVerification[];
43
- config: ManifestConfig[];
44
- pendingActions: ManifestPendingAction[];
45
- }
46
- export interface ManifestDeps {
47
- catalog: ModuleCatalog;
48
- modules: readonly ResolvedModule[];
49
- paths: WorkspacePaths;
50
- config?: Record<string, Record<string, string>>;
51
- now?: Date;
52
- }
53
- /**
54
- * Dynamically composes the platform manifest from current reality: the resolved
55
- * module set, module/adapter versions, external resource identity/version, live
56
- * runtime status (via `statusModules`, never persisted state), provides/requires,
57
- * secret-redacted config readiness, verification history summary, pending
58
- * ACTION_REQUIRED, and the aggregated platform status. Every composition carries
59
- * an explicit `observedAt` freshness timestamp.
60
- */
61
- export declare function buildManifest(deps: ManifestDeps): Promise<PlatformManifest>;
@@ -1,167 +0,0 @@
1
- import { statusModules } from "../lifecycle/index.js";
2
- import { loadConfig, loadDeploymentState, loadVerificationHistory, } from "../persistence/index.js";
3
- import { clearCompletedPendingActions } from "../persistence/state.js";
4
- import { resolveModuleConfig } from "../preflight/index.js";
5
- import { assessPlatformReady, } from "../ready/index.js";
6
- import { redactSecretValues } from "../security/index.js";
7
- import { configFingerprint } from "../verification/index.js";
8
- export const MANIFEST_CONTRACT = "proflow.manifest.v1";
9
- function compareRef(a, b) {
10
- return a < b ? -1 : a > b ? 1 : 0;
11
- }
12
- function lastVerifiedAt(records, result) {
13
- for (let i = records.length - 1; i >= 0; i -= 1) {
14
- const record = records[i];
15
- if (record !== undefined && record.result === result) {
16
- return record.verifiedAt;
17
- }
18
- }
19
- return undefined;
20
- }
21
- /**
22
- * Dynamically composes the platform manifest from current reality: the resolved
23
- * module set, module/adapter versions, external resource identity/version, live
24
- * runtime status (via `statusModules`, never persisted state), provides/requires,
25
- * secret-redacted config readiness, verification history summary, pending
26
- * ACTION_REQUIRED, and the aggregated platform status. Every composition carries
27
- * an explicit `observedAt` freshness timestamp.
28
- */
29
- export async function buildManifest(deps) {
30
- const observedAt = (deps.now ?? new Date()).toISOString();
31
- const modules = [...deps.modules].sort((a, b) => compareRef(a.moduleRef, b.moduleRef));
32
- const statusResults = await statusModules(deps.catalog, modules);
33
- const statusByRef = new Map(statusResults.map((run) => [run.moduleRef, run]));
34
- const manifestModules = [];
35
- const verification = [];
36
- const configEntries = [];
37
- const allRecords = [];
38
- const pendingActions = [];
39
- const blockingActions = [];
40
- const resources = [];
41
- const currentVerifiedModuleRefs = new Set();
42
- const materializedConfig = {};
43
- for (const module of modules) {
44
- // materialized config current reality (never caller-supplied intent)
45
- const persisted = await loadConfig(deps.paths, module.moduleRef);
46
- if (persisted !== undefined) {
47
- materializedConfig[module.moduleRef] = {
48
- ...persisted.publicValues,
49
- ...persisted.secretValues,
50
- };
51
- }
52
- // live runtime status — current reality, not persisted state
53
- const run = statusByRef.get(module.moduleRef);
54
- let runtimeObserved = false;
55
- let runtimeStatus;
56
- let runtimeResourceVersion;
57
- if (run !== undefined &&
58
- run.status === "EXECUTED" &&
59
- run.result !== undefined) {
60
- runtimeObserved = true;
61
- runtimeStatus = run.result.status;
62
- if (run.result.resourceVersion !== undefined) {
63
- runtimeResourceVersion = run.result.resourceVersion;
64
- }
65
- if (run.result.status === "ACTION_REQUIRED" &&
66
- run.result.actionRequired !== undefined) {
67
- const action = {
68
- moduleRef: module.moduleRef,
69
- action: run.result.actionRequired.action,
70
- description: run.result.actionRequired.description,
71
- };
72
- pendingActions.push(action);
73
- blockingActions.push(action);
74
- }
75
- }
76
- // external resource identity/version
77
- let resourceIdentity;
78
- if (module.kind === "external-resource") {
79
- const secretRefs = module.configSlots
80
- .filter((slot) => slot.type === "secretRef")
81
- .map((slot) => slot.key);
82
- resourceIdentity = configFingerprint(persisted?.publicValues ?? {}, secretRefs);
83
- resources.push({
84
- moduleRef: module.moduleRef,
85
- ...(resourceIdentity !== undefined ? { resourceIdentity } : {}),
86
- ...(runtimeResourceVersion !== undefined
87
- ? { resourceVersion: runtimeResourceVersion }
88
- : {}),
89
- });
90
- }
91
- manifestModules.push({
92
- moduleRef: module.moduleRef,
93
- packageName: module.packageName,
94
- moduleVersion: module.moduleVersion,
95
- kind: module.kind,
96
- source: module.source,
97
- provides: module.provides,
98
- requires: module.requires,
99
- runtimeObserved,
100
- ...(runtimeStatus !== undefined ? { runtimeStatus } : {}),
101
- ...(runtimeResourceVersion !== undefined
102
- ? { runtimeResourceVersion }
103
- : {}),
104
- ...(resourceIdentity !== undefined ? { resourceIdentity } : {}),
105
- });
106
- // verification history summary
107
- const history = await loadVerificationHistory(deps.paths, module.moduleRef);
108
- allRecords.push(...history);
109
- const latest = history[history.length - 1];
110
- if (latest?.result === "PASS" &&
111
- latest.moduleVersion === module.moduleVersion) {
112
- currentVerifiedModuleRefs.add(module.moduleRef);
113
- }
114
- const lastPassAt = lastVerifiedAt(history, "PASS");
115
- const lastFailAt = lastVerifiedAt(history, "FAIL");
116
- verification.push({
117
- moduleRef: module.moduleRef,
118
- historyCount: history.length,
119
- ...(latest !== undefined ? { latest } : {}),
120
- ...(lastPassAt !== undefined ? { lastPassAt } : {}),
121
- ...(lastFailAt !== undefined ? { lastFailAt } : {}),
122
- });
123
- // config readiness, secret-redacted, from materialized current reality
124
- const resolved = resolveModuleConfig(module, materializedConfig[module.moduleRef]);
125
- configEntries.push({
126
- moduleRef: module.moduleRef,
127
- values: redactSecretValues(resolved.values, resolved.secretRefs),
128
- secretRefs: [...resolved.secretRefs],
129
- missing: resolved.missing,
130
- });
131
- }
132
- // persisted pending ACTION_REQUIRED blocks readiness unless its plan is COMPLETE
133
- const deploymentState = await loadDeploymentState(deps.paths);
134
- if (deploymentState !== undefined) {
135
- for (const pending of clearCompletedPendingActions(deploymentState)
136
- .pendingActions) {
137
- if (currentVerifiedModuleRefs.has(pending.moduleRef))
138
- continue;
139
- const action = {
140
- moduleRef: pending.moduleRef,
141
- action: pending.action,
142
- ...(pending.description !== undefined
143
- ? { description: pending.description }
144
- : {}),
145
- };
146
- blockingActions.push(action);
147
- pendingActions.push(action);
148
- }
149
- }
150
- const { state } = assessPlatformReady({
151
- modules,
152
- status: statusResults,
153
- verification: allRecords,
154
- config: materializedConfig,
155
- blockingActions,
156
- resources,
157
- });
158
- return {
159
- contract: MANIFEST_CONTRACT,
160
- observedAt,
161
- status: state,
162
- modules: manifestModules,
163
- verification,
164
- config: configEntries,
165
- pendingActions,
166
- };
167
- }
@@ -1,31 +0,0 @@
1
- import type { PlatformState } from "../contracts.ts";
2
- import { type WorkspacePaths } from "../paths.ts";
3
- export type DeploymentObserverSource = "status" | "verify" | "doctor";
4
- export type DeploymentObserverModuleStatus = "SUCCEEDED" | "BLOCKED" | "ACTION_REQUIRED" | "FAILED";
5
- export interface DeploymentObserverSummary {
6
- contract: "proflow.deployment-observer-summary.v1";
7
- scope: "PLATFORM";
8
- source: DeploymentObserverSource;
9
- state: PlatformState;
10
- selectedModuleCount: number;
11
- totalModuleCount: number;
12
- observedModuleCount: number;
13
- blockingModuleCount: number;
14
- modules: readonly {
15
- moduleRef: string;
16
- status: DeploymentObserverModuleStatus;
17
- }[];
18
- observedAt: string;
19
- freshUntil: string;
20
- }
21
- export declare const DEPLOYMENT_OBSERVER_SUMMARY_TTL_MS = 60000;
22
- export declare function writeDeploymentObserverSummary(input: {
23
- paths: WorkspacePaths;
24
- source: DeploymentObserverSource;
25
- selectedModuleCount: number;
26
- totalModuleCount: number;
27
- modules: readonly {
28
- moduleRef: string;
29
- status: DeploymentObserverModuleStatus;
30
- }[];
31
- }): Promise<DeploymentObserverSummary>;
@@ -1,34 +0,0 @@
1
- import { join } from "node:path";
2
- import { atomicWrite } from "../paths.js";
3
- function stateOf(statuses) {
4
- if (statuses.includes("FAILED") || statuses.includes("BLOCKED"))
5
- return "NOT_READY";
6
- if (statuses.includes("ACTION_REQUIRED"))
7
- return "ACTION_REQUIRED";
8
- return statuses.length === 0 ? "NOT_READY" : "READY";
9
- }
10
- export const DEPLOYMENT_OBSERVER_SUMMARY_TTL_MS = 60_000;
11
- export async function writeDeploymentObserverSummary(input) {
12
- const modules = [...input.modules].sort((a, b) => a.moduleRef.localeCompare(b.moduleRef));
13
- const observedAt = new Date();
14
- const isCompletePlatformObservation = input.selectedModuleCount === input.totalModuleCount &&
15
- modules.length === input.totalModuleCount;
16
- const summary = {
17
- contract: "proflow.deployment-observer-summary.v1",
18
- scope: "PLATFORM",
19
- source: input.source,
20
- state: isCompletePlatformObservation
21
- ? stateOf(modules.map((item) => item.status))
22
- : "NOT_READY",
23
- selectedModuleCount: input.selectedModuleCount,
24
- totalModuleCount: input.totalModuleCount,
25
- observedModuleCount: modules.length,
26
- blockingModuleCount: modules.filter((item) => item.status !== "SUCCEEDED")
27
- .length,
28
- modules,
29
- observedAt: observedAt.toISOString(),
30
- freshUntil: new Date(observedAt.getTime() + DEPLOYMENT_OBSERVER_SUMMARY_TTL_MS).toISOString(),
31
- };
32
- await atomicWrite(join(input.paths.deployment, "observer-summary.json"), `${JSON.stringify(summary, null, 2)}\n`);
33
- return summary;
34
- }
@@ -1,2 +0,0 @@
1
- export declare function writeFileAtomic(file: string, content: string, mode?: number): Promise<void>;
2
- export declare function writeJsonAtomic(file: string, value: unknown, mode?: number): Promise<void>;
@@ -1,16 +0,0 @@
1
- import { randomBytes } from "node:crypto";
2
- import { chmod, mkdir, rename, writeFile } from "node:fs/promises";
3
- import { dirname } from "node:path";
4
- // Mode-aware atomic write: bounded write to a temp sibling, then rename into
5
- // place. This is an enhancement of paths.atomicWrite that pins an explicit file
6
- // mode (e.g. 0o600 for secret-bearing materialization) independent of umask.
7
- export async function writeFileAtomic(file, content, mode = 0o644) {
8
- await mkdir(dirname(file), { recursive: true });
9
- const tmp = `${file}.tmp-${randomBytes(6).toString("hex")}`;
10
- await writeFile(tmp, content, { encoding: "utf8", mode });
11
- await chmod(tmp, mode);
12
- await rename(tmp, file);
13
- }
14
- export async function writeJsonAtomic(file, value, mode = 0o644) {
15
- await writeFileAtomic(file, `${JSON.stringify(value, null, 2)}\n`, mode);
16
- }
@@ -1,3 +0,0 @@
1
- import type { ResolvedModule } from "../contracts.ts";
2
- import type { WorkspacePaths } from "../paths.ts";
3
- export declare function mergeEffectiveConfig(paths: WorkspacePaths, modules: readonly ResolvedModule[], provided: Record<string, Record<string, string>>): Promise<Record<string, Record<string, string>>>;