@aopslabs/aops 0.2.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.
Files changed (171) hide show
  1. package/LICENSE +19 -0
  2. package/NOTICE +7 -0
  3. package/README.md +203 -0
  4. package/assets/agent-assets/core/SKILL.md +49 -0
  5. package/assets/agent-assets/core/references/agentspace/SKILL.md +78 -0
  6. package/assets/agent-assets/core/references/aops-cli-core/SKILL.md +108 -0
  7. package/assets/agent-assets/core/references/chatv3/SKILL.md +48 -0
  8. package/assets/agent-assets/core/references/collaborative-work/SKILL.md +85 -0
  9. package/assets/agent-assets/core/references/discuss/SKILL.md +66 -0
  10. package/assets/agent-assets/core/references/docman/SKILL.md +69 -0
  11. package/assets/agent-assets/core/references/hosted-chat/SKILL.md +64 -0
  12. package/assets/agent-assets/core/references/mission/SKILL.md +54 -0
  13. package/assets/agent-assets/core/references/projectman/SKILL.md +86 -0
  14. package/assets/agent-assets/core/references/sys/SKILL.md +36 -0
  15. package/assets/agent-assets/core/references/view/SKILL.md +58 -0
  16. package/assets/agent-assets/core/references/working-disciplines/SKILL.md +69 -0
  17. package/assets/agent-assets/core/user-guides/agent-assets.md +81 -0
  18. package/assets/agent-assets/core/user-guides/agentspace.md +1098 -0
  19. package/assets/agent-assets/core/user-guides/aops-cli.md +1107 -0
  20. package/assets/agent-assets/core/user-guides/aops-system.md +63 -0
  21. package/assets/agent-assets/core/user-guides/chatv3.md +113 -0
  22. package/assets/agent-assets/core/user-guides/docman.md +162 -0
  23. package/assets/agent-assets/core/user-guides/projectman.md +212 -0
  24. package/assets/agent-assets/core/user-guides/sys.md +94 -0
  25. package/assets/agent-assets/core/user-guides/working-disciplines.md +588 -0
  26. package/assets/agent-assets/gateway/aops/SKILL.md +14 -0
  27. package/assets/r6-remote-session-baseline.json +19 -0
  28. package/assets/skills/aops-install/SKILL.md +231 -0
  29. package/assets/skills/aops-install/agents/openai.yaml +4 -0
  30. package/dist/commands/activity.js +352 -0
  31. package/dist/commands/agent-profile.js +653 -0
  32. package/dist/commands/agent.js +710 -0
  33. package/dist/commands/agents-md.js +125 -0
  34. package/dist/commands/api.js +84 -0
  35. package/dist/commands/archive.js +873 -0
  36. package/dist/commands/artifact.js +535 -0
  37. package/dist/commands/assets.js +1414 -0
  38. package/dist/commands/auth/device-login.js +1 -0
  39. package/dist/commands/auth/legacy-export.js +177 -0
  40. package/dist/commands/auth/login.js +426 -0
  41. package/dist/commands/chat.js +1363 -0
  42. package/dist/commands/chatv3.js +1684 -0
  43. package/dist/commands/checkpoint.js +549 -0
  44. package/dist/commands/commercial-artifact.js +475 -0
  45. package/dist/commands/commercial-license.js +594 -0
  46. package/dist/commands/community-auth.js +62 -0
  47. package/dist/commands/community-cockpit.js +185 -0
  48. package/dist/commands/community-console.js +109 -0
  49. package/dist/commands/community-doctor.js +335 -0
  50. package/dist/commands/community-server.js +2062 -0
  51. package/dist/commands/community-setup.js +284 -0
  52. package/dist/commands/discuss.js +1448 -0
  53. package/dist/commands/doc.js +5219 -0
  54. package/dist/commands/experience.js +922 -0
  55. package/dist/commands/host.js +1127 -0
  56. package/dist/commands/init.js +118 -0
  57. package/dist/commands/memory.js +3129 -0
  58. package/dist/commands/mission.js +562 -0
  59. package/dist/commands/official-catalog.js +134 -0
  60. package/dist/commands/plan.js +84 -0
  61. package/dist/commands/playbook.js +415 -0
  62. package/dist/commands/pm/context.js +6 -0
  63. package/dist/commands/pm/index.js +701 -0
  64. package/dist/commands/pm/projectman.js +6266 -0
  65. package/dist/commands/project.js +1080 -0
  66. package/dist/commands/prompt.js +1024 -0
  67. package/dist/commands/repo-sync.js +1907 -0
  68. package/dist/commands/resource.js +527 -0
  69. package/dist/commands/skill.js +1211 -0
  70. package/dist/commands/start-disciplines.js +510 -0
  71. package/dist/commands/start.js +1343 -0
  72. package/dist/commands/target.js +309 -0
  73. package/dist/commands/version.js +42 -0
  74. package/dist/commands/view.js +1852 -0
  75. package/dist/launchers/cockpit.js +28 -0
  76. package/dist/launchers/options.js +74 -0
  77. package/dist/launchers/server.js +41 -0
  78. package/dist/lib/agent-assets/envelope.js +83 -0
  79. package/dist/lib/agent-assets/gateway.js +15 -0
  80. package/dist/lib/agent-assets/guards.js +23 -0
  81. package/dist/lib/agent-assets/hosted-discovery.js +148 -0
  82. package/dist/lib/agent-assets/hosted-package-input.js +154 -0
  83. package/dist/lib/agent-assets/legacy-pointer-migration.js +677 -0
  84. package/dist/lib/agent-assets/native-fs.js +589 -0
  85. package/dist/lib/agent-assets/package-manifest.js +324 -0
  86. package/dist/lib/agent-assets/portable-path.js +67 -0
  87. package/dist/lib/agent-assets/release-input.js +177 -0
  88. package/dist/lib/agent-assets/roots.js +45 -0
  89. package/dist/lib/agent-assets/runtime-binding-reader.js +545 -0
  90. package/dist/lib/agent-assets/runtime-targets.js +50 -0
  91. package/dist/lib/agent-assets/store-reader.js +1212 -0
  92. package/dist/lib/agent-assets/store-types.js +1 -0
  93. package/dist/lib/agent-assets/store-writer.js +1484 -0
  94. package/dist/lib/agent-assets/types.js +1 -0
  95. package/dist/lib/agent-assets/unicode-case-folding-v15-1.generated.js +1548 -0
  96. package/dist/lib/commercial-final-readiness.js +504 -0
  97. package/dist/lib/commercial-lifecycle-smoke-isolation.js +236 -0
  98. package/dist/lib/commercial-managed-postgres-docker-adapter.js +274 -0
  99. package/dist/lib/commercial-setup-readiness-adapter.js +179 -0
  100. package/dist/lib/community-client-contract.js +164 -0
  101. package/dist/lib/community-cockpit-lifecycle.js +601 -0
  102. package/dist/lib/community-command-abort.js +59 -0
  103. package/dist/lib/community-diagnostic.js +237 -0
  104. package/dist/lib/community-docker-adapter.js +498 -0
  105. package/dist/lib/community-home.js +31 -0
  106. package/dist/lib/community-instance-contract.js +148 -0
  107. package/dist/lib/community-instance-layout.js +97 -0
  108. package/dist/lib/community-lifecycle.js +1631 -0
  109. package/dist/lib/community-migration-snapshot.js +770 -0
  110. package/dist/lib/community-native-application-recovery.js +425 -0
  111. package/dist/lib/community-native-child.js +240 -0
  112. package/dist/lib/community-native-database-recovery.js +456 -0
  113. package/dist/lib/community-native-lifecycle.js +2458 -0
  114. package/dist/lib/community-native-migration.js +339 -0
  115. package/dist/lib/community-native-postgres.js +596 -0
  116. package/dist/lib/community-operation-journal.js +773 -0
  117. package/dist/lib/community-operation-lock.js +596 -0
  118. package/dist/lib/community-release-resolver.js +291 -0
  119. package/dist/lib/community-release-verifier.js +475 -0
  120. package/dist/lib/community-repo-discovery.js +252 -0
  121. package/dist/lib/community-setup-server-env.js +203 -0
  122. package/dist/lib/external-url.js +110 -0
  123. package/dist/lib/official-catalog-gateway.js +108 -0
  124. package/dist/lib/official-catalog.js +364 -0
  125. package/dist/lib/server-bootstrap.js +240 -0
  126. package/dist/lib/setup-agent-assets-bridge.js +176 -0
  127. package/dist/lib/setup-agent-assets-release.js +52 -0
  128. package/dist/lib/setup-external-postgres.js +108 -0
  129. package/dist/lib/setup-init-orchestrator.js +990 -0
  130. package/dist/lib/setup-install-guide.js +52 -0
  131. package/dist/lib/setup-installer-release.js +47 -0
  132. package/dist/lib/setup-local-postgres.js +329 -0
  133. package/dist/lib/setup-official-catalog-bridge.js +148 -0
  134. package/dist/lib/setup-readiness.js +888 -0
  135. package/dist/lib/tui-launcher.js +59 -0
  136. package/dist/main.js +150 -0
  137. package/dist/seeds/agents-md.js +54 -0
  138. package/dist/utils/agent-gateway.js +275 -0
  139. package/dist/utils/agents-md.js +281 -0
  140. package/dist/utils/api.js +255 -0
  141. package/dist/utils/chatv3-session-store.js +185 -0
  142. package/dist/utils/command.js +67 -0
  143. package/dist/utils/community-host-runtime.js +376 -0
  144. package/dist/utils/config.js +1015 -0
  145. package/dist/utils/discussion-workspace.js +552 -0
  146. package/dist/utils/experience-workspace.js +326 -0
  147. package/dist/utils/guide-paths.js +67 -0
  148. package/dist/utils/hosted-sugar.js +56 -0
  149. package/dist/utils/hosted-workspace.js +229 -0
  150. package/dist/utils/memory-workspace.js +537 -0
  151. package/dist/utils/operator-runtime.js +199 -0
  152. package/dist/utils/password-crypto.js +39 -0
  153. package/dist/utils/playbook-workspace.js +479 -0
  154. package/dist/utils/project-context.js +255 -0
  155. package/dist/utils/prompts.js +70 -0
  156. package/dist/utils/refresh-race.js +28 -0
  157. package/dist/utils/repo-config.js +109 -0
  158. package/dist/utils/repo-first-projectman.js +194 -0
  159. package/dist/utils/repo-first-storage.js +13 -0
  160. package/dist/utils/repo-first-view.js +305 -0
  161. package/dist/utils/session-guidance.js +278 -0
  162. package/dist/utils/session-state.js +137 -0
  163. package/dist/utils/table.js +39 -0
  164. package/dist/utils/transient-fs.js +49 -0
  165. package/dist/utils/workspace-root.js +1 -0
  166. package/launchers/aops-cockpit.ps1 +15 -0
  167. package/launchers/aops-cockpit.sh +16 -0
  168. package/launchers/aops-server.ps1 +15 -0
  169. package/launchers/aops-server.sh +16 -0
  170. package/native/tui/win32-x64/aops-tui.exe +0 -0
  171. package/package.json +81 -0
@@ -0,0 +1,770 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { closeSync, constants, existsSync, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, readSync, readFileSync, realpathSync, rmSync, writeFileSync, } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { planCommunityStrictPgSchema, } from '@aopslabs/aops-pg-bootstrap';
5
+ import { assertCommunityNativePostgresInstanceState, buildCommunityNativePostgresUrl, communityNativePostgresRuntime, } from './community-native-postgres.js';
6
+ import { readCommunityNativeMigrationPolicy } from './community-native-migration.js';
7
+ const RAW_SHA256 = /^[a-f0-9]{64}$/;
8
+ const PREFIXED_SHA256 = /^sha256:[a-f0-9]{64}$/;
9
+ const SAFE_TEXT = /^[^\u0000-\u001f\u007f]+$/;
10
+ const BUFFER_BYTES = 1024 * 1024;
11
+ const MAX_EVIDENCE_BYTES = 1024 * 1024;
12
+ const SAFE_DATABASE_NAME = /^[a-z][a-z0-9_]{0,62}$/;
13
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
14
+ function sha256(value) {
15
+ return createHash('sha256').update(value).digest('hex');
16
+ }
17
+ function samePath(left, right) {
18
+ const a = path.resolve(left);
19
+ const b = path.resolve(right);
20
+ return process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b;
21
+ }
22
+ function ensureCanonicalDirectory(directory, code) {
23
+ const resolved = path.resolve(directory);
24
+ const existed = existsSync(resolved);
25
+ mkdirSync(resolved, { recursive: true, mode: 0o700 });
26
+ if (!existed)
27
+ fsyncDirectoryBestEffortOnWindows(path.dirname(resolved));
28
+ const stats = lstatSync(resolved);
29
+ if (!stats.isDirectory() || stats.isSymbolicLink() || !samePath(realpathSync.native(resolved), resolved)) {
30
+ throw new Error(code);
31
+ }
32
+ return resolved;
33
+ }
34
+ function assertFreshDirectChild(root, candidate, code) {
35
+ const resolved = path.resolve(candidate);
36
+ if (!samePath(path.dirname(resolved), root) || existsSync(resolved))
37
+ throw new Error(code);
38
+ return resolved;
39
+ }
40
+ function writeJsonExclusive(targetPath, value) {
41
+ const root = ensureCanonicalDirectory(path.dirname(targetPath), 'community_snapshot_evidence_root_unsafe');
42
+ const target = assertFreshDirectChild(root, targetPath, 'community_snapshot_evidence_already_exists');
43
+ const temp = path.join(root, `.${path.basename(target)}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`);
44
+ let descriptor;
45
+ try {
46
+ descriptor = openSync(temp, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
47
+ writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8' });
48
+ fsyncSync(descriptor);
49
+ closeSync(descriptor);
50
+ descriptor = undefined;
51
+ try {
52
+ linkSync(temp, target);
53
+ }
54
+ catch (error) {
55
+ if (error.code === 'EEXIST') {
56
+ throw new Error('community_snapshot_evidence_already_exists');
57
+ }
58
+ throw error;
59
+ }
60
+ fsyncDirectoryBestEffortOnWindows(root);
61
+ rmSync(temp, { force: true });
62
+ fsyncDirectoryBestEffortOnWindows(root);
63
+ }
64
+ finally {
65
+ if (descriptor !== undefined)
66
+ closeSync(descriptor);
67
+ rmSync(temp, { force: true });
68
+ }
69
+ }
70
+ function fsyncDirectoryBestEffortOnWindows(directory) {
71
+ let descriptor;
72
+ try {
73
+ descriptor = openSync(directory, 'r');
74
+ fsyncSync(descriptor);
75
+ }
76
+ catch (error) {
77
+ const code = error.code;
78
+ if (process.platform !== 'win32' || !['EACCES', 'EBADF', 'EINVAL', 'EISDIR', 'EPERM'].includes(String(code))) {
79
+ throw error;
80
+ }
81
+ }
82
+ finally {
83
+ if (descriptor !== undefined)
84
+ closeSync(descriptor);
85
+ }
86
+ }
87
+ function sourceDataFingerprint(planning) {
88
+ return sha256(JSON.stringify(planning.dataSentinels));
89
+ }
90
+ function evidenceBase(planning, policy) {
91
+ if (planning.migrationPlan.action !== 'migrate' ||
92
+ !planning.migrationPlan.pendingMigrations.some((migration) => migration.risk === 'destructive-or-dynamic')) {
93
+ throw new Error('community_snapshot_evidence_not_required');
94
+ }
95
+ return {
96
+ acceptedPlanSha256: planning.acceptedPlanSha256,
97
+ sourceMigrationStateFingerprintSha256: planning.sourceFingerprintSha256,
98
+ sourceLineageId: planning.lineageId,
99
+ sourceSchemaFingerprintSha256: planning.schemaFingerprintSha256,
100
+ sourceReceiptFingerprintSha256: planning.receiptFingerprintSha256,
101
+ sourceDataFingerprintSha256: sourceDataFingerprint(planning),
102
+ sourceStateFingerprintSha256: planning.stateFingerprintSha256,
103
+ targetInventorySha256: policy.inventorySha256,
104
+ };
105
+ }
106
+ function canonicalText(value, maximumLength) {
107
+ const canonical = value.trim();
108
+ if (canonical.length < 1 || canonical.length > maximumLength || !SAFE_TEXT.test(canonical)) {
109
+ throw new Error('community_external_snapshot_attestation_invalid');
110
+ }
111
+ return canonical;
112
+ }
113
+ function canonicalNonSecretReference(value, maximumLength) {
114
+ const canonical = canonicalText(value, maximumLength);
115
+ if (canonical.includes('?') ||
116
+ /^(?:[a-z][a-z0-9+.-]*:)?\/\/[^/\s]*@/i.test(canonical) ||
117
+ /(?:token|secret|password|signature|credential|sig)\s*=/i.test(canonical)) {
118
+ throw new Error('community_external_snapshot_attestation_secret_ref_refused');
119
+ }
120
+ return canonical;
121
+ }
122
+ export function createCommunityExternalSnapshotAttestationV1(params) {
123
+ const provider = canonicalText(params.provider, 128);
124
+ const snapshotRef = canonicalNonSecretReference(params.snapshotRef, 1_024);
125
+ const attestedBy = canonicalText(params.attestedBy, 256);
126
+ const restoreInstructionsRef = canonicalNonSecretReference(params.restoreInstructionsRef, 2_048);
127
+ if (params.snapshotDigest !== null && !PREFIXED_SHA256.test(params.snapshotDigest)) {
128
+ throw new Error('community_external_snapshot_attestation_invalid');
129
+ }
130
+ return {
131
+ schemaVersion: 1,
132
+ kind: 'external-snapshot-attestation',
133
+ evidencePolicy: 'external-recovery-owner-attested-v1',
134
+ createdAt: (params.now ?? (() => new Date()))().toISOString(),
135
+ recoveryOwner: 'external',
136
+ provider,
137
+ snapshotRef,
138
+ snapshotDigest: params.snapshotDigest,
139
+ attestedBy,
140
+ restoreInstructionsRef,
141
+ ...evidenceBase(params.planning, params.policy),
142
+ };
143
+ }
144
+ export function writeCommunityExternalSnapshotAttestationV1(params) {
145
+ const evidence = createCommunityExternalSnapshotAttestationV1(params);
146
+ const root = ensureCanonicalDirectory(params.evidenceRoot, 'community_snapshot_evidence_root_unsafe');
147
+ const evidencePath = path.join(root, `external-${params.planning.acceptedPlanSha256}.json`);
148
+ writeJsonExclusive(evidencePath, evidence);
149
+ return { evidencePath, evidence };
150
+ }
151
+ function isRecord(value) {
152
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
153
+ }
154
+ function exactKeys(value, expected, code) {
155
+ const keys = Object.keys(value).sort();
156
+ const wanted = [...expected].sort();
157
+ if (keys.length !== wanted.length || keys.some((key, index) => key !== wanted[index]))
158
+ throw new Error(code);
159
+ }
160
+ export function readCommunityNativeSnapshotEvidenceV1(params) {
161
+ const root = ensureCanonicalDirectory(params.evidenceRoot, 'community_snapshot_evidence_root_unsafe');
162
+ const evidencePath = path.resolve(params.evidencePath);
163
+ if (!samePath(path.dirname(evidencePath), root) || !existsSync(evidencePath)) {
164
+ throw new Error('community_native_snapshot_evidence_path_invalid');
165
+ }
166
+ const pathStats = lstatSync(evidencePath, { bigint: true });
167
+ if (!pathStats.isFile() || pathStats.isSymbolicLink() || pathStats.size < 1n ||
168
+ pathStats.size > BigInt(MAX_EVIDENCE_BYTES) ||
169
+ !samePath(realpathSync.native(evidencePath), evidencePath)) {
170
+ throw new Error('community_native_snapshot_evidence_invalid');
171
+ }
172
+ let parsed;
173
+ let descriptor;
174
+ try {
175
+ const noFollow = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
176
+ descriptor = openSync(evidencePath, constants.O_RDONLY | noFollow);
177
+ const before = fstatSync(descriptor, { bigint: true });
178
+ if (!before.isFile() || before.nlink !== 1n || before.size < 1n || before.size > BigInt(MAX_EVIDENCE_BYTES) ||
179
+ pathStats.dev !== before.dev || pathStats.ino !== before.ino) {
180
+ throw new Error('community_native_snapshot_evidence_invalid');
181
+ }
182
+ parsed = JSON.parse(readFileSync(descriptor, 'utf8'));
183
+ const after = fstatSync(descriptor, { bigint: true });
184
+ if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size ||
185
+ after.nlink !== before.nlink || after.mtimeNs !== before.mtimeNs ||
186
+ !samePath(realpathSync.native(evidencePath), evidencePath)) {
187
+ throw new Error('community_native_snapshot_evidence_identity_changed');
188
+ }
189
+ }
190
+ catch {
191
+ throw new Error('community_native_snapshot_evidence_invalid');
192
+ }
193
+ finally {
194
+ if (descriptor !== undefined)
195
+ closeSync(descriptor);
196
+ }
197
+ if (!isRecord(parsed))
198
+ throw new Error('community_native_snapshot_evidence_invalid');
199
+ const commonKeys = [
200
+ 'schemaVersion', 'kind', 'evidencePolicy', 'createdAt', 'acceptedPlanSha256',
201
+ 'sourceMigrationStateFingerprintSha256', 'sourceLineageId', 'sourceSchemaFingerprintSha256',
202
+ 'sourceReceiptFingerprintSha256', 'sourceDataFingerprintSha256', 'sourceStateFingerprintSha256',
203
+ 'targetInventorySha256',
204
+ ];
205
+ if (parsed.schemaVersion !== 1 || Number.isNaN(Date.parse(String(parsed.createdAt))) ||
206
+ typeof parsed.sourceLineageId !== 'string' || parsed.sourceLineageId.length < 1 ||
207
+ ![
208
+ parsed.acceptedPlanSha256, parsed.sourceMigrationStateFingerprintSha256,
209
+ parsed.sourceSchemaFingerprintSha256, parsed.sourceReceiptFingerprintSha256,
210
+ parsed.sourceDataFingerprintSha256, parsed.sourceStateFingerprintSha256,
211
+ parsed.targetInventorySha256,
212
+ ].every((value) => typeof value === 'string' && RAW_SHA256.test(value))) {
213
+ throw new Error('community_native_snapshot_evidence_invalid');
214
+ }
215
+ if (parsed.kind === 'managed-verified-backup') {
216
+ exactKeys(parsed, [...commonKeys, 'backupPath', 'sha256', 'byteLength', 'restoreProof'], 'community_native_managed_snapshot_evidence_invalid');
217
+ if (parsed.evidencePolicy !== 'managed-restore-verified-v1' ||
218
+ typeof parsed.backupPath !== 'string' || !path.isAbsolute(parsed.backupPath) ||
219
+ typeof parsed.sha256 !== 'string' || !PREFIXED_SHA256.test(parsed.sha256) ||
220
+ !Number.isSafeInteger(parsed.byteLength) || parsed.byteLength < 1 || !isRecord(parsed.restoreProof)) {
221
+ throw new Error('community_native_managed_snapshot_evidence_invalid');
222
+ }
223
+ exactKeys(parsed.restoreProof, [
224
+ 'method', 'backupSha256', 'backupByteLength', 'restoredSchemaFingerprintSha256',
225
+ 'restoredReceiptFingerprintSha256', 'restoredDataFingerprintSha256',
226
+ 'restoredStateFingerprintSha256',
227
+ ], 'community_native_managed_snapshot_restore_proof_invalid');
228
+ if (parsed.restoreProof.method !== 'pg-restore-disposable-v1' ||
229
+ parsed.restoreProof.backupSha256 !== parsed.sha256 ||
230
+ parsed.restoreProof.backupByteLength !== parsed.byteLength ||
231
+ parsed.restoreProof.restoredSchemaFingerprintSha256 !== parsed.sourceSchemaFingerprintSha256 ||
232
+ parsed.restoreProof.restoredReceiptFingerprintSha256 !== parsed.sourceReceiptFingerprintSha256 ||
233
+ parsed.restoreProof.restoredDataFingerprintSha256 !== parsed.sourceDataFingerprintSha256 ||
234
+ parsed.restoreProof.restoredStateFingerprintSha256 !== parsed.sourceStateFingerprintSha256) {
235
+ throw new Error('community_native_managed_snapshot_restore_proof_invalid');
236
+ }
237
+ }
238
+ else if (parsed.kind === 'external-snapshot-attestation') {
239
+ exactKeys(parsed, [
240
+ ...commonKeys, 'recoveryOwner', 'provider', 'snapshotRef', 'snapshotDigest',
241
+ 'attestedBy', 'restoreInstructionsRef',
242
+ ], 'community_native_external_snapshot_evidence_invalid');
243
+ if (parsed.evidencePolicy !== 'external-recovery-owner-attested-v1' || parsed.recoveryOwner !== 'external' ||
244
+ typeof parsed.provider !== 'string' || canonicalText(parsed.provider, 128) !== parsed.provider ||
245
+ typeof parsed.snapshotRef !== 'string' || canonicalNonSecretReference(parsed.snapshotRef, 1_024) !== parsed.snapshotRef ||
246
+ (parsed.snapshotDigest !== null &&
247
+ (typeof parsed.snapshotDigest !== 'string' || !PREFIXED_SHA256.test(parsed.snapshotDigest))) ||
248
+ typeof parsed.attestedBy !== 'string' || canonicalText(parsed.attestedBy, 256) !== parsed.attestedBy ||
249
+ typeof parsed.restoreInstructionsRef !== 'string' ||
250
+ canonicalNonSecretReference(parsed.restoreInstructionsRef, 2_048) !== parsed.restoreInstructionsRef) {
251
+ throw new Error('community_native_external_snapshot_evidence_invalid');
252
+ }
253
+ }
254
+ else {
255
+ throw new Error('community_native_snapshot_evidence_kind_invalid');
256
+ }
257
+ if (params.expectedKind !== undefined && parsed.kind !== params.expectedKind) {
258
+ throw new Error('community_native_snapshot_evidence_kind_mismatch');
259
+ }
260
+ const evidence = parsed;
261
+ const evidenceSha256 = sha256(JSON.stringify(evidence));
262
+ const expected = params.expectedEvidenceSha256?.replace(/^sha256:/, '') ?? null;
263
+ if (expected !== null && (!RAW_SHA256.test(expected) || evidenceSha256 !== expected)) {
264
+ throw new Error('community_native_snapshot_evidence_hash_mismatch');
265
+ }
266
+ return { evidencePath, evidenceSha256, evidence };
267
+ }
268
+ async function runDocker(params) {
269
+ const result = await params.runtime.run({
270
+ command: 'docker',
271
+ args: params.args,
272
+ env: {},
273
+ signal: params.signal,
274
+ inputFd: params.inputFd,
275
+ outputFd: params.outputFd,
276
+ });
277
+ if (result.exitCode !== 0) {
278
+ const diagnostic = result.stderr
279
+ .replace(/(postgres(?:ql)?:\/\/[^:\s/@]+:)[^@\s/]+@/gi, '$1[REDACTED]@')
280
+ .replace(/((?:password|secret|token)\s*[=:]\s*)[^\s,;]+/gi, '$1[REDACTED]')
281
+ .slice(-1_000);
282
+ throw new Error(`community_native_snapshot_${params.operation}_failed:${result.exitCode ?? result.signal ?? 'unknown'}:${diagnostic}`);
283
+ }
284
+ }
285
+ async function runDockerCapture(params) {
286
+ const result = await params.runtime.run({
287
+ command: 'docker',
288
+ args: params.args,
289
+ env: {},
290
+ signal: params.signal,
291
+ });
292
+ if (result.exitCode !== 0) {
293
+ const diagnostic = result.stderr
294
+ .replace(/(postgres(?:ql)?:\/\/[^:\s/@]+:)[^@\s/]+@/gi, '$1[REDACTED]@')
295
+ .replace(/((?:password|secret|token)\s*[=:]\s*)[^\s,;]+/gi, '$1[REDACTED]')
296
+ .slice(-1_000);
297
+ throw new Error(`community_native_snapshot_${params.operation}_failed:${result.exitCode ?? result.signal ?? 'unknown'}:${diagnostic}`);
298
+ }
299
+ return result.stdout;
300
+ }
301
+ function hashHeldFile(fd, syncBeforeRead = true) {
302
+ if (syncBeforeRead)
303
+ fsyncSync(fd);
304
+ const before = fstatSync(fd, { bigint: true });
305
+ if (!before.isFile() || before.nlink !== 1n || before.size < 1n || before.size > BigInt(Number.MAX_SAFE_INTEGER)) {
306
+ throw new Error('community_native_snapshot_backup_invalid');
307
+ }
308
+ const digest = createHash('sha256');
309
+ const buffer = Buffer.allocUnsafe(BUFFER_BYTES);
310
+ let position = 0;
311
+ while (position < Number(before.size)) {
312
+ const count = readSync(fd, buffer, 0, Math.min(buffer.length, Number(before.size) - position), position);
313
+ if (count < 1)
314
+ throw new Error('community_native_snapshot_backup_short_read');
315
+ digest.update(buffer.subarray(0, count));
316
+ position += count;
317
+ }
318
+ const after = fstatSync(fd, { bigint: true });
319
+ if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size || after.nlink !== 1n) {
320
+ throw new Error('community_native_snapshot_backup_identity_changed');
321
+ }
322
+ return { sha256: `sha256:${digest.digest('hex')}`, byteLength: position };
323
+ }
324
+ function assertRestoredState(expected, restored) {
325
+ if (restored.lineageId !== expected.lineageId ||
326
+ restored.schemaFingerprintSha256 !== expected.schemaFingerprintSha256 ||
327
+ restored.receiptFingerprintSha256 !== expected.receiptFingerprintSha256 ||
328
+ restored.stateFingerprintSha256 !== expected.stateFingerprintSha256 ||
329
+ JSON.stringify(restored.dataSentinels) !== JSON.stringify(expected.dataSentinels)) {
330
+ throw new Error('community_native_snapshot_restore_proof_mismatch');
331
+ }
332
+ }
333
+ function assertDatabaseName(value) {
334
+ if (!SAFE_DATABASE_NAME.test(value))
335
+ throw new Error('community_native_snapshot_database_name_invalid');
336
+ return value;
337
+ }
338
+ function throwIfAborted(signal) {
339
+ if (signal?.aborted)
340
+ throw new Error('community_operation_aborted');
341
+ }
342
+ async function databaseExists(params) {
343
+ const database = assertDatabaseName(params.database);
344
+ const output = (await runDockerCapture({
345
+ runtime: params.runtime,
346
+ args: ['container', 'exec', params.state.containerName, 'psql', '-U', 'aops', '-d', 'postgres',
347
+ '-Atqc', `SELECT 1 FROM pg_database WHERE datname = '${database}'`],
348
+ operation: 'restore_database_exists',
349
+ signal: params.signal,
350
+ })).trim();
351
+ if (output === '')
352
+ return false;
353
+ if (output === '1')
354
+ return true;
355
+ throw new Error('community_native_snapshot_database_probe_invalid');
356
+ }
357
+ async function runDatabaseSql(params) {
358
+ await runDocker({
359
+ runtime: params.runtime,
360
+ args: ['container', 'exec', params.state.containerName, 'psql', '-U', 'aops', '-d', 'postgres',
361
+ '-v', 'ON_ERROR_STOP=1', '-c', params.sql],
362
+ operation: params.operation,
363
+ signal: params.signal,
364
+ });
365
+ }
366
+ async function terminateDatabaseConnections(params) {
367
+ const database = assertDatabaseName(params.database);
368
+ await runDatabaseSql({
369
+ ...params,
370
+ sql: `SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${database}' AND pid <> pg_backend_pid()`,
371
+ operation: 'restore_terminate_connections',
372
+ });
373
+ }
374
+ async function renameDatabase(params) {
375
+ const from = assertDatabaseName(params.from);
376
+ const to = assertDatabaseName(params.to);
377
+ await terminateDatabaseConnections({ ...params, database: from });
378
+ await runDatabaseSql({
379
+ ...params,
380
+ sql: `ALTER DATABASE "${from}" RENAME TO "${to}"`,
381
+ operation: 'restore_rename_database',
382
+ });
383
+ }
384
+ async function dropDatabase(params) {
385
+ const database = assertDatabaseName(params.database);
386
+ await runDocker({
387
+ runtime: params.runtime,
388
+ args: ['container', 'exec', params.state.containerName, 'dropdb', '-U', 'aops', '--if-exists', '--force', database],
389
+ operation: 'restore_drop_database',
390
+ signal: params.signal,
391
+ });
392
+ }
393
+ async function createDatabase(params) {
394
+ const database = assertDatabaseName(params.database);
395
+ await runDocker({
396
+ runtime: params.runtime,
397
+ args: ['container', 'exec', params.state.containerName, 'createdb', '-U', 'aops', database],
398
+ operation: 'restore_create_database',
399
+ signal: params.signal,
400
+ });
401
+ }
402
+ async function inspectRestoreDatabases(params) {
403
+ const [main, candidate, rescue] = await Promise.all([
404
+ databaseExists({ ...params, database: 'aops' }),
405
+ databaseExists({ ...params, database: params.candidateDatabase }),
406
+ databaseExists({ ...params, database: params.rescueDatabase }),
407
+ ]);
408
+ return { main, candidate, rescue };
409
+ }
410
+ export function assertCommunityNativeRestoredSnapshotStateV1(evidence, restored) {
411
+ if (restored.migrationPlan.action !== 'verify-only' || restored.requiresSnapshotEvidence ||
412
+ restored.migrationPlan.pendingMigrations.length !== 0 ||
413
+ restored.sourceFingerprintSha256 !== evidence.sourceMigrationStateFingerprintSha256 ||
414
+ restored.lineageId !== evidence.sourceLineageId ||
415
+ restored.schemaFingerprintSha256 !== evidence.sourceSchemaFingerprintSha256 ||
416
+ restored.receiptFingerprintSha256 !== evidence.sourceReceiptFingerprintSha256 ||
417
+ sourceDataFingerprint(restored) !== evidence.sourceDataFingerprintSha256 ||
418
+ restored.stateFingerprintSha256 !== evidence.sourceStateFingerprintSha256) {
419
+ throw new Error('community_native_snapshot_restored_state_mismatch');
420
+ }
421
+ }
422
+ async function recoverOriginalDatabase(params) {
423
+ const observed = await inspectRestoreDatabases(params);
424
+ if (observed.rescue) {
425
+ if (observed.main)
426
+ await dropDatabase({ ...params, database: 'aops' });
427
+ if (observed.candidate)
428
+ await dropDatabase({ ...params, database: params.candidateDatabase });
429
+ await renameDatabase({ ...params, from: params.rescueDatabase, to: 'aops' });
430
+ }
431
+ else if (observed.main && observed.candidate) {
432
+ await dropDatabase({ ...params, database: params.candidateDatabase });
433
+ }
434
+ }
435
+ export async function createCommunityNativeManagedSnapshotV1(params) {
436
+ if (!RAW_SHA256.test(params.planning.acceptedPlanSha256)) {
437
+ throw new Error('community_native_snapshot_plan_invalid');
438
+ }
439
+ const state = assertCommunityNativePostgresInstanceState({
440
+ state: params.state,
441
+ instanceName: params.instanceName,
442
+ instanceRoot: params.instanceRoot,
443
+ });
444
+ const runtime = params.runtime ?? communityNativePostgresRuntime;
445
+ const backupRoot = ensureCanonicalDirectory(params.backupRoot, 'community_native_snapshot_backup_root_unsafe');
446
+ const evidenceRoot = ensureCanonicalDirectory(params.evidenceRoot, 'community_snapshot_evidence_root_unsafe');
447
+ const backupPath = assertFreshDirectChild(backupRoot, path.join(backupRoot, `pre-migration-${params.planning.acceptedPlanSha256}.dump`), 'community_native_snapshot_backup_already_exists');
448
+ const evidencePath = assertFreshDirectChild(evidenceRoot, path.join(evidenceRoot, `managed-${params.planning.acceptedPlanSha256}.json`), 'community_snapshot_evidence_already_exists');
449
+ const noFollow = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
450
+ const backupFd = openSync(backupPath, constants.O_CREAT | constants.O_EXCL | constants.O_RDWR | noFollow, 0o600);
451
+ const verifyDatabase = `aops_verify_${params.planning.acceptedPlanSha256.slice(0, 16)}`;
452
+ let keepBackup = false;
453
+ try {
454
+ await runDocker({
455
+ runtime,
456
+ args: ['container', 'exec', state.containerName, 'pg_dump', '-U', 'aops', '-d', 'aops',
457
+ '--format=custom', '--no-owner', '--no-acl'],
458
+ operation: 'dump',
459
+ outputFd: backupFd,
460
+ signal: params.signal,
461
+ });
462
+ const backup = hashHeldFile(backupFd);
463
+ fsyncDirectoryBestEffortOnWindows(backupRoot);
464
+ await runDocker({
465
+ runtime,
466
+ args: ['container', 'exec', state.containerName, 'dropdb', '-U', 'aops', '--if-exists', '--force', verifyDatabase],
467
+ operation: 'verify_drop_stale',
468
+ signal: params.signal,
469
+ });
470
+ await runDocker({
471
+ runtime,
472
+ args: ['container', 'exec', state.containerName, 'createdb', '-U', 'aops', verifyDatabase],
473
+ operation: 'verify_create',
474
+ signal: params.signal,
475
+ });
476
+ try {
477
+ await runDocker({
478
+ runtime,
479
+ args: ['container', 'exec', '-i', state.containerName, 'pg_restore', '-U', 'aops',
480
+ '-d', verifyDatabase, '--no-owner', '--no-acl', '--exit-on-error'],
481
+ operation: 'verify_restore',
482
+ inputFd: backupFd,
483
+ signal: params.signal,
484
+ });
485
+ const restored = await (params.planStrictPgSchema ?? planCommunityStrictPgSchema)({
486
+ repoUrl: buildCommunityNativePostgresUrl(state, verifyDatabase),
487
+ workspaceRoot: params.sourceRoot,
488
+ policy: params.policy,
489
+ });
490
+ assertRestoredState(params.planning, restored);
491
+ }
492
+ finally {
493
+ await runDocker({
494
+ runtime,
495
+ args: ['container', 'exec', state.containerName, 'dropdb', '-U', 'aops', '--if-exists', '--force', verifyDatabase],
496
+ operation: 'verify_drop',
497
+ signal: params.signal,
498
+ });
499
+ }
500
+ const dataFingerprint = sourceDataFingerprint(params.planning);
501
+ const evidence = {
502
+ schemaVersion: 1,
503
+ kind: 'managed-verified-backup',
504
+ evidencePolicy: 'managed-restore-verified-v1',
505
+ createdAt: (params.now ?? (() => new Date()))().toISOString(),
506
+ backupPath,
507
+ sha256: backup.sha256,
508
+ byteLength: backup.byteLength,
509
+ ...evidenceBase(params.planning, params.policy),
510
+ restoreProof: {
511
+ method: 'pg-restore-disposable-v1',
512
+ backupSha256: backup.sha256,
513
+ backupByteLength: backup.byteLength,
514
+ restoredSchemaFingerprintSha256: params.planning.schemaFingerprintSha256,
515
+ restoredReceiptFingerprintSha256: params.planning.receiptFingerprintSha256,
516
+ restoredDataFingerprintSha256: dataFingerprint,
517
+ restoredStateFingerprintSha256: params.planning.stateFingerprintSha256,
518
+ },
519
+ };
520
+ writeJsonExclusive(evidencePath, evidence);
521
+ keepBackup = true;
522
+ return { evidencePath, backupPath, evidence };
523
+ }
524
+ finally {
525
+ closeSync(backupFd);
526
+ if (!keepBackup)
527
+ rmSync(backupPath, { force: true });
528
+ }
529
+ }
530
+ export async function restoreCommunityNativeManagedSnapshotV1(params) {
531
+ if (!UUID.test(params.restoreId) || !path.isAbsolute(params.sourceRoot)) {
532
+ throw new Error('community_native_managed_restore_invocation_invalid');
533
+ }
534
+ throwIfAborted(params.signal);
535
+ const state = assertCommunityNativePostgresInstanceState({
536
+ state: params.state,
537
+ instanceName: params.instanceName,
538
+ instanceRoot: params.instanceRoot,
539
+ });
540
+ const runtime = params.runtime ?? communityNativePostgresRuntime;
541
+ const read = readCommunityNativeSnapshotEvidenceV1({
542
+ evidenceRoot: params.evidenceRoot,
543
+ evidencePath: params.evidencePath,
544
+ expectedEvidenceSha256: params.expectedEvidenceSha256,
545
+ expectedKind: 'managed-verified-backup',
546
+ });
547
+ const evidence = read.evidence;
548
+ const backupRoot = ensureCanonicalDirectory(params.backupRoot, 'community_native_snapshot_backup_root_unsafe');
549
+ const backupPath = path.resolve(evidence.backupPath);
550
+ if (!samePath(path.dirname(backupPath), backupRoot) || !existsSync(backupPath)) {
551
+ throw new Error('community_native_snapshot_backup_path_invalid');
552
+ }
553
+ const backupStats = lstatSync(backupPath);
554
+ if (!backupStats.isFile() || backupStats.isSymbolicLink() ||
555
+ !samePath(realpathSync.native(backupPath), backupPath)) {
556
+ throw new Error('community_native_snapshot_backup_invalid');
557
+ }
558
+ const noFollow = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
559
+ const backupFd = openSync(backupPath, constants.O_RDONLY | noFollow);
560
+ const candidateDatabase = assertDatabaseName(`aops_restore_${params.restoreId.replaceAll('-', '').toLowerCase()}`);
561
+ const rescueDatabase = assertDatabaseName(`aops_rescue_${params.restoreId.replaceAll('-', '').toLowerCase()}`);
562
+ const phase = async (value) => {
563
+ throwIfAborted(params.signal);
564
+ await params.onPhase?.({ restoreId: params.restoreId, phase: value, candidateDatabase, rescueDatabase });
565
+ };
566
+ const planner = params.planStrictPgSchema ?? planCommunityStrictPgSchema;
567
+ const policy = readCommunityNativeMigrationPolicy(params.sourceRoot);
568
+ const verifyDatabase = async (database) => {
569
+ const restored = await planner({
570
+ repoUrl: buildCommunityNativePostgresUrl(state, database),
571
+ workspaceRoot: params.sourceRoot,
572
+ policy,
573
+ });
574
+ assertCommunityNativeRestoredSnapshotStateV1(evidence, restored);
575
+ return restored;
576
+ };
577
+ let completed = false;
578
+ try {
579
+ const initialBackup = hashHeldFile(backupFd, false);
580
+ if (initialBackup.sha256 !== evidence.sha256 || initialBackup.byteLength !== evidence.byteLength) {
581
+ throw new Error('community_native_snapshot_backup_hash_mismatch');
582
+ }
583
+ let observed = await inspectRestoreDatabases({ runtime, state, candidateDatabase, rescueDatabase, signal: params.signal });
584
+ const resumed = observed.candidate || observed.rescue || !observed.main;
585
+ if (!observed.main && !observed.candidate && observed.rescue) {
586
+ await renameDatabase({ runtime, state, from: rescueDatabase, to: 'aops', signal: params.signal });
587
+ throw new Error('community_native_managed_restore_interrupted_source_recovered');
588
+ }
589
+ if ((!observed.main && !observed.rescue) ||
590
+ (observed.main && observed.candidate && observed.rescue) ||
591
+ (!observed.main && observed.candidate && !observed.rescue)) {
592
+ throw new Error('community_native_managed_restore_database_state_ambiguous');
593
+ }
594
+ if (observed.main && !observed.candidate && !observed.rescue) {
595
+ await createDatabase({ runtime, state, database: candidateDatabase, signal: params.signal });
596
+ let restoreFd;
597
+ try {
598
+ restoreFd = openSync(backupPath, constants.O_RDONLY | noFollow);
599
+ const held = fstatSync(backupFd, { bigint: true });
600
+ const input = fstatSync(restoreFd, { bigint: true });
601
+ if (held.dev !== input.dev || held.ino !== input.ino || held.size !== input.size || input.nlink !== 1n) {
602
+ throw new Error('community_native_snapshot_backup_identity_changed');
603
+ }
604
+ await runDocker({
605
+ runtime,
606
+ args: ['container', 'exec', '-i', state.containerName, 'pg_restore', '-U', 'aops', '-d', candidateDatabase,
607
+ '--no-owner', '--no-acl', '--exit-on-error'],
608
+ operation: 'managed_restore_candidate',
609
+ inputFd: restoreFd,
610
+ signal: params.signal,
611
+ });
612
+ }
613
+ finally {
614
+ if (restoreFd !== undefined)
615
+ closeSync(restoreFd);
616
+ }
617
+ await phase('candidate-restored');
618
+ observed = await inspectRestoreDatabases({ runtime, state, candidateDatabase, rescueDatabase, signal: params.signal });
619
+ }
620
+ if (observed.main && observed.candidate && !observed.rescue) {
621
+ await verifyDatabase(candidateDatabase);
622
+ await phase('candidate-verified');
623
+ await renameDatabase({ runtime, state, from: 'aops', to: rescueDatabase, signal: params.signal });
624
+ await phase('source-preserved');
625
+ observed = await inspectRestoreDatabases({ runtime, state, candidateDatabase, rescueDatabase, signal: params.signal });
626
+ }
627
+ if (!observed.main && observed.candidate && observed.rescue) {
628
+ await verifyDatabase(candidateDatabase);
629
+ await renameDatabase({ runtime, state, from: candidateDatabase, to: 'aops', signal: params.signal });
630
+ await phase('target-promoted');
631
+ observed = await inspectRestoreDatabases({ runtime, state, candidateDatabase, rescueDatabase, signal: params.signal });
632
+ }
633
+ if (!observed.main || observed.candidate || !observed.rescue) {
634
+ throw new Error('community_native_managed_restore_database_state_ambiguous');
635
+ }
636
+ const restored = await verifyDatabase('aops');
637
+ const finalBackup = hashHeldFile(backupFd, false);
638
+ if (finalBackup.sha256 !== initialBackup.sha256 || finalBackup.byteLength !== initialBackup.byteLength) {
639
+ throw new Error('community_native_snapshot_backup_identity_changed');
640
+ }
641
+ await phase('target-verified');
642
+ completed = true;
643
+ return {
644
+ restoreId: params.restoreId,
645
+ evidencePath: read.evidencePath,
646
+ evidenceSha256: read.evidenceSha256,
647
+ backupPath,
648
+ backupSha256: finalBackup.sha256,
649
+ backupByteLength: finalBackup.byteLength,
650
+ candidateDatabase,
651
+ rescueDatabase,
652
+ restoredLineageId: restored.lineageId,
653
+ restoredMigrationStateFingerprintSha256: restored.sourceFingerprintSha256,
654
+ restoredSchemaFingerprintSha256: restored.schemaFingerprintSha256,
655
+ restoredReceiptFingerprintSha256: restored.receiptFingerprintSha256,
656
+ restoredDataFingerprintSha256: sourceDataFingerprint(restored),
657
+ restoredStateFingerprintSha256: restored.stateFingerprintSha256,
658
+ resumed,
659
+ };
660
+ }
661
+ catch (error) {
662
+ if (!completed) {
663
+ try {
664
+ await recoverOriginalDatabase({ runtime, state, candidateDatabase, rescueDatabase });
665
+ }
666
+ catch (recoveryError) {
667
+ const original = error instanceof Error ? error.message : String(error);
668
+ const recovery = recoveryError instanceof Error ? recoveryError.message : String(recoveryError);
669
+ throw new Error(`${original}:automatic_source_recovery_failed:${recovery}`);
670
+ }
671
+ }
672
+ throw error;
673
+ }
674
+ finally {
675
+ closeSync(backupFd);
676
+ }
677
+ }
678
+ export async function cleanupCommunityNativeManagedRestoreRescueV1(params) {
679
+ if (!UUID.test(params.restoreId) || !RAW_SHA256.test(params.expectedCompletedReceiptSha256) ||
680
+ !path.isAbsolute(params.sourceRoot)) {
681
+ throw new Error('community_native_managed_restore_cleanup_proof_invalid');
682
+ }
683
+ const state = assertCommunityNativePostgresInstanceState({
684
+ state: params.state,
685
+ instanceName: params.instanceName,
686
+ instanceRoot: params.instanceRoot,
687
+ });
688
+ const runtime = params.runtime ?? communityNativePostgresRuntime;
689
+ const candidateDatabase = assertDatabaseName(`aops_restore_${params.restoreId.replaceAll('-', '').toLowerCase()}`);
690
+ const rescueDatabase = assertDatabaseName(`aops_rescue_${params.restoreId.replaceAll('-', '').toLowerCase()}`);
691
+ const receiptRoot = ensureCanonicalDirectory(params.receiptRoot, 'community_native_database_restore_root_unsafe');
692
+ const receiptPath = path.resolve(params.completedReceiptPath);
693
+ if (!samePath(path.dirname(receiptPath), receiptRoot) || !existsSync(receiptPath)) {
694
+ throw new Error('community_native_managed_restore_cleanup_receipt_invalid');
695
+ }
696
+ const noFollow = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
697
+ let receiptFd;
698
+ let receipt;
699
+ try {
700
+ const pathStats = lstatSync(receiptPath, { bigint: true });
701
+ if (!pathStats.isFile() || pathStats.isSymbolicLink() || pathStats.size < 1n ||
702
+ pathStats.size > BigInt(MAX_EVIDENCE_BYTES) || !samePath(realpathSync.native(receiptPath), receiptPath)) {
703
+ throw new Error('community_native_managed_restore_cleanup_receipt_invalid');
704
+ }
705
+ receiptFd = openSync(receiptPath, constants.O_RDONLY | noFollow);
706
+ const before = fstatSync(receiptFd, { bigint: true });
707
+ if (before.dev !== pathStats.dev || before.ino !== pathStats.ino || before.size !== pathStats.size ||
708
+ before.nlink !== 1n) {
709
+ throw new Error('community_native_managed_restore_cleanup_receipt_invalid');
710
+ }
711
+ const parsed = JSON.parse(readFileSync(receiptFd, 'utf8'));
712
+ const after = fstatSync(receiptFd, { bigint: true });
713
+ if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size ||
714
+ after.nlink !== before.nlink || after.mtimeNs !== before.mtimeNs || !isRecord(parsed)) {
715
+ throw new Error('community_native_managed_restore_cleanup_receipt_invalid');
716
+ }
717
+ receipt = parsed;
718
+ }
719
+ catch {
720
+ throw new Error('community_native_managed_restore_cleanup_receipt_invalid');
721
+ }
722
+ finally {
723
+ if (receiptFd !== undefined)
724
+ closeSync(receiptFd);
725
+ }
726
+ exactKeys(receipt, [
727
+ 'schemaVersion', 'status', 'restoreId', 'updateId', 'instanceName', 'completedAt', 'recoveryOwner',
728
+ 'evidenceKind', 'evidenceSha256', 'restoredLineageId', 'restoredMigrationStateFingerprintSha256',
729
+ 'restoredSchemaFingerprintSha256', 'restoredReceiptFingerprintSha256', 'restoredDataFingerprintSha256',
730
+ 'restoredStateFingerprintSha256', 'rescueDatabase',
731
+ ], 'community_native_managed_restore_cleanup_receipt_invalid');
732
+ if (sha256(JSON.stringify(receipt)) !== params.expectedCompletedReceiptSha256 ||
733
+ receipt.schemaVersion !== 1 || receipt.status !== 'community-native-database-restored' ||
734
+ receipt.restoreId !== params.restoreId || receipt.instanceName !== params.instanceName ||
735
+ receipt.recoveryOwner !== 'managed' || receipt.evidenceKind !== 'managed-verified-backup' ||
736
+ receipt.rescueDatabase !== rescueDatabase || Number.isNaN(Date.parse(String(receipt.completedAt)))) {
737
+ throw new Error('community_native_managed_restore_cleanup_receipt_invalid');
738
+ }
739
+ const evidenceRead = readCommunityNativeSnapshotEvidenceV1({
740
+ evidenceRoot: params.evidenceRoot,
741
+ evidencePath: params.evidencePath,
742
+ expectedEvidenceSha256: params.expectedEvidenceSha256,
743
+ expectedKind: 'managed-verified-backup',
744
+ });
745
+ if (receipt.evidenceSha256 !== evidenceRead.evidenceSha256 ||
746
+ receipt.restoredLineageId !== evidenceRead.evidence.sourceLineageId ||
747
+ receipt.restoredMigrationStateFingerprintSha256 !==
748
+ evidenceRead.evidence.sourceMigrationStateFingerprintSha256 ||
749
+ receipt.restoredSchemaFingerprintSha256 !== evidenceRead.evidence.sourceSchemaFingerprintSha256 ||
750
+ receipt.restoredReceiptFingerprintSha256 !== evidenceRead.evidence.sourceReceiptFingerprintSha256 ||
751
+ receipt.restoredDataFingerprintSha256 !== evidenceRead.evidence.sourceDataFingerprintSha256 ||
752
+ receipt.restoredStateFingerprintSha256 !== evidenceRead.evidence.sourceStateFingerprintSha256) {
753
+ throw new Error('community_native_managed_restore_cleanup_receipt_mismatch');
754
+ }
755
+ const policy = readCommunityNativeMigrationPolicy(params.sourceRoot);
756
+ const restored = await (params.planStrictPgSchema ?? planCommunityStrictPgSchema)({
757
+ repoUrl: buildCommunityNativePostgresUrl(state, 'aops'),
758
+ workspaceRoot: params.sourceRoot,
759
+ policy,
760
+ });
761
+ assertCommunityNativeRestoredSnapshotStateV1(evidenceRead.evidence, restored);
762
+ const observed = await inspectRestoreDatabases({ runtime, state, candidateDatabase, rescueDatabase, signal: params.signal });
763
+ if (!observed.main || observed.candidate) {
764
+ throw new Error('community_native_managed_restore_cleanup_state_invalid');
765
+ }
766
+ if (!observed.rescue)
767
+ return { rescueDatabase, dropped: false };
768
+ await dropDatabase({ runtime, state, database: rescueDatabase, signal: params.signal });
769
+ return { rescueDatabase, dropped: true };
770
+ }