@aopslabs/aops 0.3.24 → 0.3.26
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-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/SKILL.md +0 -1
- package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/agentspace/SKILL.md +0 -6
- package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/collaborative-work/SKILL.md +1 -2
- package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/working-disciplines/SKILL.md +2 -5
- package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/agentspace.md +1 -16
- package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/aops-cli.md +4 -33
- package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/working-disciplines.md +32 -138
- package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/manifest.json +17 -22
- package/agent-assets-release/agent-assets/inventory.json +5 -5
- package/agent-assets-release/agent-assets/projection.json +1 -1
- package/agent-assets-release/agent-assets-release.json +48 -53
- package/agent-assets-release/agent-assets-release.sigstore.json +27 -28
- package/assets/agent-assets/core/SKILL.md +0 -1
- package/assets/agent-assets/core/references/agentspace/SKILL.md +0 -6
- package/assets/agent-assets/core/references/collaborative-work/SKILL.md +1 -2
- package/assets/agent-assets/core/references/working-disciplines/SKILL.md +2 -5
- package/assets/agent-assets/core/user-guides/agentspace.md +1 -16
- package/assets/agent-assets/core/user-guides/aops-cli.md +4 -33
- package/assets/agent-assets/core/user-guides/working-disciplines.md +32 -138
- package/dist/commands/checkpoint.js +4 -23
- package/dist/commands/community-server.js +212 -6
- package/dist/commands/db.js +145 -0
- package/dist/commands/experience.js +0 -2
- package/dist/commands/memory.js +8 -2
- package/dist/commands/official-catalog.js +1 -1
- package/dist/commands/plan.js +2 -2
- package/dist/commands/pm/index.js +39 -3
- package/dist/commands/pm/projectman.js +281 -8
- package/dist/commands/start-disciplines.js +8 -21
- package/dist/commands/start.js +20 -504
- package/dist/lib/agent-assets/native-fs.js +2 -2
- package/dist/lib/community-migration-snapshot.js +43 -2
- package/dist/lib/community-native-backup.js +386 -0
- package/dist/lib/community-native-lifecycle.js +93 -4
- package/dist/lib/community-native-migration.js +20 -0
- package/dist/main.js +2 -2
- package/dist/seeds/agents-md.js +1 -1
- package/dist/utils/playbook-workspace.js +5 -33
- package/dist/utils/session-guidance.js +0 -7
- package/native/bin/darwin-arm64/aops-agent-assets-fs +0 -0
- package/native/bin/darwin-x64/aops-agent-assets-fs +0 -0
- package/native/bin/win32-x64/aops-agent-assets-fs.exe +0 -0
- package/native/manifest.json +4 -4
- package/native/qualifications/win32-x64.json +2 -2
- package/native/tui/darwin-arm64/aops-tui +0 -0
- package/native/tui/linux-x64/aops-tui +0 -0
- package/native/tui/win32-x64/aops-tui.exe +0 -0
- package/package.json +18 -6
- package/agent-assets-release/agent-assets/core/647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09/files/references/mission/SKILL.md +0 -54
- package/assets/agent-assets/core/references/mission/SKILL.md +0 -54
- package/dist/commands/mission.js +0 -562
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/aops-cli-core/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/chatv3/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/discuss/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/docman/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/projectman/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/sys/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/references/view/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/agent-assets.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/aops-system.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/chatv3.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/docman.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/projectman.md +0 -0
- /package/agent-assets-release/agent-assets/core/{647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09 → bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb}/files/user-guides/sys.md +0 -0
|
@@ -543,9 +543,9 @@ function defaultNativeRoot() {
|
|
|
543
543
|
return moduleDirectory.endsWith(`${path.sep}dist`) ? bundled : compiledOrSource;
|
|
544
544
|
}
|
|
545
545
|
function injectedNativeManifestSha256() {
|
|
546
|
-
return typeof "
|
|
546
|
+
return typeof "d079a8a4ff6a05567912c963ac82f52259e980fafc22a041e4934932cdf7c2dd" === 'undefined'
|
|
547
547
|
? undefined
|
|
548
|
-
: "
|
|
548
|
+
: "d079a8a4ff6a05567912c963ac82f52259e980fafc22a041e4934932cdf7c2dd";
|
|
549
549
|
}
|
|
550
550
|
function sha256(bytes) {
|
|
551
551
|
return createHash('sha256').update(bytes).digest('hex');
|
|
@@ -265,6 +265,33 @@ export function readCommunityNativeSnapshotEvidenceV1(params) {
|
|
|
265
265
|
}
|
|
266
266
|
return { evidencePath, evidenceSha256, evidence };
|
|
267
267
|
}
|
|
268
|
+
export function assertBackupUnchanged(params) {
|
|
269
|
+
const reread = hashHeldFile(params.heldFd, false);
|
|
270
|
+
if (reread.sha256 !== params.expectedSha256 || reread.byteLength !== params.expectedByteLength) {
|
|
271
|
+
throw new Error('community_native_snapshot_backup_identity_changed');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
export function openBackupForVerify(params) {
|
|
275
|
+
const noFollow = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
|
|
276
|
+
const verifyFd = openSync(params.backupPath, constants.O_RDONLY | noFollow);
|
|
277
|
+
try {
|
|
278
|
+
const written = fstatSync(params.writtenFd, { bigint: true });
|
|
279
|
+
const opened = fstatSync(verifyFd, { bigint: true });
|
|
280
|
+
if (!opened.isFile() || opened.nlink !== 1n ||
|
|
281
|
+
opened.dev !== written.dev || opened.ino !== written.ino ||
|
|
282
|
+
opened.size !== BigInt(params.expectedByteLength)) {
|
|
283
|
+
throw new Error('community_native_snapshot_backup_identity_changed');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
try {
|
|
288
|
+
closeSync(verifyFd);
|
|
289
|
+
}
|
|
290
|
+
catch { }
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
return verifyFd;
|
|
294
|
+
}
|
|
268
295
|
async function runDocker(params) {
|
|
269
296
|
const result = await params.runtime.run({
|
|
270
297
|
command: 'docker',
|
|
@@ -298,7 +325,7 @@ async function runDockerCapture(params) {
|
|
|
298
325
|
}
|
|
299
326
|
return result.stdout;
|
|
300
327
|
}
|
|
301
|
-
function hashHeldFile(fd, syncBeforeRead = true) {
|
|
328
|
+
export function hashHeldFile(fd, syncBeforeRead = true) {
|
|
302
329
|
if (syncBeforeRead)
|
|
303
330
|
fsyncSync(fd);
|
|
304
331
|
const before = fstatSync(fd, { bigint: true });
|
|
@@ -473,13 +500,18 @@ export async function createCommunityNativeManagedSnapshotV1(params) {
|
|
|
473
500
|
operation: 'verify_create',
|
|
474
501
|
signal: params.signal,
|
|
475
502
|
});
|
|
503
|
+
const verifyFd = openBackupForVerify({
|
|
504
|
+
backupPath,
|
|
505
|
+
writtenFd: backupFd,
|
|
506
|
+
expectedByteLength: backup.byteLength,
|
|
507
|
+
});
|
|
476
508
|
try {
|
|
477
509
|
await runDocker({
|
|
478
510
|
runtime,
|
|
479
511
|
args: ['container', 'exec', '-i', state.containerName, 'pg_restore', '-U', 'aops',
|
|
480
512
|
'-d', verifyDatabase, '--no-owner', '--no-acl', '--exit-on-error'],
|
|
481
513
|
operation: 'verify_restore',
|
|
482
|
-
inputFd:
|
|
514
|
+
inputFd: verifyFd,
|
|
483
515
|
signal: params.signal,
|
|
484
516
|
});
|
|
485
517
|
const restored = await (params.planStrictPgSchema ?? planCommunityStrictPgSchema)({
|
|
@@ -490,6 +522,10 @@ export async function createCommunityNativeManagedSnapshotV1(params) {
|
|
|
490
522
|
assertRestoredState(params.planning, restored);
|
|
491
523
|
}
|
|
492
524
|
finally {
|
|
525
|
+
try {
|
|
526
|
+
closeSync(verifyFd);
|
|
527
|
+
}
|
|
528
|
+
catch { }
|
|
493
529
|
await runDocker({
|
|
494
530
|
runtime,
|
|
495
531
|
args: ['container', 'exec', state.containerName, 'dropdb', '-U', 'aops', '--if-exists', '--force', verifyDatabase],
|
|
@@ -497,6 +533,11 @@ export async function createCommunityNativeManagedSnapshotV1(params) {
|
|
|
497
533
|
signal: params.signal,
|
|
498
534
|
});
|
|
499
535
|
}
|
|
536
|
+
assertBackupUnchanged({
|
|
537
|
+
heldFd: backupFd,
|
|
538
|
+
expectedSha256: backup.sha256,
|
|
539
|
+
expectedByteLength: backup.byteLength,
|
|
540
|
+
});
|
|
500
541
|
const dataFingerprint = sourceDataFingerprint(params.planning);
|
|
501
542
|
const evidence = {
|
|
502
543
|
schemaVersion: 1,
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { closeSync, constants, existsSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { assertBackupUnchanged, hashHeldFile, openBackupForVerify } from './community-migration-snapshot.js';
|
|
6
|
+
export async function runNativePgProcess(params) {
|
|
7
|
+
return await new Promise((resolve, reject) => {
|
|
8
|
+
const child = spawn(params.tool, params.args, {
|
|
9
|
+
env: params.env ? { ...process.env, ...params.env } : process.env,
|
|
10
|
+
stdio: [
|
|
11
|
+
params.inputFd === undefined ? 'ignore' : params.inputFd,
|
|
12
|
+
params.outputFd === undefined ? 'ignore' : params.outputFd,
|
|
13
|
+
'inherit',
|
|
14
|
+
],
|
|
15
|
+
shell: false,
|
|
16
|
+
});
|
|
17
|
+
child.on('error', reject);
|
|
18
|
+
child.on('close', (exitCode, signal) => resolve({ exitCode, signal }));
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export function pgEnvironmentFromUrl(repoUrl) {
|
|
22
|
+
let parsed;
|
|
23
|
+
try {
|
|
24
|
+
parsed = new URL(repoUrl);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new Error('community_native_backup_repo_url_invalid');
|
|
28
|
+
}
|
|
29
|
+
if (parsed.protocol !== 'postgres:' && parsed.protocol !== 'postgresql:') {
|
|
30
|
+
throw new Error('community_native_backup_repo_url_invalid');
|
|
31
|
+
}
|
|
32
|
+
const database = decodeURIComponent(parsed.pathname.replace(/^\//, ''));
|
|
33
|
+
if (!database)
|
|
34
|
+
throw new Error('community_native_backup_repo_url_invalid');
|
|
35
|
+
const env = {
|
|
36
|
+
PGHOST: parsed.hostname,
|
|
37
|
+
PGDATABASE: database,
|
|
38
|
+
};
|
|
39
|
+
if (parsed.port)
|
|
40
|
+
env.PGPORT = parsed.port;
|
|
41
|
+
if (parsed.username)
|
|
42
|
+
env.PGUSER = decodeURIComponent(parsed.username);
|
|
43
|
+
if (parsed.password)
|
|
44
|
+
env.PGPASSWORD = decodeURIComponent(parsed.password);
|
|
45
|
+
const sslmode = parsed.searchParams.get('sslmode');
|
|
46
|
+
if (sslmode)
|
|
47
|
+
env.PGSSLMODE = sslmode;
|
|
48
|
+
return env;
|
|
49
|
+
}
|
|
50
|
+
export function resolvePgTool(tool, options = {}) {
|
|
51
|
+
const exists = options.exists ?? existsSync;
|
|
52
|
+
const executable = process.platform === 'win32' ? `${tool}.exe` : tool;
|
|
53
|
+
if (options.explicit)
|
|
54
|
+
return options.explicit;
|
|
55
|
+
if (options.binDir)
|
|
56
|
+
return path.join(options.binDir, executable);
|
|
57
|
+
const candidates = process.platform === 'darwin'
|
|
58
|
+
? ['/Applications/pgAdmin 4.app/Contents/SharedSupport', '/opt/homebrew/bin', '/usr/local/bin']
|
|
59
|
+
: process.platform === 'win32'
|
|
60
|
+
? ['C:\\Program Files\\PostgreSQL\\17\\bin', 'C:\\Program Files\\PostgreSQL\\16\\bin']
|
|
61
|
+
: ['/usr/bin', '/usr/local/bin'];
|
|
62
|
+
for (const dir of candidates) {
|
|
63
|
+
const candidate = path.join(dir, executable);
|
|
64
|
+
if (exists(candidate))
|
|
65
|
+
return candidate;
|
|
66
|
+
}
|
|
67
|
+
return executable;
|
|
68
|
+
}
|
|
69
|
+
function assertSafeBackupRoot(backupRoot) {
|
|
70
|
+
mkdirSync(backupRoot, { recursive: true, mode: 0o700 });
|
|
71
|
+
const stats = lstatSync(backupRoot, { bigint: true });
|
|
72
|
+
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
73
|
+
throw new Error('community_backup_root_unsafe');
|
|
74
|
+
}
|
|
75
|
+
return realpathSync.native(backupRoot);
|
|
76
|
+
}
|
|
77
|
+
export function verifyHeldBackupAgainstReceipt(params) {
|
|
78
|
+
const hashed = hashHeldFile(params.heldFd, false);
|
|
79
|
+
if (hashed.byteLength !== params.receipt.byteLength) {
|
|
80
|
+
throw new Error('community_native_backup_size_mismatch');
|
|
81
|
+
}
|
|
82
|
+
if (hashed.sha256 !== params.receipt.sha256) {
|
|
83
|
+
throw new Error('community_native_backup_digest_mismatch');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export function planSchemaRewind(params) {
|
|
87
|
+
const inArchive = new Set([...params.archiveSchemas, 'public']);
|
|
88
|
+
const orphaned = params.databaseSchemas.filter((name) => !inArchive.has(name));
|
|
89
|
+
if (orphaned.length > 0) {
|
|
90
|
+
throw new Error(`community_native_restore_schema_not_in_backup:${orphaned.join(',')}:` +
|
|
91
|
+
'dropping_would_lose_them_keeping_would_leave_a_partial_rewind');
|
|
92
|
+
}
|
|
93
|
+
const drop = new Set(params.databaseSchemas);
|
|
94
|
+
drop.add('public');
|
|
95
|
+
return { drop: [...drop].sort() };
|
|
96
|
+
}
|
|
97
|
+
export function quoteSchemaIdentifier(name) {
|
|
98
|
+
if (name === '' || name.includes('\u0000')) {
|
|
99
|
+
throw new Error('community_native_restore_schema_name_unrepresentable');
|
|
100
|
+
}
|
|
101
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
102
|
+
}
|
|
103
|
+
export function parseArchiveSchemas(pgRestoreListOutput) {
|
|
104
|
+
const names = new Set();
|
|
105
|
+
for (const line of pgRestoreListOutput.split('\n')) {
|
|
106
|
+
const match = /\bSCHEMA\s+-\s+(\S+)/.exec(line);
|
|
107
|
+
if (match?.[1])
|
|
108
|
+
names.add(match[1]);
|
|
109
|
+
}
|
|
110
|
+
return [...names].sort();
|
|
111
|
+
}
|
|
112
|
+
export async function resolveSchemaRewindPlan(params) {
|
|
113
|
+
const pgEnv = pgEnvironmentFromUrl(params.repoUrl);
|
|
114
|
+
const capture = params.captureOutput ?? defaultCaptureOutput;
|
|
115
|
+
const query = "select nspname from pg_namespace where nspname not in ('information_schema','pg_catalog','pg_toast') and nspname not like 'pg_temp%' and nspname not like 'pg_toast_temp%'";
|
|
116
|
+
const databaseSchemas = (await capture({
|
|
117
|
+
tool: resolvePgTool('psql', { explicit: params.psql, binDir: params.pgBinDir }),
|
|
118
|
+
args: ['--dbname', String(pgEnv.PGDATABASE), '--tuples-only', '--no-align', '--quiet', '--command', query],
|
|
119
|
+
env: pgEnv,
|
|
120
|
+
}))
|
|
121
|
+
.split('\n')
|
|
122
|
+
.map((line) => line.trim())
|
|
123
|
+
.filter(Boolean);
|
|
124
|
+
const archiveSchemas = parseArchiveSchemas(await capture(params.archiveFd === undefined
|
|
125
|
+
? {
|
|
126
|
+
tool: resolvePgTool('pg_restore', { explicit: params.pgRestore, binDir: params.pgBinDir }),
|
|
127
|
+
args: ['--list', params.archivePath],
|
|
128
|
+
}
|
|
129
|
+
: {
|
|
130
|
+
tool: resolvePgTool('pg_restore', { explicit: params.pgRestore, binDir: params.pgBinDir }),
|
|
131
|
+
args: ['--list'],
|
|
132
|
+
inputFd: params.archiveFd,
|
|
133
|
+
}));
|
|
134
|
+
return planSchemaRewind({ databaseSchemas, archiveSchemas });
|
|
135
|
+
}
|
|
136
|
+
async function defaultCaptureOutput(params) {
|
|
137
|
+
return await new Promise((resolve, reject) => {
|
|
138
|
+
const child = spawn(params.tool, params.args, {
|
|
139
|
+
stdio: [params.inputFd ?? 'ignore', 'pipe', 'inherit'],
|
|
140
|
+
shell: false,
|
|
141
|
+
env: params.env ? { ...process.env, ...params.env } : process.env,
|
|
142
|
+
});
|
|
143
|
+
let out = '';
|
|
144
|
+
const stdout = child.stdout;
|
|
145
|
+
if (!stdout) {
|
|
146
|
+
reject(new Error('community_native_restore_schema_probe_no_output'));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
stdout.setEncoding('utf8');
|
|
150
|
+
stdout.on('data', (chunk) => { out += chunk; });
|
|
151
|
+
child.on('error', reject);
|
|
152
|
+
child.on('close', (code) => {
|
|
153
|
+
if (code !== 0)
|
|
154
|
+
reject(new Error(`community_native_restore_schema_probe_failed:exit_${String(code)}`));
|
|
155
|
+
else
|
|
156
|
+
resolve(out);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function hashBackupAtPath(backupPath) {
|
|
161
|
+
const fd = openSync(backupPath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
162
|
+
try {
|
|
163
|
+
return hashHeldFile(fd, false).sha256;
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
try {
|
|
167
|
+
closeSync(fd);
|
|
168
|
+
}
|
|
169
|
+
catch { }
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
export async function verifyCommunityNativeBackupV1(params) {
|
|
173
|
+
const readText = params.readFileText ?? ((candidate) => readFileSync(candidate, 'utf8'));
|
|
174
|
+
const receiptPath = `${params.backupPath}.json`;
|
|
175
|
+
if (!existsSync(receiptPath))
|
|
176
|
+
throw new Error('community_native_backup_receipt_missing');
|
|
177
|
+
let record;
|
|
178
|
+
try {
|
|
179
|
+
record = JSON.parse(readText(receiptPath));
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
throw new Error('community_native_backup_receipt_unreadable');
|
|
183
|
+
}
|
|
184
|
+
if (record?.kind !== 'native-pg-custom' || record.schemaVersion !== 1 || record.verified !== true) {
|
|
185
|
+
throw new Error('community_native_backup_receipt_invalid');
|
|
186
|
+
}
|
|
187
|
+
if (record.verifiedBy !== undefined && record.verifiedBy !== 'pg-restore-list') {
|
|
188
|
+
throw new Error('community_native_backup_receipt_unknown_verification_depth');
|
|
189
|
+
}
|
|
190
|
+
if (path.resolve(String(record.path)) !== path.resolve(params.backupPath)) {
|
|
191
|
+
throw new Error('community_native_backup_receipt_path_mismatch');
|
|
192
|
+
}
|
|
193
|
+
const stats = params.statFile ? params.statFile(params.backupPath) : statSync(params.backupPath);
|
|
194
|
+
if (stats.size !== record.byteLength)
|
|
195
|
+
throw new Error('community_native_backup_size_mismatch');
|
|
196
|
+
const digest = params.hashFile ? params.hashFile(params.backupPath) : hashBackupAtPath(params.backupPath);
|
|
197
|
+
if (digest !== record.sha256)
|
|
198
|
+
throw new Error('community_native_backup_digest_mismatch');
|
|
199
|
+
return record;
|
|
200
|
+
}
|
|
201
|
+
export async function createCommunityNativeBackupV1(params) {
|
|
202
|
+
const run = params.runProcess ?? runNativePgProcess;
|
|
203
|
+
const now = params.now ?? (() => new Date());
|
|
204
|
+
const backupRoot = assertSafeBackupRoot(params.backupRoot);
|
|
205
|
+
const label = params.label ? `-${params.label.replace(/[^A-Za-z0-9._-]/g, '_').slice(0, 64)}` : '';
|
|
206
|
+
const backupPath = path.join(backupRoot, `${now().toISOString().replace(/[:.]/g, '-')}${label}-${randomUUID()}.dump`);
|
|
207
|
+
const noFollow = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
|
|
208
|
+
const backupFd = openSync(backupPath, constants.O_CREAT | constants.O_EXCL | constants.O_RDWR | noFollow, 0o600);
|
|
209
|
+
let keep = false;
|
|
210
|
+
try {
|
|
211
|
+
const pgEnv = pgEnvironmentFromUrl(params.repoUrl);
|
|
212
|
+
const dump = await run({
|
|
213
|
+
tool: resolvePgTool('pg_dump', { explicit: params.pgDump, binDir: params.pgBinDir }),
|
|
214
|
+
args: ['--format=custom', '--no-owner', '--no-acl'],
|
|
215
|
+
outputFd: backupFd,
|
|
216
|
+
env: pgEnv,
|
|
217
|
+
});
|
|
218
|
+
if (dump.exitCode !== 0) {
|
|
219
|
+
throw new Error(`community_native_backup_dump_failed:exit_${String(dump.exitCode ?? 'null')}`);
|
|
220
|
+
}
|
|
221
|
+
const hashed = hashHeldFile(backupFd);
|
|
222
|
+
const verifyFd = openBackupForVerify({
|
|
223
|
+
backupPath,
|
|
224
|
+
writtenFd: backupFd,
|
|
225
|
+
expectedByteLength: hashed.byteLength,
|
|
226
|
+
});
|
|
227
|
+
try {
|
|
228
|
+
const verify = await run({
|
|
229
|
+
tool: resolvePgTool('pg_restore', { explicit: params.pgRestore, binDir: params.pgBinDir }),
|
|
230
|
+
args: ['--list'],
|
|
231
|
+
inputFd: verifyFd,
|
|
232
|
+
});
|
|
233
|
+
if (verify.exitCode !== 0) {
|
|
234
|
+
throw new Error(`community_native_backup_verify_failed:exit_${String(verify.exitCode ?? 'null')}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
finally {
|
|
238
|
+
try {
|
|
239
|
+
closeSync(verifyFd);
|
|
240
|
+
}
|
|
241
|
+
catch { }
|
|
242
|
+
}
|
|
243
|
+
assertBackupUnchanged({
|
|
244
|
+
heldFd: backupFd,
|
|
245
|
+
expectedSha256: hashed.sha256,
|
|
246
|
+
expectedByteLength: hashed.byteLength,
|
|
247
|
+
});
|
|
248
|
+
const record = {
|
|
249
|
+
schemaVersion: 1,
|
|
250
|
+
kind: 'native-pg-custom',
|
|
251
|
+
path: backupPath,
|
|
252
|
+
sha256: hashed.sha256,
|
|
253
|
+
byteLength: hashed.byteLength,
|
|
254
|
+
verified: true,
|
|
255
|
+
verifiedBy: 'pg-restore-list',
|
|
256
|
+
createdAt: now().toISOString(),
|
|
257
|
+
instanceName: params.instanceName,
|
|
258
|
+
releaseVersion: params.releaseVersion ?? null,
|
|
259
|
+
};
|
|
260
|
+
const receiptPath = `${backupPath}.json`;
|
|
261
|
+
if (existsSync(receiptPath))
|
|
262
|
+
throw new Error('community_native_backup_receipt_already_exists');
|
|
263
|
+
writeFileSync(receiptPath, `${JSON.stringify(record, null, 2)}\n`, { mode: 0o600, flag: 'wx' });
|
|
264
|
+
keep = true;
|
|
265
|
+
return { record, receiptPath };
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
try {
|
|
269
|
+
closeSync(backupFd);
|
|
270
|
+
}
|
|
271
|
+
catch { }
|
|
272
|
+
if (!keep) {
|
|
273
|
+
try {
|
|
274
|
+
const { unlinkSync } = await import('node:fs');
|
|
275
|
+
unlinkSync(backupPath);
|
|
276
|
+
}
|
|
277
|
+
catch { }
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
export async function restoreCommunityNativeBackupV1(params) {
|
|
282
|
+
if (params.confirmDataRewind !== true) {
|
|
283
|
+
throw new Error('community_native_restore_confirmation_required:pass_--confirm-data-rewind');
|
|
284
|
+
}
|
|
285
|
+
if (params.skipPreRestoreBackup === true && params.confirmNoUndo !== true) {
|
|
286
|
+
throw new Error('community_native_restore_no_undo_confirmation_required:pass_--confirm-no-undo');
|
|
287
|
+
}
|
|
288
|
+
const run = params.runProcess ?? runNativePgProcess;
|
|
289
|
+
const receiptPath = `${params.backupPath}.json`;
|
|
290
|
+
if (!existsSync(receiptPath))
|
|
291
|
+
throw new Error('community_native_backup_receipt_missing');
|
|
292
|
+
let record;
|
|
293
|
+
try {
|
|
294
|
+
record = JSON.parse(readFileSync(receiptPath, 'utf8'));
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
throw new Error('community_native_backup_receipt_unreadable');
|
|
298
|
+
}
|
|
299
|
+
if (record?.kind !== 'native-pg-custom' || record.schemaVersion !== 1 || record.verified !== true) {
|
|
300
|
+
throw new Error('community_native_backup_receipt_invalid');
|
|
301
|
+
}
|
|
302
|
+
if (path.resolve(String(record.path)) !== path.resolve(params.backupPath)) {
|
|
303
|
+
throw new Error('community_native_backup_receipt_path_mismatch');
|
|
304
|
+
}
|
|
305
|
+
const noFollow = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
|
|
306
|
+
const archiveFd = openSync(params.backupPath, constants.O_RDONLY | noFollow);
|
|
307
|
+
let preRestoreBackup = null;
|
|
308
|
+
try {
|
|
309
|
+
const pgEnv = pgEnvironmentFromUrl(params.repoUrl);
|
|
310
|
+
const probeFd = openBackupForVerify({
|
|
311
|
+
backupPath: params.backupPath,
|
|
312
|
+
writtenFd: archiveFd,
|
|
313
|
+
expectedByteLength: record.byteLength,
|
|
314
|
+
});
|
|
315
|
+
let rewindPlan;
|
|
316
|
+
try {
|
|
317
|
+
rewindPlan = await resolveSchemaRewindPlan({
|
|
318
|
+
repoUrl: params.repoUrl,
|
|
319
|
+
archivePath: params.backupPath,
|
|
320
|
+
archiveFd: probeFd,
|
|
321
|
+
pgBinDir: params.pgBinDir,
|
|
322
|
+
psql: params.psql,
|
|
323
|
+
pgRestore: params.pgRestore,
|
|
324
|
+
captureOutput: params.captureSchemaProbe,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
finally {
|
|
328
|
+
try {
|
|
329
|
+
closeSync(probeFd);
|
|
330
|
+
}
|
|
331
|
+
catch { }
|
|
332
|
+
}
|
|
333
|
+
if (params.skipPreRestoreBackup !== true) {
|
|
334
|
+
const taken = await createCommunityNativeBackupV1({
|
|
335
|
+
backupRoot: params.backupRoot,
|
|
336
|
+
repoUrl: params.repoUrl,
|
|
337
|
+
instanceName: params.instanceName,
|
|
338
|
+
releaseVersion: params.releaseVersion ?? null,
|
|
339
|
+
pgBinDir: params.pgBinDir,
|
|
340
|
+
pgDump: params.pgDump,
|
|
341
|
+
pgRestore: params.pgRestore,
|
|
342
|
+
now: params.now,
|
|
343
|
+
runProcess: params.runProcess,
|
|
344
|
+
});
|
|
345
|
+
preRestoreBackup = {
|
|
346
|
+
path: taken.record.path,
|
|
347
|
+
sha256: taken.record.sha256,
|
|
348
|
+
byteLength: taken.record.byteLength,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
verifyHeldBackupAgainstReceipt({ heldFd: archiveFd, receipt: record });
|
|
352
|
+
const rewind = await run({
|
|
353
|
+
tool: resolvePgTool('psql', { explicit: params.psql, binDir: params.pgBinDir }),
|
|
354
|
+
args: [
|
|
355
|
+
'--dbname', String(pgEnv.PGDATABASE),
|
|
356
|
+
'--set', 'ON_ERROR_STOP=1',
|
|
357
|
+
'--quiet',
|
|
358
|
+
'--command',
|
|
359
|
+
`${rewindPlan.drop.map((name) => `DROP SCHEMA IF EXISTS ${quoteSchemaIdentifier(name)} CASCADE;`).join(' ')} CREATE SCHEMA public;`,
|
|
360
|
+
],
|
|
361
|
+
env: pgEnv,
|
|
362
|
+
});
|
|
363
|
+
if (rewind.exitCode !== 0) {
|
|
364
|
+
throw new Error(`community_native_restore_rewind_failed:exit_${String(rewind.exitCode ?? 'null')}`);
|
|
365
|
+
}
|
|
366
|
+
const restore = await run({
|
|
367
|
+
tool: resolvePgTool('pg_restore', { explicit: params.pgRestore, binDir: params.pgBinDir }),
|
|
368
|
+
args: [
|
|
369
|
+
'--dbname', String(pgEnv.PGDATABASE),
|
|
370
|
+
'--no-owner', '--no-acl', '--exit-on-error',
|
|
371
|
+
],
|
|
372
|
+
inputFd: archiveFd,
|
|
373
|
+
env: pgEnv,
|
|
374
|
+
});
|
|
375
|
+
if (restore.exitCode !== 0) {
|
|
376
|
+
throw new Error(`community_native_restore_failed:exit_${String(restore.exitCode ?? 'null')}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
finally {
|
|
380
|
+
try {
|
|
381
|
+
closeSync(archiveFd);
|
|
382
|
+
}
|
|
383
|
+
catch { }
|
|
384
|
+
}
|
|
385
|
+
return { restored: record, preRestoreBackup };
|
|
386
|
+
}
|
|
@@ -10,7 +10,8 @@ import { resolveCommunityInstallPaths } from './community-lifecycle.js';
|
|
|
10
10
|
import { buildCommunityInstanceContract, } from './community-instance-contract.js';
|
|
11
11
|
import { COMMUNITY_NATIVE_POSTGRES_CONTRACT_PATH, assertCommunityNativePostgresInstanceState, assertCommunityNativePostgresState, buildCommunityNativePostgresUrl, setupCommunityNativePostgres, startCommunityNativePostgres, } from './community-native-postgres.js';
|
|
12
12
|
import { COMMUNITY_NATIVE_MIGRATION_POLICY_PATH, assertCommunityNativeMigrationReceiptV1, planCommunityNativeMigration, runCommunityNativeMigration, } from './community-native-migration.js';
|
|
13
|
-
import { createCommunityExternalSnapshotAttestationV1, writeCommunityExternalSnapshotAttestationV1, } from './community-migration-snapshot.js';
|
|
13
|
+
import { createCommunityExternalSnapshotAttestationV1, createCommunityNativeManagedSnapshotV1, writeCommunityExternalSnapshotAttestationV1, } from './community-migration-snapshot.js';
|
|
14
|
+
import { createCommunityNativeBackupV1 } from './community-native-backup.js';
|
|
14
15
|
import { createCommunityNativeApplicationReferenceV1, readCommunityNativeApplicationUpdate, sameCommunityNativeApplicationContent, writeCommunityNativeApplicationRollbackOutcome, writeCommunityNativeApplicationRollbackPrepared, writeCommunityNativeApplicationUpdateOutcome, writeCommunityNativeApplicationUpdatePrepared, } from './community-native-application-recovery.js';
|
|
15
16
|
const PUBLIC_ROOT_NAME = 'aops-community-distribution';
|
|
16
17
|
const PUBLIC_SERVER_PACKAGE_NAME = '@aopslabs/aops-server';
|
|
@@ -1195,10 +1196,17 @@ export async function attestCommunityNativeExternalSnapshot(params, dependencies
|
|
|
1195
1196
|
evidence,
|
|
1196
1197
|
};
|
|
1197
1198
|
}
|
|
1198
|
-
|
|
1199
|
+
const FORWARDED_TUNING_ENV_KEYS = new Set([
|
|
1200
|
+
'CHATV3_RATE_OPEN_PER_MIN',
|
|
1201
|
+
'CHATV3_RATE_MEMBER_PER_MIN',
|
|
1202
|
+
'CHATV3_RATE_LOCAL_PER_MIN',
|
|
1203
|
+
]);
|
|
1204
|
+
export function childEnvironment(params) {
|
|
1199
1205
|
const env = { ...(params.env ?? process.env) };
|
|
1200
1206
|
for (const key of Object.keys(env)) {
|
|
1201
1207
|
const upper = key.toUpperCase();
|
|
1208
|
+
if (FORWARDED_TUNING_ENV_KEYS.has(upper))
|
|
1209
|
+
continue;
|
|
1202
1210
|
if (/^PG[A-Z0-9_]+$/i.test(key) ||
|
|
1203
1211
|
/(?:^|_)(?:AUTH|COOKIE|DATABASE|ENCRYPT|JWT|PASSWORD|PG|POSTGRES|REPO|SECRET|SESSION|SQLITE|TOKEN)(?:_|$)/i.test(key) ||
|
|
1204
1212
|
/^(?:AOPS|PROJECTMAN|DOCMAN|AGENTSPACE|CHATV3|AUTHV2|KANBAN|SPRINT|TASKER|FILEMAN|SYS)_/i.test(key) ||
|
|
@@ -1529,6 +1537,16 @@ function removeControlIfExact(paths, launchId) {
|
|
|
1529
1537
|
throw new Error('community_native_control_launch_mismatch');
|
|
1530
1538
|
rmSync(paths.controlPath, { force: true });
|
|
1531
1539
|
}
|
|
1540
|
+
function reconcileDisappearedRecord(record, reason, now) {
|
|
1541
|
+
if (record.status !== 'starting' && record.status !== 'running')
|
|
1542
|
+
return record;
|
|
1543
|
+
return {
|
|
1544
|
+
...record,
|
|
1545
|
+
status: 'failed',
|
|
1546
|
+
updatedAt: now().toISOString(),
|
|
1547
|
+
failure: record.failure ?? reason,
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1532
1550
|
async function observeInstalledNative(params) {
|
|
1533
1551
|
const record = params.processRecord;
|
|
1534
1552
|
const identityPresent = existsSync(params.paths.identityPath);
|
|
@@ -1607,7 +1625,8 @@ async function observeInstalledNative(params) {
|
|
|
1607
1625
|
if (!identity) {
|
|
1608
1626
|
if (!supervisorAlive && !hostAlive) {
|
|
1609
1627
|
return {
|
|
1610
|
-
runtimeState: 'crashed', recoverable: true,
|
|
1628
|
+
runtimeState: 'crashed', recoverable: true, identity: null,
|
|
1629
|
+
process: reconcileDisappearedRecord(record, 'community_native_process_disappeared_without_identity', params.now),
|
|
1611
1630
|
supervisorAlive: false, hostAlive: false, health: 'not-checked',
|
|
1612
1631
|
reason: 'community_native_process_disappeared_without_identity',
|
|
1613
1632
|
};
|
|
@@ -1620,7 +1639,8 @@ async function observeInstalledNative(params) {
|
|
|
1620
1639
|
}
|
|
1621
1640
|
if (!supervisorAlive && !hostAlive) {
|
|
1622
1641
|
return {
|
|
1623
|
-
runtimeState: 'crashed', recoverable: true,
|
|
1642
|
+
runtimeState: 'crashed', recoverable: true, identity,
|
|
1643
|
+
process: reconcileDisappearedRecord(record, 'community_native_process_disappeared', params.now),
|
|
1624
1644
|
supervisorAlive: false, hostAlive: false, health: 'not-checked',
|
|
1625
1645
|
reason: 'community_native_process_disappeared',
|
|
1626
1646
|
};
|
|
@@ -1689,6 +1709,10 @@ export async function stopCommunityNativeInstall(params = {}) {
|
|
|
1689
1709
|
now,
|
|
1690
1710
|
});
|
|
1691
1711
|
if (observed.runtimeState === 'stopped' || observed.runtimeState === 'crashed') {
|
|
1712
|
+
if (observed.runtimeState === 'crashed' && observed.process &&
|
|
1713
|
+
inspection.process && inspection.process.status !== observed.process.status) {
|
|
1714
|
+
writeProcess(inspection.paths, observed.process);
|
|
1715
|
+
}
|
|
1692
1716
|
return { status: 'already-stopped', state: inspection.state, process: observed.process };
|
|
1693
1717
|
}
|
|
1694
1718
|
if (!observed.process || !observed.identity || !observed.supervisorAlive ||
|
|
@@ -1842,6 +1866,60 @@ export function resolveCommunityNativeLaunchMode(input) {
|
|
|
1842
1866
|
throw new Error('community_native_launch_mode_conflict');
|
|
1843
1867
|
return input.foreground === true ? 'foreground' : 'detached';
|
|
1844
1868
|
}
|
|
1869
|
+
export async function resolveCommunityNativeSnapshotEvidence(params) {
|
|
1870
|
+
if (params.context.planning.migrationPlan.action !== 'migrate')
|
|
1871
|
+
return null;
|
|
1872
|
+
const exists = params.fileExists ?? existsSync;
|
|
1873
|
+
if (params.postgres.mode === 'container') {
|
|
1874
|
+
const create = params.createManagedSnapshot ?? createCommunityNativeManagedSnapshotV1;
|
|
1875
|
+
const snapshot = await create({
|
|
1876
|
+
planning: params.context.planning,
|
|
1877
|
+
policy: params.context.policy,
|
|
1878
|
+
sourceRoot: params.context.sourceRoot,
|
|
1879
|
+
state: params.postgres,
|
|
1880
|
+
instanceName: params.instanceName,
|
|
1881
|
+
instanceRoot: params.instanceRoot,
|
|
1882
|
+
backupRoot: params.backupRoot,
|
|
1883
|
+
evidenceRoot: params.evidenceRoot,
|
|
1884
|
+
runtime: params.postgresRuntime,
|
|
1885
|
+
signal: params.signal,
|
|
1886
|
+
});
|
|
1887
|
+
return { path: snapshot.evidencePath, policy: 'managed-verified-only-v1' };
|
|
1888
|
+
}
|
|
1889
|
+
const attestationPath = path.join(params.evidenceRoot, `external-${params.context.planning.acceptedPlanSha256}.json`);
|
|
1890
|
+
if (exists(attestationPath)) {
|
|
1891
|
+
return { path: attestationPath, policy: 'managed-or-external-attested-v1' };
|
|
1892
|
+
}
|
|
1893
|
+
const postgres = params.postgres;
|
|
1894
|
+
const loadUrl = params.loadRepoUrl
|
|
1895
|
+
?? ((state) => {
|
|
1896
|
+
const configRef = state.configRef;
|
|
1897
|
+
if (typeof configRef !== 'string' || configRef.trim() === '') {
|
|
1898
|
+
throw new Error('community_native_snapshot_external_config_ref_missing');
|
|
1899
|
+
}
|
|
1900
|
+
return loadExternalPostgresUrl(configRef, state.tlsPolicy);
|
|
1901
|
+
});
|
|
1902
|
+
const createBackup = params.createExternalBackup ?? createCommunityNativeBackupV1;
|
|
1903
|
+
const writeAttestation = params.writeExternalAttestation ?? writeCommunityExternalSnapshotAttestationV1;
|
|
1904
|
+
const firstPending = params.context.planning.migrationPlan.pendingMigrations?.[0]?.tag;
|
|
1905
|
+
const { record } = await createBackup({
|
|
1906
|
+
backupRoot: params.backupRoot,
|
|
1907
|
+
repoUrl: loadUrl(postgres),
|
|
1908
|
+
instanceName: params.instanceName,
|
|
1909
|
+
label: firstPending ? `pre-${firstPending}` : 'pre-migration',
|
|
1910
|
+
});
|
|
1911
|
+
const written = writeAttestation({
|
|
1912
|
+
planning: params.context.planning,
|
|
1913
|
+
policy: params.context.policy,
|
|
1914
|
+
evidenceRoot: params.evidenceRoot,
|
|
1915
|
+
provider: 'aops-cli-native-pg-dump',
|
|
1916
|
+
snapshotRef: record.path,
|
|
1917
|
+
snapshotDigest: record.sha256,
|
|
1918
|
+
attestedBy: 'aops-cli',
|
|
1919
|
+
restoreInstructionsRef: `aops db restore --backup ${record.path} --confirm-data-rewind`,
|
|
1920
|
+
});
|
|
1921
|
+
return { path: written.evidencePath, policy: 'managed-or-external-attested-v1' };
|
|
1922
|
+
}
|
|
1845
1923
|
async function launchInstalledNative(params) {
|
|
1846
1924
|
throwIfNativeAborted(params.signal);
|
|
1847
1925
|
assertCommunityNativePathLayout(params.paths, { requireInstanceRoot: true });
|
|
@@ -1911,6 +1989,17 @@ async function launchInstalledNative(params) {
|
|
|
1911
1989
|
sourceFingerprint: params.state.source.sourceFingerprint,
|
|
1912
1990
|
requiredPlanSha256: params.requiredMigrationPlanSha256,
|
|
1913
1991
|
requiredAction: params.requiredMigrationAction,
|
|
1992
|
+
snapshotEvidenceProvider: (context) => resolveCommunityNativeSnapshotEvidence({
|
|
1993
|
+
context,
|
|
1994
|
+
postgres,
|
|
1995
|
+
instanceName: params.state.instanceName,
|
|
1996
|
+
instanceRoot: params.paths.instanceRoot,
|
|
1997
|
+
backupRoot: params.paths.backupRoot,
|
|
1998
|
+
evidenceRoot: params.paths.migrationEvidenceRoot,
|
|
1999
|
+
postgresRuntime: params.postgresRuntime,
|
|
2000
|
+
signal: params.signal,
|
|
2001
|
+
createManagedSnapshot: params.createManagedSnapshot,
|
|
2002
|
+
}),
|
|
1914
2003
|
onPlanAccepted: (context) => {
|
|
1915
2004
|
const pendingMigrationCount = context.migrationPlan.pendingMigrations.length;
|
|
1916
2005
|
params.progress?.({
|
|
@@ -212,6 +212,26 @@ function receiptFromResult(invocation, result, completedAt, migrationLogCount) {
|
|
|
212
212
|
migrationLogCount,
|
|
213
213
|
});
|
|
214
214
|
}
|
|
215
|
+
export async function reportCommunityNativePendingMigrations(params, dependencies = {}) {
|
|
216
|
+
try {
|
|
217
|
+
const policy = readCommunityNativeMigrationPolicy(params.sourceRoot);
|
|
218
|
+
const planning = await (dependencies.planStrictPgSchema ?? planCommunityStrictPgSchema)({
|
|
219
|
+
repoUrl: params.repoUrl,
|
|
220
|
+
workspaceRoot: params.sourceRoot,
|
|
221
|
+
policy,
|
|
222
|
+
});
|
|
223
|
+
const pending = planning.migrationPlan.pendingMigrations ?? [];
|
|
224
|
+
return {
|
|
225
|
+
status: 'planned',
|
|
226
|
+
action: planning.migrationPlan.action,
|
|
227
|
+
pendingMigrationCount: pending.length,
|
|
228
|
+
pendingMigrationTags: pending.map((entry) => String(entry.tag ?? '')).filter(Boolean),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
return { status: 'unavailable', reason: error instanceof Error ? error.message : String(error) };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
215
235
|
export async function runCommunityNativeMigration(invocation, dependencies = {}) {
|
|
216
236
|
const prior = invocation.priorIntentExpectation;
|
|
217
237
|
if (!path.isAbsolute(invocation.sourceRoot) || !invocation.repoUrl ||
|