@volter/world-runtime 3.0.56 → 3.0.57
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/dist/src/consumers.d.ts +9 -0
- package/dist/src/consumers.js +20 -9
- package/dist/src/runtime.js +9 -10
- package/package.json +4 -4
- package/src/consumers.ts +21 -8
- package/src/runtime.ts +7 -4
package/dist/src/consumers.d.ts
CHANGED
|
@@ -21,6 +21,15 @@ export declare function createConsumer(root: string, name: string, instanceCreat
|
|
|
21
21
|
}): WorldConsumer;
|
|
22
22
|
export declare function refreshConsumer(root: string, name: string, record: WorldConsumer): void;
|
|
23
23
|
export declare function releaseConsumer(root: string, name: string, record: WorldConsumer): void;
|
|
24
|
+
export type ConsumerProcessState = {
|
|
25
|
+
state: 'present' | 'absent';
|
|
26
|
+
} | {
|
|
27
|
+
state: 'unknown';
|
|
28
|
+
reason: string;
|
|
29
|
+
};
|
|
30
|
+
/** Signal delivery cannot prove existence. Only a successful signal-0 probe or ESRCH
|
|
31
|
+
* answers it; permission and registry errors retain uncertainty, never cleanup authority. */
|
|
32
|
+
export declare function consumerProcessState(pid: number): ConsumerProcessState;
|
|
24
33
|
/** Inspection never deletes records or interprets incomplete coverage as no consumers. */
|
|
25
34
|
export declare function worldConsumers(root: string, name: string, instanceCreatedAt?: string): {
|
|
26
35
|
consumers: (WorldConsumer & {
|
package/dist/src/consumers.js
CHANGED
|
@@ -80,15 +80,18 @@ export function releaseConsumer(root, name, record) {
|
|
|
80
80
|
rmSync(join(dir, `${record.id}.json`), { force: true });
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
/** Signal delivery cannot prove existence. Only a successful signal-0 probe or ESRCH
|
|
84
|
+
* answers it; permission and registry errors retain uncertainty, never cleanup authority. */
|
|
85
|
+
export function consumerProcessState(pid) {
|
|
86
86
|
try {
|
|
87
87
|
process.kill(pid, 0);
|
|
88
|
-
return
|
|
88
|
+
return { state: 'present' };
|
|
89
89
|
}
|
|
90
90
|
catch (error) {
|
|
91
|
-
|
|
91
|
+
const cause = error;
|
|
92
|
+
if (cause?.code === 'ESRCH')
|
|
93
|
+
return { state: 'absent' };
|
|
94
|
+
return { state: 'unknown', reason: cause?.code ?? cause?.message ?? String(error) };
|
|
92
95
|
}
|
|
93
96
|
}
|
|
94
97
|
/** Inspection never deletes records or interprets incomplete coverage as no consumers. */
|
|
@@ -131,10 +134,18 @@ export function worldConsumers(root, name, instanceCreatedAt) {
|
|
|
131
134
|
checkedOwner(r.owner);
|
|
132
135
|
const age = Date.now() - Date.parse(r.heartbeatAt);
|
|
133
136
|
const sameInstance = instanceCreatedAt === r.instanceCreatedAt;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
// Never interpret another host's numeric PIDs as processes on this host.
|
|
138
|
+
const local = r.hostname === hostname() && sameInstance;
|
|
139
|
+
const runner = local ? consumerProcessState(r.runnerPid) : undefined;
|
|
140
|
+
const command = local && r.consumerPid !== undefined ? consumerProcessState(r.consumerPid) : undefined;
|
|
141
|
+
if (runner?.state === 'unknown')
|
|
142
|
+
warnings.push(`Consumer ${r.id}: runner ${r.runnerPid} existence unknown (${runner.reason}).`);
|
|
143
|
+
if (command?.state === 'unknown')
|
|
144
|
+
warnings.push(`Consumer ${r.id}: command ${r.consumerPid} existence unknown (${command.reason}).`);
|
|
145
|
+
const state = r.retirementUncertain || !local || runner?.state === 'unknown' || command?.state === 'unknown' ? 'uncertain'
|
|
146
|
+
: runner?.state === 'absent' && r.consumerPid === undefined ? 'uncertain'
|
|
147
|
+
: runner?.state === 'absent' && command?.state === 'absent' ? (r.completionRequired ? 'uncertain' : 'exited')
|
|
148
|
+
: runner?.state === 'present' && age >= -STALE_MS && age <= STALE_MS ? 'active' : 'uncertain';
|
|
138
149
|
// Explicit fields only: malformed records must never leak arbitrary metadata into reports.
|
|
139
150
|
consumers.push({ id: r.id, instanceCreatedAt: r.instanceCreatedAt, kind: r.kind, owner: r.owner,
|
|
140
151
|
uid: r.uid, hostname: r.hostname, runnerPid: r.runnerPid, consumerPid: r.consumerPid,
|
package/dist/src/runtime.js
CHANGED
|
@@ -38,7 +38,7 @@ import { readAppUrl } from "./app-url.js";
|
|
|
38
38
|
import { inertInjectEnvWarnings } from "./inject-map.js";
|
|
39
39
|
import { activeVendorMap, ensureCa, hostHasRawEgress, opensslAvailable, proxyEnvFor, startRedirectProxy, tearDownCa } from "./redirect-proxy.js";
|
|
40
40
|
import { CaTrustError, sessionTrustEnv } from "./ca-trust.js";
|
|
41
|
-
import { checkedOwner, CONSUMER_HEARTBEAT_MS, createConsumer, refreshConsumer, releaseConsumer, removeConsumerRecord, worldConsumers } from "./consumers.js";
|
|
41
|
+
import { checkedOwner, CONSUMER_HEARTBEAT_MS, createConsumer, refreshConsumer, releaseConsumer, removeConsumerRecord, worldConsumers, consumerProcessState } from "./consumers.js";
|
|
42
42
|
import { assertLifecycleStartable, beginWorldLifecycle, handoffWorldLifecycle, recordWorldLifecycleEvent, releaseWorldLifecycle, retainWorldLifecycleForCleanup, hasWorldLifecycle, isWorldLifecycleRunning, lifecycleLogPath, releaseAbandonedWorldLifecycle, exitedWorldLifecyclePids } from "./lifecycle-record.js";
|
|
43
43
|
// Births captured at actual spawn; retries never remeasure a retained PID.
|
|
44
44
|
const spawnedIdentities = new Map();
|
|
@@ -3831,24 +3831,23 @@ export function retireWorldConsumers(name, root = process.cwd(), options = {}) {
|
|
|
3831
3831
|
const chosen = consumers.filter(c => c.state === 'uncertain' && (options.consumer === undefined || c.id === options.consumer));
|
|
3832
3832
|
if (options.consumer !== undefined && chosen.length === 0)
|
|
3833
3833
|
throw new Error(`World "${name}" has no uncertain consumer ${options.consumer}`);
|
|
3834
|
-
const gone = (pid) => { try {
|
|
3835
|
-
process.kill(pid, 0);
|
|
3836
|
-
return false;
|
|
3837
|
-
}
|
|
3838
|
-
catch (error) {
|
|
3839
|
-
return error.code === 'ESRCH';
|
|
3840
|
-
} };
|
|
3841
3834
|
return chosen.map((c) => {
|
|
3842
3835
|
const keep = (reason) => ({ id: c.id, owner: c.owner, retired: false, reason });
|
|
3843
3836
|
if (c.hostname !== osHostname())
|
|
3844
3837
|
return keep(`recorded on host ${c.hostname}; retire it there`);
|
|
3845
3838
|
if (c.instanceCreatedAt !== current.createdAt)
|
|
3846
3839
|
return keep('belongs to another instance of this World');
|
|
3847
|
-
|
|
3840
|
+
const runner = consumerProcessState(c.runnerPid);
|
|
3841
|
+
if (runner.state === 'unknown')
|
|
3842
|
+
return keep(`runner ${c.runnerPid} existence is unknown (${runner.reason})`);
|
|
3843
|
+
if (runner.state === 'present')
|
|
3848
3844
|
return keep(`runner ${c.runnerPid} is still alive`);
|
|
3849
3845
|
if (c.consumerPid === undefined)
|
|
3850
3846
|
return keep('its command was never recorded as started, so its processes cannot be proven gone');
|
|
3851
|
-
|
|
3847
|
+
const command = consumerProcessState(c.consumerPid);
|
|
3848
|
+
if (command.state === 'unknown')
|
|
3849
|
+
return keep(`command ${c.consumerPid} existence is unknown (${command.reason})`);
|
|
3850
|
+
if (command.state === 'present')
|
|
3852
3851
|
return keep(`command ${c.consumerPid} is still alive`);
|
|
3853
3852
|
const surviving = survivingOwnedGroups([c.consumerPid]);
|
|
3854
3853
|
if (surviving.length)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-runtime",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.57",
|
|
4
4
|
"description": "World configs for twins: boot named local runtimes, allocate ports, generate world.env/instance.json, and run apps against fake-key twin worlds.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"twin",
|
|
@@ -69,14 +69,14 @@
|
|
|
69
69
|
"node": ">=22.3"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"@volter/world-core": "3.0.
|
|
73
|
-
"@volter/world-console": "3.0.
|
|
72
|
+
"@volter/world-core": "3.0.57",
|
|
73
|
+
"@volter/world-console": "3.0.52"
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@electric-sql/pglite": "0.5.8",
|
|
77
77
|
"@electric-sql/pglite-pgvector": "0.0.9",
|
|
78
78
|
"@volter/world-access": "3.0.1",
|
|
79
|
-
"@volter/world-core": "3.0.
|
|
79
|
+
"@volter/world-core": "3.0.57",
|
|
80
80
|
"mongodb": "~6.20.0",
|
|
81
81
|
"pg-gateway": "0.3.0-beta.4",
|
|
82
82
|
"smol-toml": "^1.8.0",
|
package/src/consumers.ts
CHANGED
|
@@ -91,10 +91,17 @@ export function releaseConsumer(root: string, name: string, record: WorldConsume
|
|
|
91
91
|
} finally { rmSync(join(dir, `${record.id}.json`), { force: true }); }
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
export type ConsumerProcessState = { state: 'present' | 'absent' } | { state: 'unknown'; reason: string };
|
|
95
|
+
|
|
96
|
+
/** Signal delivery cannot prove existence. Only a successful signal-0 probe or ESRCH
|
|
97
|
+
* answers it; permission and registry errors retain uncertainty, never cleanup authority. */
|
|
98
|
+
export function consumerProcessState(pid: number): ConsumerProcessState {
|
|
99
|
+
try { process.kill(pid, 0); return { state: 'present' }; }
|
|
100
|
+
catch (error) {
|
|
101
|
+
const cause = error as NodeJS.ErrnoException;
|
|
102
|
+
if (cause?.code === 'ESRCH') return { state: 'absent' };
|
|
103
|
+
return { state: 'unknown', reason: cause?.code ?? cause?.message ?? String(error) };
|
|
104
|
+
}
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
/** Inspection never deletes records or interprets incomplete coverage as no consumers. */
|
|
@@ -126,10 +133,16 @@ export function worldConsumers(root: string, name: string, instanceCreatedAt?: s
|
|
|
126
133
|
checkedOwner(r.owner);
|
|
127
134
|
const age = Date.now() - Date.parse(r.heartbeatAt);
|
|
128
135
|
const sameInstance = instanceCreatedAt === r.instanceCreatedAt;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
136
|
+
// Never interpret another host's numeric PIDs as processes on this host.
|
|
137
|
+
const local = r.hostname === hostname() && sameInstance;
|
|
138
|
+
const runner = local ? consumerProcessState(r.runnerPid) : undefined;
|
|
139
|
+
const command = local && r.consumerPid !== undefined ? consumerProcessState(r.consumerPid) : undefined;
|
|
140
|
+
if (runner?.state === 'unknown') warnings.push(`Consumer ${r.id}: runner ${r.runnerPid} existence unknown (${runner.reason}).`);
|
|
141
|
+
if (command?.state === 'unknown') warnings.push(`Consumer ${r.id}: command ${r.consumerPid} existence unknown (${command.reason}).`);
|
|
142
|
+
const state = r.retirementUncertain || !local || runner?.state === 'unknown' || command?.state === 'unknown' ? 'uncertain'
|
|
143
|
+
: runner?.state === 'absent' && r.consumerPid === undefined ? 'uncertain'
|
|
144
|
+
: runner?.state === 'absent' && command?.state === 'absent' ? (r.completionRequired ? 'uncertain' : 'exited')
|
|
145
|
+
: runner?.state === 'present' && age >= -STALE_MS && age <= STALE_MS ? 'active' : 'uncertain';
|
|
133
146
|
// Explicit fields only: malformed records must never leak arbitrary metadata into reports.
|
|
134
147
|
consumers.push({ id: r.id, instanceCreatedAt: r.instanceCreatedAt, kind: r.kind, owner: r.owner,
|
|
135
148
|
uid: r.uid, hostname: r.hostname, runnerPid: r.runnerPid, consumerPid: r.consumerPid,
|
package/src/runtime.ts
CHANGED
|
@@ -41,7 +41,7 @@ import { inertInjectEnvWarnings } from './inject-map.ts';
|
|
|
41
41
|
import { activeVendorMap, ensureCa, hostHasRawEgress, opensslAvailable, proxyEnvFor, startRedirectProxy, tearDownCa } from './redirect-proxy.ts';
|
|
42
42
|
import { CaTrustError, sessionTrustEnv, type CaTrustInputs } from './ca-trust.ts';
|
|
43
43
|
import type { WorldConfig, WorldExternalReadyWhen, WorldInstance, WorldIsolation, WorldMode, WorldRunOutcome, WorldRunRecord, WorldServiceConfig, WorldServiceInstance } from './schema.ts';
|
|
44
|
-
import { checkedOwner, CONSUMER_HEARTBEAT_MS, createConsumer, refreshConsumer, releaseConsumer, removeConsumerRecord, worldConsumers } from './consumers.ts';
|
|
44
|
+
import { checkedOwner, CONSUMER_HEARTBEAT_MS, createConsumer, refreshConsumer, releaseConsumer, removeConsumerRecord, worldConsumers, consumerProcessState } from './consumers.ts';
|
|
45
45
|
import { assertLifecycleStartable, beginWorldLifecycle, handoffWorldLifecycle, recordWorldLifecycleEvent, releaseWorldLifecycle, retainWorldLifecycleForCleanup, hasWorldLifecycle, isWorldLifecycleRunning, lifecycleLogPath, releaseAbandonedWorldLifecycle, exitedWorldLifecyclePids } from './lifecycle-record.ts';
|
|
46
46
|
|
|
47
47
|
// Births captured at actual spawn; retries never remeasure a retained PID.
|
|
@@ -3628,14 +3628,17 @@ export function retireWorldConsumers(name: string, root = process.cwd(), options
|
|
|
3628
3628
|
const { consumers } = worldConsumers(resolvedRoot, name, current.createdAt);
|
|
3629
3629
|
const chosen = consumers.filter(c => c.state === 'uncertain' && (options.consumer === undefined || c.id === options.consumer));
|
|
3630
3630
|
if (options.consumer !== undefined && chosen.length === 0) throw new Error(`World "${name}" has no uncertain consumer ${options.consumer}`);
|
|
3631
|
-
const gone = (pid: number) => { try { process.kill(pid, 0); return false; } catch (error) { return (error as NodeJS.ErrnoException).code === 'ESRCH'; } };
|
|
3632
3631
|
return chosen.map((c): ConsumerRetirement => {
|
|
3633
3632
|
const keep = (reason: string): ConsumerRetirement => ({ id: c.id, owner: c.owner, retired: false, reason });
|
|
3634
3633
|
if (c.hostname !== osHostname()) return keep(`recorded on host ${c.hostname}; retire it there`);
|
|
3635
3634
|
if (c.instanceCreatedAt !== current.createdAt) return keep('belongs to another instance of this World');
|
|
3636
|
-
|
|
3635
|
+
const runner = consumerProcessState(c.runnerPid);
|
|
3636
|
+
if (runner.state === 'unknown') return keep(`runner ${c.runnerPid} existence is unknown (${runner.reason})`);
|
|
3637
|
+
if (runner.state === 'present') return keep(`runner ${c.runnerPid} is still alive`);
|
|
3637
3638
|
if (c.consumerPid === undefined) return keep('its command was never recorded as started, so its processes cannot be proven gone');
|
|
3638
|
-
|
|
3639
|
+
const command = consumerProcessState(c.consumerPid);
|
|
3640
|
+
if (command.state === 'unknown') return keep(`command ${c.consumerPid} existence is unknown (${command.reason})`);
|
|
3641
|
+
if (command.state === 'present') return keep(`command ${c.consumerPid} is still alive`);
|
|
3639
3642
|
const surviving = survivingOwnedGroups([c.consumerPid]);
|
|
3640
3643
|
if (surviving.length) return keep(`process group ${c.consumerPid} still has live members`);
|
|
3641
3644
|
removeConsumerRecord(resolvedRoot, name, c.id);
|