@forgezero/agent 0.1.19 → 0.1.21
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 +13 -5
- package/dist/fz-agent.js +60 -33
- package/dist/fz.js +1 -1
- package/dist/guest-enrolment.js +9 -12
- package/dist/node-vault.js +9 -12
- package/dist/provisioning-pull.js +8 -11
- package/dist/shutdown.d.ts +10 -0
- package/dist/signed-node-http.d.ts +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -90,10 +90,11 @@ evidence: report acquisition and AMD-rooted measurement verification still have
|
|
|
90
90
|
to cross the production fleet. Until then the node is honestly `enrolled`; the
|
|
91
91
|
Agent never manufactures an attestation-shaped fallback.
|
|
92
92
|
|
|
93
|
-
Every
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
there is no Ed25519-only or
|
|
93
|
+
Every Agent request uses the same hybrid signature codec as an external API key
|
|
94
|
+
and signs its one-use hybrid ML-KEM-768 + X25519 response key. Vault replication,
|
|
95
|
+
deployment claims, attestation and metal provisioning responses are AES-256-GCM
|
|
96
|
+
sealed to that request before crossing the edge; there is no Ed25519-only or
|
|
97
|
+
successful plaintext-response downgrade.
|
|
97
98
|
|
|
98
99
|
The first guest enrolment uses the same contract before the identity exists in
|
|
99
100
|
the database: the guest signs the exact token and public-key body with both keys
|
|
@@ -135,13 +136,20 @@ code, and only an awaited successful pipeline writes `deployed`. An expired
|
|
|
135
136
|
claim can be recovered; its stale token cannot renew or finish.
|
|
136
137
|
|
|
137
138
|
The same package also runs the identity-only Metal Agent. It initiates outbound
|
|
138
|
-
signed HTTPS
|
|
139
|
+
hybrid-signed HTTPS and requires request-bound PQ-sealed responses while pulling
|
|
140
|
+
only claims assigned to its enrolled hostname, then passes
|
|
139
141
|
the fixed claim over a local Unix socket to a narrowly privileged root helper.
|
|
140
142
|
The helper can materialize the audited QEMU profile; it cannot clone a project,
|
|
141
143
|
read Vault data, accept arbitrary commands, or retain tenant credentials. Root
|
|
142
144
|
SSH is an operator-only platform-genesis/recovery path, not the normal tenant
|
|
143
145
|
dispatch mechanism.
|
|
144
146
|
|
|
147
|
+
Shutdown has one process-wide deadline. New deployment claims and background
|
|
148
|
+
sync stop first, claimed work drains under its lease fence, and only then do the
|
|
149
|
+
queues and application socket close. Vault/attestation calls or a stale local
|
|
150
|
+
socket cannot consume systemd's longer stop timeout: exceeding the Agent deadline
|
|
151
|
+
is reported and exits non-zero instead of being silently killed midway by PID 1.
|
|
152
|
+
|
|
145
153
|
Repository read authorization is explicit per pipeline: public HTTPS, the
|
|
146
154
|
compute's systemd-sealed SSH deploy key, or a fine-grained HTTPS token selected
|
|
147
155
|
by a project-vault secret name. A signed claim contains the mode and secret name
|
package/dist/fz-agent.js
CHANGED
|
@@ -123,19 +123,19 @@ class SignedNodeHttpError extends Error {
|
|
|
123
123
|
this.name = "SignedNodeHttpError";
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
-
async function postSignedNode(options, path, body
|
|
126
|
+
async function postSignedNode(options, path, body) {
|
|
127
127
|
const url = new URL(options.apiUrl);
|
|
128
128
|
url.pathname = `${url.pathname.replace(/\/$/, "")}/${path.replace(/^\//, "")}`.replace(/\/+/g, "/");
|
|
129
129
|
url.search = "";
|
|
130
130
|
url.hash = "";
|
|
131
131
|
const raw = JSON.stringify(body);
|
|
132
|
-
const recipient =
|
|
132
|
+
const recipient = generateResponseRecipient();
|
|
133
133
|
const envelope = signRequest(options.keys, options.nodeKey, {
|
|
134
134
|
method: "POST",
|
|
135
135
|
path: url.pathname,
|
|
136
136
|
query: "",
|
|
137
137
|
body: raw,
|
|
138
|
-
responseKey: recipient
|
|
138
|
+
responseKey: recipient.publicKey
|
|
139
139
|
});
|
|
140
140
|
const signature = encodeSignatureHeader(envelope);
|
|
141
141
|
const response = await (options.fetch ?? globalThis.fetch)(url, {
|
|
@@ -144,7 +144,7 @@ async function postSignedNode(options, path, body, sealedResponse = false) {
|
|
|
144
144
|
"content-type": "application/json",
|
|
145
145
|
"x-fz-node": options.nodeKey,
|
|
146
146
|
"x-fz-signature": signature,
|
|
147
|
-
|
|
147
|
+
[RESPONSE_KEY_HEADER]: recipient.publicKey
|
|
148
148
|
},
|
|
149
149
|
body: raw,
|
|
150
150
|
signal: AbortSignal.timeout(options.requestTimeoutMs ?? 15000)
|
|
@@ -155,14 +155,11 @@ async function postSignedNode(options, path, body, sealedResponse = false) {
|
|
|
155
155
|
const reason = failure ? failure.error?.message ?? failure.message : undefined;
|
|
156
156
|
throw new SignedNodeHttpError(response.status, reason || `signed node request returned HTTP ${response.status}`);
|
|
157
157
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
throw new SignedNodeHttpError(502, "The node response was not sealed to this request.");
|
|
163
|
-
}
|
|
158
|
+
try {
|
|
159
|
+
return await openResponse(recipient.secretKey, signature, payload);
|
|
160
|
+
} catch {
|
|
161
|
+
throw new SignedNodeHttpError(502, "The node response was not sealed to this request.");
|
|
164
162
|
}
|
|
165
|
-
return payload;
|
|
166
163
|
}
|
|
167
164
|
|
|
168
165
|
// src/node-vault.ts
|
|
@@ -194,7 +191,7 @@ function tenantNodeApiUrl(apiUrl, tenantSlug) {
|
|
|
194
191
|
return url.toString().replace(/\/$/, "");
|
|
195
192
|
}
|
|
196
193
|
function createNodeVaultCache(options) {
|
|
197
|
-
const post = (operation, body) => postSignedNode(options, `v1/node/vault/${operation}`, body
|
|
194
|
+
const post = (operation, body) => postSignedNode(options, `v1/node/vault/${operation}`, body);
|
|
198
195
|
return createSecretCache({
|
|
199
196
|
ttlMs: options.ttlMs,
|
|
200
197
|
maxStaleMs: options.maxStaleMs,
|
|
@@ -1216,7 +1213,7 @@ async function enrolGuestIdentity(options) {
|
|
|
1216
1213
|
ed25519: options.keys.ed25519.publicKey,
|
|
1217
1214
|
mlDsa: options.keys.mlDsa.publicKey
|
|
1218
1215
|
}
|
|
1219
|
-
}
|
|
1216
|
+
});
|
|
1220
1217
|
if (!payload?.ok || payload.nodeKey !== options.nodeKey || !payload.computeReference || !payload.projectKey || !payload.environmentKey || payload.realm !== "platform" && (payload.realm !== "tenant" || !payload.tenantSlug)) {
|
|
1221
1218
|
throw new Error(payload?.error?.message || "guest enrolment response was not accepted");
|
|
1222
1219
|
}
|
|
@@ -2717,8 +2714,31 @@ async function applyMetalIsolation(profile, exec = defaultExec) {
|
|
|
2717
2714
|
}
|
|
2718
2715
|
}
|
|
2719
2716
|
|
|
2717
|
+
// src/shutdown.ts
|
|
2718
|
+
async function settleWithin(work, timeoutMs, setTimer = setTimeout, clearTimer = clearTimeout) {
|
|
2719
|
+
let timer;
|
|
2720
|
+
try {
|
|
2721
|
+
return await Promise.race([
|
|
2722
|
+
work.then(() => true),
|
|
2723
|
+
new Promise((resolve2) => {
|
|
2724
|
+
timer = setTimer(() => resolve2(false), Math.max(1, timeoutMs));
|
|
2725
|
+
timer.unref?.();
|
|
2726
|
+
})
|
|
2727
|
+
]);
|
|
2728
|
+
} finally {
|
|
2729
|
+
if (timer)
|
|
2730
|
+
clearTimer(timer);
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
async function closeServerWithin(server, timeoutMs) {
|
|
2734
|
+
const closed = await settleWithin(new Promise((resolve2) => server.close(() => resolve2())), timeoutMs);
|
|
2735
|
+
if (!closed)
|
|
2736
|
+
server.unref();
|
|
2737
|
+
return closed;
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2720
2740
|
// src/version.ts
|
|
2721
|
-
var VERSION = "0.1.
|
|
2741
|
+
var VERSION = "0.1.21";
|
|
2722
2742
|
|
|
2723
2743
|
// src/index.ts
|
|
2724
2744
|
function loadOrCreateSeed(path) {
|
|
@@ -3153,24 +3173,26 @@ if (import.meta.main) {
|
|
|
3153
3173
|
return;
|
|
3154
3174
|
stopping = true;
|
|
3155
3175
|
console.log(`[agent] ${signal}: stopping deployment intake and draining`);
|
|
3156
|
-
if (control)
|
|
3157
|
-
await new Promise((resolve2) => control.close(() => resolve2()));
|
|
3158
3176
|
const deadlineMs = Math.max(1, Number(process.env.FZ_DRAIN_DEADLINE_MS ?? 30000));
|
|
3159
3177
|
const deadline = Date.now() + deadlineMs;
|
|
3178
|
+
const remaining = () => Math.max(1, deadline - Date.now());
|
|
3179
|
+
const controlClosed = control ? await settleWithin(new Promise((resolve2) => control.close(() => resolve2())), remaining()) : true;
|
|
3160
3180
|
const pullDrain = pull?.stop() ?? Promise.resolve();
|
|
3161
3181
|
const vaultDrain = vaultSync?.stop() ?? Promise.resolve();
|
|
3162
3182
|
const attestationDrain = attestationLoop?.stop() ?? Promise.resolve();
|
|
3163
|
-
const
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
const pullDrained = await pullWithinDeadline;
|
|
3168
|
-
await Promise.all([vaultDrain, attestationDrain]);
|
|
3169
|
-
const managerReports = await Promise.all([...new Set(managers.values())].map((manager) => manager.stop(Math.max(1, deadline - Date.now()))));
|
|
3170
|
-
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
3183
|
+
const pullDrained = pull ? await settleWithin(pullDrain, remaining()) : true;
|
|
3184
|
+
const backgroundDrained = await settleWithin(Promise.all([vaultDrain, attestationDrain]), remaining());
|
|
3185
|
+
const managerReports = await Promise.all([...new Set(managers.values())].map((manager) => manager.stop(remaining())));
|
|
3186
|
+
const socketClosed = await closeServerWithin(server, remaining());
|
|
3171
3187
|
const timedOut = managerReports.some((report) => report.timedOut);
|
|
3172
|
-
console.log(`[agent] drain ${JSON.stringify({
|
|
3173
|
-
|
|
3188
|
+
console.log(`[agent] drain ${JSON.stringify({
|
|
3189
|
+
managers: managerReports,
|
|
3190
|
+
controlClosed,
|
|
3191
|
+
pullDrained,
|
|
3192
|
+
backgroundDrained,
|
|
3193
|
+
socketClosed
|
|
3194
|
+
})}`);
|
|
3195
|
+
process.exit(timedOut || !controlClosed || !pullDrained || !backgroundDrained || !socketClosed ? 1 : 0);
|
|
3174
3196
|
};
|
|
3175
3197
|
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
3176
3198
|
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
@@ -3178,19 +3200,24 @@ if (import.meta.main) {
|
|
|
3178
3200
|
console.log("[agent] signing/vault mode only; deployment root and pull are not configured");
|
|
3179
3201
|
if (vaultSync || attestationLoop) {
|
|
3180
3202
|
let stopping = false;
|
|
3181
|
-
const stop = async () => {
|
|
3203
|
+
const stop = async (signal) => {
|
|
3182
3204
|
if (stopping)
|
|
3183
3205
|
return;
|
|
3184
3206
|
stopping = true;
|
|
3185
|
-
|
|
3207
|
+
const deadlineMs = Math.max(1, Number(process.env.FZ_DRAIN_DEADLINE_MS ?? 30000));
|
|
3208
|
+
const deadline = Date.now() + deadlineMs;
|
|
3209
|
+
const remaining = () => Math.max(1, deadline - Date.now());
|
|
3210
|
+
console.log(`[agent] ${signal}: stopping background intake and draining`);
|
|
3211
|
+
const backgroundDrained = await settleWithin(Promise.all([
|
|
3186
3212
|
vaultSync?.stop() ?? Promise.resolve(),
|
|
3187
3213
|
attestationLoop?.stop() ?? Promise.resolve()
|
|
3188
|
-
]);
|
|
3189
|
-
|
|
3190
|
-
|
|
3214
|
+
]), remaining());
|
|
3215
|
+
const socketClosed = await closeServerWithin(server, remaining());
|
|
3216
|
+
console.log(`[agent] drain ${JSON.stringify({ backgroundDrained, socketClosed })}`);
|
|
3217
|
+
process.exit(backgroundDrained && socketClosed ? 0 : 1);
|
|
3191
3218
|
};
|
|
3192
|
-
process.on("SIGTERM", () => void stop());
|
|
3193
|
-
process.on("SIGINT", () => void stop());
|
|
3219
|
+
process.on("SIGTERM", () => void stop("SIGTERM"));
|
|
3220
|
+
process.on("SIGINT", () => void stop("SIGINT"));
|
|
3194
3221
|
}
|
|
3195
3222
|
}
|
|
3196
3223
|
}
|
package/dist/fz.js
CHANGED
package/dist/guest-enrolment.js
CHANGED
|
@@ -28,19 +28,19 @@ class SignedNodeHttpError extends Error {
|
|
|
28
28
|
this.name = "SignedNodeHttpError";
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
async function postSignedNode(options, path, body
|
|
31
|
+
async function postSignedNode(options, path, body) {
|
|
32
32
|
const url = new URL(options.apiUrl);
|
|
33
33
|
url.pathname = `${url.pathname.replace(/\/$/, "")}/${path.replace(/^\//, "")}`.replace(/\/+/g, "/");
|
|
34
34
|
url.search = "";
|
|
35
35
|
url.hash = "";
|
|
36
36
|
const raw = JSON.stringify(body);
|
|
37
|
-
const recipient =
|
|
37
|
+
const recipient = generateResponseRecipient();
|
|
38
38
|
const envelope = signRequest(options.keys, options.nodeKey, {
|
|
39
39
|
method: "POST",
|
|
40
40
|
path: url.pathname,
|
|
41
41
|
query: "",
|
|
42
42
|
body: raw,
|
|
43
|
-
responseKey: recipient
|
|
43
|
+
responseKey: recipient.publicKey
|
|
44
44
|
});
|
|
45
45
|
const signature = encodeSignatureHeader(envelope);
|
|
46
46
|
const response = await (options.fetch ?? globalThis.fetch)(url, {
|
|
@@ -49,7 +49,7 @@ async function postSignedNode(options, path, body, sealedResponse = false) {
|
|
|
49
49
|
"content-type": "application/json",
|
|
50
50
|
"x-fz-node": options.nodeKey,
|
|
51
51
|
"x-fz-signature": signature,
|
|
52
|
-
|
|
52
|
+
[RESPONSE_KEY_HEADER]: recipient.publicKey
|
|
53
53
|
},
|
|
54
54
|
body: raw,
|
|
55
55
|
signal: AbortSignal.timeout(options.requestTimeoutMs ?? 15000)
|
|
@@ -60,14 +60,11 @@ async function postSignedNode(options, path, body, sealedResponse = false) {
|
|
|
60
60
|
const reason = failure ? failure.error?.message ?? failure.message : undefined;
|
|
61
61
|
throw new SignedNodeHttpError(response.status, reason || `signed node request returned HTTP ${response.status}`);
|
|
62
62
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
throw new SignedNodeHttpError(502, "The node response was not sealed to this request.");
|
|
68
|
-
}
|
|
63
|
+
try {
|
|
64
|
+
return await openResponse(recipient.secretKey, signature, payload);
|
|
65
|
+
} catch {
|
|
66
|
+
throw new SignedNodeHttpError(502, "The node response was not sealed to this request.");
|
|
69
67
|
}
|
|
70
|
-
return payload;
|
|
71
68
|
}
|
|
72
69
|
|
|
73
70
|
// src/guest-enrolment.ts
|
|
@@ -121,7 +118,7 @@ async function enrolGuestIdentity(options) {
|
|
|
121
118
|
ed25519: options.keys.ed25519.publicKey,
|
|
122
119
|
mlDsa: options.keys.mlDsa.publicKey
|
|
123
120
|
}
|
|
124
|
-
}
|
|
121
|
+
});
|
|
125
122
|
if (!payload?.ok || payload.nodeKey !== options.nodeKey || !payload.computeReference || !payload.projectKey || !payload.environmentKey || payload.realm !== "platform" && (payload.realm !== "tenant" || !payload.tenantSlug)) {
|
|
126
123
|
throw new Error(payload?.error?.message || "guest enrolment response was not accepted");
|
|
127
124
|
}
|
package/dist/node-vault.js
CHANGED
|
@@ -108,19 +108,19 @@ class SignedNodeHttpError extends Error {
|
|
|
108
108
|
this.name = "SignedNodeHttpError";
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
-
async function postSignedNode(options, path, body
|
|
111
|
+
async function postSignedNode(options, path, body) {
|
|
112
112
|
const url = new URL(options.apiUrl);
|
|
113
113
|
url.pathname = `${url.pathname.replace(/\/$/, "")}/${path.replace(/^\//, "")}`.replace(/\/+/g, "/");
|
|
114
114
|
url.search = "";
|
|
115
115
|
url.hash = "";
|
|
116
116
|
const raw = JSON.stringify(body);
|
|
117
|
-
const recipient =
|
|
117
|
+
const recipient = generateResponseRecipient();
|
|
118
118
|
const envelope = signRequest(options.keys, options.nodeKey, {
|
|
119
119
|
method: "POST",
|
|
120
120
|
path: url.pathname,
|
|
121
121
|
query: "",
|
|
122
122
|
body: raw,
|
|
123
|
-
responseKey: recipient
|
|
123
|
+
responseKey: recipient.publicKey
|
|
124
124
|
});
|
|
125
125
|
const signature = encodeSignatureHeader(envelope);
|
|
126
126
|
const response = await (options.fetch ?? globalThis.fetch)(url, {
|
|
@@ -129,7 +129,7 @@ async function postSignedNode(options, path, body, sealedResponse = false) {
|
|
|
129
129
|
"content-type": "application/json",
|
|
130
130
|
"x-fz-node": options.nodeKey,
|
|
131
131
|
"x-fz-signature": signature,
|
|
132
|
-
|
|
132
|
+
[RESPONSE_KEY_HEADER]: recipient.publicKey
|
|
133
133
|
},
|
|
134
134
|
body: raw,
|
|
135
135
|
signal: AbortSignal.timeout(options.requestTimeoutMs ?? 15000)
|
|
@@ -140,14 +140,11 @@ async function postSignedNode(options, path, body, sealedResponse = false) {
|
|
|
140
140
|
const reason = failure ? failure.error?.message ?? failure.message : undefined;
|
|
141
141
|
throw new SignedNodeHttpError(response.status, reason || `signed node request returned HTTP ${response.status}`);
|
|
142
142
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
throw new SignedNodeHttpError(502, "The node response was not sealed to this request.");
|
|
148
|
-
}
|
|
143
|
+
try {
|
|
144
|
+
return await openResponse(recipient.secretKey, signature, payload);
|
|
145
|
+
} catch {
|
|
146
|
+
throw new SignedNodeHttpError(502, "The node response was not sealed to this request.");
|
|
149
147
|
}
|
|
150
|
-
return payload;
|
|
151
148
|
}
|
|
152
149
|
|
|
153
150
|
// src/node-vault.ts
|
|
@@ -179,7 +176,7 @@ function tenantNodeApiUrl(apiUrl, tenantSlug) {
|
|
|
179
176
|
return url.toString().replace(/\/$/, "");
|
|
180
177
|
}
|
|
181
178
|
function createNodeVaultCache(options) {
|
|
182
|
-
const post = (operation, body) => postSignedNode(options, `v1/node/vault/${operation}`, body
|
|
179
|
+
const post = (operation, body) => postSignedNode(options, `v1/node/vault/${operation}`, body);
|
|
183
180
|
return createSecretCache({
|
|
184
181
|
ttlMs: options.ttlMs,
|
|
185
182
|
maxStaleMs: options.maxStaleMs,
|
|
@@ -15,19 +15,19 @@ class SignedNodeHttpError extends Error {
|
|
|
15
15
|
this.name = "SignedNodeHttpError";
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
async function postSignedNode(options, path, body
|
|
18
|
+
async function postSignedNode(options, path, body) {
|
|
19
19
|
const url = new URL(options.apiUrl);
|
|
20
20
|
url.pathname = `${url.pathname.replace(/\/$/, "")}/${path.replace(/^\//, "")}`.replace(/\/+/g, "/");
|
|
21
21
|
url.search = "";
|
|
22
22
|
url.hash = "";
|
|
23
23
|
const raw = JSON.stringify(body);
|
|
24
|
-
const recipient =
|
|
24
|
+
const recipient = generateResponseRecipient();
|
|
25
25
|
const envelope = signRequest(options.keys, options.nodeKey, {
|
|
26
26
|
method: "POST",
|
|
27
27
|
path: url.pathname,
|
|
28
28
|
query: "",
|
|
29
29
|
body: raw,
|
|
30
|
-
responseKey: recipient
|
|
30
|
+
responseKey: recipient.publicKey
|
|
31
31
|
});
|
|
32
32
|
const signature = encodeSignatureHeader(envelope);
|
|
33
33
|
const response = await (options.fetch ?? globalThis.fetch)(url, {
|
|
@@ -36,7 +36,7 @@ async function postSignedNode(options, path, body, sealedResponse = false) {
|
|
|
36
36
|
"content-type": "application/json",
|
|
37
37
|
"x-fz-node": options.nodeKey,
|
|
38
38
|
"x-fz-signature": signature,
|
|
39
|
-
|
|
39
|
+
[RESPONSE_KEY_HEADER]: recipient.publicKey
|
|
40
40
|
},
|
|
41
41
|
body: raw,
|
|
42
42
|
signal: AbortSignal.timeout(options.requestTimeoutMs ?? 15000)
|
|
@@ -47,14 +47,11 @@ async function postSignedNode(options, path, body, sealedResponse = false) {
|
|
|
47
47
|
const reason = failure ? failure.error?.message ?? failure.message : undefined;
|
|
48
48
|
throw new SignedNodeHttpError(response.status, reason || `signed node request returned HTTP ${response.status}`);
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
throw new SignedNodeHttpError(502, "The node response was not sealed to this request.");
|
|
55
|
-
}
|
|
50
|
+
try {
|
|
51
|
+
return await openResponse(recipient.secretKey, signature, payload);
|
|
52
|
+
} catch {
|
|
53
|
+
throw new SignedNodeHttpError(502, "The node response was not sealed to this request.");
|
|
56
54
|
}
|
|
57
|
-
return payload;
|
|
58
55
|
}
|
|
59
56
|
|
|
60
57
|
// src/provisioning-pull.ts
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Server } from 'node:net';
|
|
2
|
+
/**
|
|
3
|
+
* Wait for one shutdown stage without allowing it to consume systemd's entire
|
|
4
|
+
* stop timeout. The work is not cancelled: deployment/lease code retains its
|
|
5
|
+
* own fencing rules, while the caller decides whether an expired global drain
|
|
6
|
+
* deadline must terminate the process non-zero.
|
|
7
|
+
*/
|
|
8
|
+
export declare function settleWithin(work: Promise<unknown>, timeoutMs: number, setTimer?: typeof setTimeout, clearTimer?: typeof clearTimeout): Promise<boolean>;
|
|
9
|
+
/** Stop accepting local Vault clients, but never let one stale socket defeat shutdown. */
|
|
10
|
+
export declare function closeServerWithin(server: Server, timeoutMs: number): Promise<boolean>;
|
|
@@ -11,4 +11,4 @@ export declare class SignedNodeHttpError extends Error {
|
|
|
11
11
|
constructor(status: number, message: string);
|
|
12
12
|
}
|
|
13
13
|
/** One implementation of the hybrid-signed machine HTTP contract. */
|
|
14
|
-
export declare function postSignedNode<T>(options: SignedNodeHttpOptions, path: string, body: object
|
|
14
|
+
export declare function postSignedNode<T>(options: SignedNodeHttpOptions, path: string, body: object): Promise<T>;
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** One package version shared by both public binaries. Pinned to package.json by tests. */
|
|
2
|
-
export declare const VERSION = "0.1.
|
|
2
|
+
export declare const VERSION = "0.1.21";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//": "Publishing happens from an operator's machine, not CI \u2014 CLAUDE.md records that the absence of CI is deliberate. npm's `provenance` attests a tarball was built by a recognised CI provider from a named commit, so it cannot be produced here: it was set, and the first publish failed with `Automatic provenance generation not supported for provider: null`. A setting that can never be satisfied is worse than none, because it reads as a guarantee nobody is getting. Restore it the day this publishes from CI, and not before.",
|
|
3
3
|
"name": "@forgezero/agent",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.21",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"check": "tsc --noEmit",
|