@hivemagnus/magnus-server 1.1.3 → 1.1.5
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/agent-version.json
CHANGED
package/dist/cli/migrations.js
CHANGED
|
@@ -239,17 +239,40 @@ export async function restoreDatabase(options) {
|
|
|
239
239
|
});
|
|
240
240
|
if (listed.code !== 0)
|
|
241
241
|
throw new Error(`${options.file} is not a readable dump: ${(listed.stderr || listed.stdout).trim().split('\n').slice(-2).join(' ')}`);
|
|
242
|
+
// A schema left aside by an earlier restore means that restore was interrupted (or could not drop
|
|
243
|
+
// it): the original data may only exist there, so never swap again on top of it.
|
|
244
|
+
const leftover = await options.client.query("SELECT nspname FROM pg_namespace WHERE nspname LIKE 'magnus\\_pre\\_restore\\_%' ORDER BY nspname");
|
|
245
|
+
if (leftover.rows.length) {
|
|
246
|
+
const names = leftover.rows.map(row => row.nspname).join(', ');
|
|
247
|
+
throw new Error(`an earlier restore left the schema ${names} behind, which may hold the original data. Check what \`public\` holds; `
|
|
248
|
+
+ `to put the set-aside schema back run \`${restoreRevertSql(leftover.rows.at(-1).nspname)}\`, or drop it once you are sure it is not needed, then restore again.`);
|
|
249
|
+
}
|
|
242
250
|
const aside = restoreAsideSchema(options.now ?? new Date());
|
|
243
251
|
await options.client.query(restoreSwapSql(aside));
|
|
244
252
|
log(` moved the current schema aside as ${aside}`);
|
|
253
|
+
// From here on every failure must put the original schema back before it is reported.
|
|
254
|
+
const putBack = async (reason) => {
|
|
255
|
+
try {
|
|
256
|
+
await options.client.query(restoreRevertSql(aside));
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
throw new Error(`${reason}, and putting the original schema back failed too; it is kept as ${aside}: ${error instanceof Error ? error.message : String(error)}`);
|
|
260
|
+
}
|
|
261
|
+
throw new Error(`${reason}; the database is unchanged`);
|
|
262
|
+
};
|
|
245
263
|
const { url, env } = splitPassword(options.databaseUrl);
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
await options.
|
|
249
|
-
throw new Error(`pg_restore failed and putting the original schema back failed too; it is kept as ${aside}: ${error instanceof Error ? error.message : String(error)}`);
|
|
250
|
-
});
|
|
251
|
-
throw new Error(`pg_restore failed (exit ${result.code}); the database is unchanged: ${result.stderr.trim().split('\n').slice(-3).join(' ')}`);
|
|
264
|
+
let result;
|
|
265
|
+
try {
|
|
266
|
+
result = await run(pgRestore, ['--single-transaction', '--exit-on-error', '--no-owner', '--dbname', url, options.file], { capture: true, env: { ...process.env, ...env } });
|
|
252
267
|
}
|
|
253
|
-
|
|
268
|
+
catch (error) {
|
|
269
|
+
return putBack(`${pgRestore} could not be run (${error instanceof Error ? error.message : String(error)})`);
|
|
270
|
+
}
|
|
271
|
+
if (result.code !== 0)
|
|
272
|
+
return putBack(`pg_restore failed (exit ${result.code}): ${result.stderr.trim().split('\n').slice(-3).join(' ')}`);
|
|
273
|
+
// The restore committed; failing to drop the old copy must not report the restore as failed.
|
|
274
|
+
await options.client.query(restoreFinishSql(aside)).catch((error) => {
|
|
275
|
+
log(` warning: the restore succeeded but the previous schema ${aside} could not be dropped (${error instanceof Error ? error.message : String(error)}); drop it with \`${restoreFinishSql(aside)}\` once you have checked the restored data`);
|
|
276
|
+
});
|
|
254
277
|
}
|
|
255
278
|
//# sourceMappingURL=migrations.js.map
|
|
@@ -163,10 +163,12 @@ export const PARTICIPANT_RECEIPT_LIMITS = { default: 16_384, scheduler: 256 * 10
|
|
|
163
163
|
export class ParticipantEvidenceError extends Error {
|
|
164
164
|
code;
|
|
165
165
|
agentCode;
|
|
166
|
-
|
|
166
|
+
agentReason;
|
|
167
|
+
constructor(code, agentCode = null, agentReason = null) {
|
|
167
168
|
super(code);
|
|
168
169
|
this.code = code;
|
|
169
170
|
this.agentCode = agentCode;
|
|
171
|
+
this.agentReason = agentReason;
|
|
170
172
|
}
|
|
171
173
|
}
|
|
172
174
|
const failures = new Map();
|
|
@@ -177,20 +179,24 @@ export function lastParticipantFailures(nodeId) {
|
|
|
177
179
|
/** Bounded response consumption, including dishonest or absent content-length. */
|
|
178
180
|
export async function readParticipantReceipt(response, limit = PARTICIPANT_RECEIPT_LIMITS.default) {
|
|
179
181
|
if (response.status !== 200 || !response.body) {
|
|
180
|
-
// An optional machine-readable code from the participant
|
|
182
|
+
// An optional machine-readable code and fixed reason from the participant
|
|
183
|
+
// (`{ error: { code, reason } }`) are kept; nothing else is.
|
|
181
184
|
let agentCode = null;
|
|
185
|
+
let agentReason = null;
|
|
182
186
|
if (response.body && response.status >= 400) {
|
|
183
187
|
const text = await response.text().catch(() => '');
|
|
184
188
|
try {
|
|
185
|
-
const
|
|
186
|
-
if (typeof code === 'string' && /^[A-Z][A-Z0-9_]{0,63}$/.test(code))
|
|
187
|
-
agentCode = code;
|
|
189
|
+
const error = JSON.parse(text.slice(0, 4096))?.error;
|
|
190
|
+
if (typeof error?.code === 'string' && /^[A-Z][A-Z0-9_]{0,63}$/.test(error.code))
|
|
191
|
+
agentCode = error.code;
|
|
192
|
+
if (typeof error?.reason === 'string' && /^[a-z][a-z0-9-]{0,47}$/.test(error.reason))
|
|
193
|
+
agentReason = error.reason;
|
|
188
194
|
}
|
|
189
195
|
catch { /* no code */ }
|
|
190
196
|
}
|
|
191
197
|
else
|
|
192
198
|
await response.body?.cancel().catch(() => undefined);
|
|
193
|
-
throw new ParticipantEvidenceError(`participant-http-${response.status}`, agentCode);
|
|
199
|
+
throw new ParticipantEvidenceError(`participant-http-${response.status}`, agentCode, agentReason);
|
|
194
200
|
}
|
|
195
201
|
const reader = response.body.getReader();
|
|
196
202
|
const chunks = [];
|
|
@@ -280,8 +286,9 @@ export async function refreshEnrollmentParticipant(nodeId, participant) {
|
|
|
280
286
|
// Outstanding challenge remains pending.
|
|
281
287
|
const cause = error instanceof ParticipantEvidenceError ? error.code : 'participant-evidence-unavailable';
|
|
282
288
|
const agentCode = error instanceof ParticipantEvidenceError ? error.agentCode : null;
|
|
283
|
-
|
|
284
|
-
|
|
289
|
+
const agentReason = error instanceof ParticipantEvidenceError ? error.agentReason : null;
|
|
290
|
+
failures.set(`${nodeId}\n${participant}`, { participant, cause, agentCode, agentReason, at: new Date().toISOString() });
|
|
291
|
+
return { participant, status: 'pending', reason: 'participant-evidence-unavailable', cause, agentCode, agentReason };
|
|
285
292
|
}
|
|
286
293
|
failures.delete(`${nodeId}\n${participant}`);
|
|
287
294
|
return getDb().$transaction(async (tx) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hivemagnus/magnus-server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "Magnus server: the fleet control plane API (Hono, PostgreSQL, Redis) for nodes running magnus-agent, with optional Hive IAM, Hive Router, HiveMem, Hermes Agent and external security console integrations.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"magnus",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"prepublishOnly": "npm run db:generate && npm run clean && npm run build"
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
|
-
"@hivemagnus/magnus-contracts": "1.1.
|
|
87
|
+
"@hivemagnus/magnus-contracts": "1.1.5",
|
|
88
88
|
"@hono/node-server": "^2.1.5",
|
|
89
89
|
"@hono/swagger-ui": "^0.6.2",
|
|
90
90
|
"@prisma/adapter-pg": "7.10.1",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"ws": "^8.22.0",
|
|
103
103
|
"yaml": "2.9.1",
|
|
104
104
|
"zod": "^4.6.7",
|
|
105
|
-
"@hivemagnus/magnus-skills": "1.1.
|
|
105
|
+
"@hivemagnus/magnus-skills": "1.1.5",
|
|
106
106
|
"prisma": "^7.10.1"
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|