@tomflow/proflow-platform-host 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.
@@ -0,0 +1,243 @@
1
+ import { descriptor } from "./descriptor.js";
2
+ const base = {
3
+ contract: "deployment.result.v1",
4
+ ok: true,
5
+ status: "SUCCEEDED",
6
+ moduleRef: descriptor.moduleRef,
7
+ moduleVersion: descriptor.moduleVersion,
8
+ };
9
+ const unbound = {
10
+ ...base,
11
+ ok: false,
12
+ status: "ACTION_REQUIRED",
13
+ actionRequired: {
14
+ action: "compose-platform-host",
15
+ description: "Provide validated Host configuration and owner transports",
16
+ },
17
+ };
18
+ export function createBehaviorAdapter(service) {
19
+ return {
20
+ describe: () => ({ result: base, observedEffects: [] }),
21
+ preflight: () => ({ result: base, observedEffects: [] }),
22
+ status: async () => {
23
+ const status = service ? await service.status() : undefined;
24
+ const ready = status?.readiness === "READY";
25
+ return {
26
+ result: service
27
+ ? {
28
+ ...(ready
29
+ ? base
30
+ : {
31
+ ...base,
32
+ ok: false,
33
+ status: "ACTION_REQUIRED",
34
+ actionRequired: {
35
+ action: "repair-platform-host",
36
+ description: "Platform Host is not READY",
37
+ },
38
+ }),
39
+ data: status,
40
+ checks: [
41
+ {
42
+ id: "platform-host-readiness",
43
+ status: ready ? "PASS" : "FAIL",
44
+ message: `Platform Host readiness is ${status?.readiness ?? "NOT_READY"}`,
45
+ },
46
+ ],
47
+ }
48
+ : unbound,
49
+ observedEffects: [],
50
+ };
51
+ },
52
+ verify: async () => {
53
+ const status = service ? await service.status() : undefined;
54
+ const ready = status?.readiness === "READY";
55
+ return {
56
+ result: {
57
+ ...(service && ready
58
+ ? base
59
+ : service
60
+ ? {
61
+ ...base,
62
+ ok: false,
63
+ status: "ACTION_REQUIRED",
64
+ actionRequired: {
65
+ action: "repair-platform-host",
66
+ description: "Platform Host is not READY",
67
+ },
68
+ }
69
+ : unbound),
70
+ checks: [
71
+ {
72
+ id: "platform-host-readiness",
73
+ status: ready ? "PASS" : "FAIL",
74
+ message: service
75
+ ? `Platform Host readiness is ${status?.readiness ?? "NOT_READY"}`
76
+ : "No configured Host process is bound",
77
+ },
78
+ ],
79
+ },
80
+ observedEffects: [],
81
+ };
82
+ },
83
+ doctor: () => ({ result: base, observedEffects: [] }),
84
+ start: async () => ({
85
+ result: service ? { ...base, data: await service.start() } : unbound,
86
+ observedEffects: service ? ["Manage the platform-host process"] : [],
87
+ }),
88
+ stop: async () => {
89
+ if (service)
90
+ await service.stop();
91
+ return {
92
+ result: service ? base : unbound,
93
+ observedEffects: service ? ["Manage the platform-host process"] : [],
94
+ };
95
+ },
96
+ restart: async () => ({
97
+ result: service ? { ...base, data: await service.restart() } : unbound,
98
+ observedEffects: service ? ["Manage the platform-host process"] : [],
99
+ }),
100
+ uninstall: async () => {
101
+ if (service)
102
+ await service.stop();
103
+ return {
104
+ result: service ? base : unbound,
105
+ observedEffects: service ? ["Manage the platform-host process"] : [],
106
+ };
107
+ },
108
+ };
109
+ }
110
+ export const behaviorAdapter = createBehaviorAdapter();
111
+ const REQUIRED_CONFIG = [
112
+ "stateRoot",
113
+ "workspaceRoot",
114
+ "gatewayTransportCredentialFile",
115
+ "executionBaseUrl",
116
+ "executionTransportCredentialFile",
117
+ "modelBaseUrl",
118
+ "modelTransportCredentialFile",
119
+ ];
120
+ // Binds the real Host process only when the materialized config is complete;
121
+ // otherwise stays unbound so the CLI's catalog falls back to the fail-closed
122
+ // default. The heavy src import is deferred until a real binding is requested.
123
+ export async function createServiceProcessBinding(input) {
124
+ const config = input.config;
125
+ if (!REQUIRED_CONFIG.every((key) => config[key] !== undefined))
126
+ return undefined;
127
+ const executionRuntime = input.configByModuleRef.get("execution-runtime");
128
+ const advertised = executionRuntime?.["identity.endpoint"];
129
+ if (!advertised)
130
+ return undefined;
131
+ const listener = new URL(advertised);
132
+ if (listener.protocol !== "http:" || listener.pathname !== "/")
133
+ return undefined;
134
+ const port = listener.port === "" ? 80 : Number(listener.port);
135
+ if (!Number.isInteger(port) || port <= 0 || port > 65_535)
136
+ return undefined;
137
+ const { parsePlatformHostConfig } = await import("../src/index.js");
138
+ const processConfig = parsePlatformHostConfig({
139
+ stateRoot: config.stateRoot,
140
+ workspaceRoot: config.workspaceRoot,
141
+ host: listener.hostname,
142
+ port,
143
+ executionBaseUrl: config.executionBaseUrl,
144
+ executionTransportCredentialFile: config.executionTransportCredentialFile,
145
+ modelBaseUrl: config.modelBaseUrl,
146
+ modelTransportCredentialFile: config.modelTransportCredentialFile,
147
+ gatewayTransportCredentialFile: config.gatewayTransportCredentialFile,
148
+ roles: [],
149
+ });
150
+ const probeAdapter = {
151
+ describe: behaviorAdapter.describe,
152
+ preflight: behaviorAdapter.preflight,
153
+ verify: async () => {
154
+ let ready = false;
155
+ try {
156
+ ready = (await fetch(new URL("/ready", listener))).ok;
157
+ }
158
+ catch {
159
+ ready = false;
160
+ }
161
+ return {
162
+ result: ready
163
+ ? {
164
+ ...base,
165
+ checks: [
166
+ {
167
+ id: "platform-host-readiness",
168
+ status: "PASS",
169
+ message: "Managed Platform Host /ready probe passed",
170
+ },
171
+ ],
172
+ }
173
+ : {
174
+ ...base,
175
+ ok: false,
176
+ status: "ACTION_REQUIRED",
177
+ actionRequired: {
178
+ action: "repair-platform-host",
179
+ description: "Managed Platform Host /ready probe failed",
180
+ },
181
+ checks: [
182
+ {
183
+ id: "platform-host-readiness",
184
+ status: "FAIL",
185
+ message: "Managed Platform Host /ready probe failed",
186
+ },
187
+ ],
188
+ },
189
+ observedEffects: [],
190
+ };
191
+ },
192
+ doctor: behaviorAdapter.doctor,
193
+ };
194
+ return {
195
+ serviceProcess: {
196
+ contract: "deployment.service-process.v1",
197
+ bin: "proflow-platform-host",
198
+ startCommand: "start",
199
+ config: processConfig,
200
+ },
201
+ behaviorAdapter: probeAdapter,
202
+ };
203
+ }
204
+ export async function createProductionBinding(input) {
205
+ const config = input.config;
206
+ if (!REQUIRED_CONFIG.every((key) => config[key] !== undefined))
207
+ return undefined;
208
+ // The Platform Host's public loopback endpoint is already a cross-module
209
+ // contract: Execution calls it through identity.endpoint. Reuse that
210
+ // materialized endpoint as the listener instead of binding an ephemeral port
211
+ // that no dependent module could discover after `start`.
212
+ const executionRuntime = input.configByModuleRef.get("execution-runtime");
213
+ const advertised = executionRuntime?.["identity.endpoint"];
214
+ if (!advertised)
215
+ return undefined;
216
+ const listener = new URL(advertised);
217
+ if (listener.protocol !== "http:" || listener.pathname !== "/")
218
+ return undefined;
219
+ const port = listener.port === "" ? 80 : Number(listener.port);
220
+ if (!Number.isInteger(port) || port <= 0 || port > 65_535)
221
+ return undefined;
222
+ const { createPlatformHost, parsePlatformHostConfig } = await import("../src/index.js");
223
+ const hostConfig = parsePlatformHostConfig({
224
+ stateRoot: config.stateRoot,
225
+ workspaceRoot: config.workspaceRoot,
226
+ host: listener.hostname,
227
+ port,
228
+ executionBaseUrl: config.executionBaseUrl,
229
+ executionTransportCredentialFile: config.executionTransportCredentialFile,
230
+ modelBaseUrl: config.modelBaseUrl,
231
+ modelTransportCredentialFile: config.modelTransportCredentialFile,
232
+ gatewayTransportCredentialFile: config.gatewayTransportCredentialFile,
233
+ roles: [],
234
+ });
235
+ const host = createPlatformHost({ config: hostConfig });
236
+ const service = {
237
+ start: () => host.start(),
238
+ stop: () => host.stop(),
239
+ restart: () => host.restart(),
240
+ status: async () => ({ readiness: (await host.status()).readiness }),
241
+ };
242
+ return { behaviorAdapter: createBehaviorAdapter(service) };
243
+ }
@@ -0,0 +1,96 @@
1
+ export declare const descriptor: {
2
+ readonly contract: "module";
3
+ readonly contractVersion: "1.0.0";
4
+ readonly moduleRef: "platform-host";
5
+ readonly packageName: "@tomflow/proflow-platform-host";
6
+ readonly moduleVersion: "0.1.0";
7
+ readonly kind: "service";
8
+ readonly templateVersion: "1.0.0";
9
+ readonly platformCompatibility: ">=1.0.0 <2.0.0";
10
+ readonly installClass: "core";
11
+ readonly identity: {
12
+ readonly domain: "platform-architecture";
13
+ readonly summary: "Provides the ProFlow local application composition root that binds Task, Agent, Execution and Model owner transports.";
14
+ };
15
+ readonly provides: readonly [{
16
+ readonly contractRef: "platform-host";
17
+ readonly version: "1.0.0";
18
+ }];
19
+ readonly requires: readonly [{
20
+ readonly contractRef: "task-orchestration";
21
+ readonly versionRange: ">=1.0.0 <2.0.0";
22
+ }, {
23
+ readonly contractRef: "agent-runtime";
24
+ readonly versionRange: ">=1.0.0 <2.0.0";
25
+ }, {
26
+ readonly contractRef: "execution";
27
+ readonly versionRange: ">=1.0.0 <2.0.0";
28
+ }, {
29
+ readonly contractRef: "model-inference";
30
+ readonly versionRange: ">=1.0.0 <2.0.0";
31
+ }];
32
+ readonly requirements: readonly [{
33
+ readonly kind: "runtime";
34
+ readonly runtime: "node";
35
+ readonly versionRange: ">=24.19.0";
36
+ }];
37
+ readonly configSlots: readonly [{
38
+ readonly key: "stateRoot";
39
+ readonly type: "path";
40
+ readonly required: true;
41
+ readonly description: "Absolute .proflow owner state root";
42
+ }, {
43
+ readonly key: "workspaceRoot";
44
+ readonly type: "path";
45
+ readonly required: true;
46
+ readonly description: "Absolute Task document workspace root";
47
+ }, {
48
+ readonly key: "gatewayTransportCredentialFile";
49
+ readonly type: "path";
50
+ readonly required: true;
51
+ readonly sensitive: true;
52
+ readonly description: "File containing the dedicated credential accepted from agent-gateway";
53
+ }, {
54
+ readonly key: "executionBaseUrl";
55
+ readonly type: "url";
56
+ readonly required: true;
57
+ readonly description: "Loopback Execution Runtime public transport";
58
+ }, {
59
+ readonly key: "executionTransportCredentialFile";
60
+ readonly type: "path";
61
+ readonly required: true;
62
+ readonly sensitive: true;
63
+ readonly description: "File containing the credential used for Platform Host calls to Execution Runtime";
64
+ }, {
65
+ readonly key: "modelTransportCredentialFile";
66
+ readonly type: "path";
67
+ readonly required: true;
68
+ readonly sensitive: true;
69
+ readonly description: "File containing the credential used for Platform Host calls to Model Runtime";
70
+ }, {
71
+ readonly key: "modelBaseUrl";
72
+ readonly type: "url";
73
+ readonly required: true;
74
+ readonly description: "Loopback Model Runtime public transport";
75
+ }];
76
+ readonly lifecycle: {
77
+ readonly supported: readonly ["describe", "preflight", "status", "verify", "doctor", "start", "stop", "restart", "uninstall"];
78
+ };
79
+ readonly verification: {
80
+ readonly checks: readonly [{
81
+ readonly id: "platform-host-readiness";
82
+ readonly description: "Host transport and current owner dependencies are ready";
83
+ readonly lifecycle: "verify";
84
+ }];
85
+ };
86
+ readonly effects: readonly [{
87
+ readonly kind: "process";
88
+ readonly description: "Manage the platform-host process";
89
+ readonly retention: "remove";
90
+ }];
91
+ readonly documentation: readonly [{
92
+ readonly id: "overview";
93
+ readonly path: "./README.md";
94
+ readonly description: "Platform Host package overview";
95
+ }];
96
+ };
@@ -0,0 +1,108 @@
1
+ export const descriptor = {
2
+ contract: "module",
3
+ contractVersion: "1.0.0",
4
+ moduleRef: "platform-host",
5
+ packageName: "@tomflow/proflow-platform-host",
6
+ moduleVersion: "0.1.0",
7
+ kind: "service",
8
+ templateVersion: "1.0.0",
9
+ platformCompatibility: ">=1.0.0 <2.0.0",
10
+ installClass: "core",
11
+ identity: {
12
+ domain: "platform-architecture",
13
+ summary: "Provides the ProFlow local application composition root that binds Task, Agent, Execution and Model owner transports.",
14
+ },
15
+ provides: [{ contractRef: "platform-host", version: "1.0.0" }],
16
+ requires: [
17
+ { contractRef: "task-orchestration", versionRange: ">=1.0.0 <2.0.0" },
18
+ { contractRef: "agent-runtime", versionRange: ">=1.0.0 <2.0.0" },
19
+ { contractRef: "execution", versionRange: ">=1.0.0 <2.0.0" },
20
+ { contractRef: "model-inference", versionRange: ">=1.0.0 <2.0.0" },
21
+ ],
22
+ requirements: [
23
+ { kind: "runtime", runtime: "node", versionRange: ">=24.19.0" },
24
+ ],
25
+ configSlots: [
26
+ {
27
+ key: "stateRoot",
28
+ type: "path",
29
+ required: true,
30
+ description: "Absolute .proflow owner state root",
31
+ },
32
+ {
33
+ key: "workspaceRoot",
34
+ type: "path",
35
+ required: true,
36
+ description: "Absolute Task document workspace root",
37
+ },
38
+ {
39
+ key: "gatewayTransportCredentialFile",
40
+ type: "path",
41
+ required: true,
42
+ sensitive: true,
43
+ description: "File containing the dedicated credential accepted from agent-gateway",
44
+ },
45
+ {
46
+ key: "executionBaseUrl",
47
+ type: "url",
48
+ required: true,
49
+ description: "Loopback Execution Runtime public transport",
50
+ },
51
+ {
52
+ key: "executionTransportCredentialFile",
53
+ type: "path",
54
+ required: true,
55
+ sensitive: true,
56
+ description: "File containing the credential used for Platform Host calls to Execution Runtime",
57
+ },
58
+ {
59
+ key: "modelTransportCredentialFile",
60
+ type: "path",
61
+ required: true,
62
+ sensitive: true,
63
+ description: "File containing the credential used for Platform Host calls to Model Runtime",
64
+ },
65
+ {
66
+ key: "modelBaseUrl",
67
+ type: "url",
68
+ required: true,
69
+ description: "Loopback Model Runtime public transport",
70
+ },
71
+ ],
72
+ lifecycle: {
73
+ supported: [
74
+ "describe",
75
+ "preflight",
76
+ "status",
77
+ "verify",
78
+ "doctor",
79
+ "start",
80
+ "stop",
81
+ "restart",
82
+ "uninstall",
83
+ ],
84
+ },
85
+ verification: {
86
+ checks: [
87
+ {
88
+ id: "platform-host-readiness",
89
+ description: "Host transport and current owner dependencies are ready",
90
+ lifecycle: "verify",
91
+ },
92
+ ],
93
+ },
94
+ effects: [
95
+ {
96
+ kind: "process",
97
+ description: "Manage the platform-host process",
98
+ retention: "remove",
99
+ },
100
+ ],
101
+ documentation: [
102
+ {
103
+ id: "overview",
104
+ path: "./README.md",
105
+ description: "Platform Host package overview",
106
+ },
107
+ ],
108
+ };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { createPlatformHost, loadPlatformHostConfig } from "./index.js";
4
+ function installSelf() {
5
+ const executable = process.platform === "win32" ? "npx.cmd" : "npx";
6
+ const result = spawnSync(executable, [
7
+ "--yes",
8
+ "@tomflow/proflow-platform-cli",
9
+ "install",
10
+ "@tomflow/proflow-platform-host",
11
+ ], { cwd: process.cwd(), env: process.env, stdio: "inherit" });
12
+ if (result.error)
13
+ throw result.error;
14
+ process.exit(result.status ?? 1);
15
+ }
16
+ async function main() {
17
+ const [command, configPath] = process.argv.slice(2);
18
+ if (command === "--help" || command === "-h") {
19
+ process.stdout.write("Usage: proflow-platform-host install | start /absolute/config.json\\n");
20
+ process.exit(0);
21
+ }
22
+ if (command === "install") {
23
+ if (configPath)
24
+ throw new Error("Usage: proflow-platform-host install");
25
+ installSelf();
26
+ }
27
+ if (command !== "start" || !configPath)
28
+ throw new Error("Usage: proflow-platform-host start /absolute/config.json");
29
+ const config = await loadPlatformHostConfig(configPath);
30
+ if (!config.gatewayTransportCredentialFile)
31
+ throw new Error("platform-host requires gatewayTransportCredentialFile for authenticated Gateway transport");
32
+ if (!config.modelTransportCredentialFile)
33
+ throw new Error("platform-host requires modelTransportCredentialFile for authenticated Model Runtime transport");
34
+ if (!config.executionTransportCredentialFile)
35
+ throw new Error("platform-host requires executionTransportCredentialFile for authenticated Execution Runtime transport");
36
+ const host = createPlatformHost({
37
+ config,
38
+ log: (entry) => process.stderr.write(`${JSON.stringify(entry)}\n`),
39
+ });
40
+ const address = await host.start();
41
+ process.stdout.write(`${JSON.stringify({ status: "RUNNING", ...address })}\n`);
42
+ let stopping = false;
43
+ const stop = () => {
44
+ if (stopping)
45
+ return;
46
+ stopping = true;
47
+ void host.stop().finally(() => process.exit(0));
48
+ };
49
+ process.on("SIGINT", stop);
50
+ process.on("SIGTERM", stop);
51
+ await new Promise(() => { });
52
+ }
53
+ if (import.meta.main) {
54
+ await main();
55
+ }