@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,236 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { closeSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync, } from 'node:fs';
3
+ import { homedir, tmpdir } from 'node:os';
4
+ import { createServer } from 'node:net';
5
+ import path from 'node:path';
6
+ const RUN_NAME = /^aops-commercial-smoke-[a-f0-9]{32}$/;
7
+ const FORBIDDEN_PORTS = new Set([5432, 5900, 5922]);
8
+ const CLAIM_FILE = '.aops-commercial-smoke-claim.json';
9
+ const verifiedIsolations = new WeakSet();
10
+ const verifiedPortLeases = new WeakSet();
11
+ function fail(reason) {
12
+ throw new Error(`commercial_developer_host_smoke_${reason}`);
13
+ }
14
+ function samePath(left, right) {
15
+ const resolvedLeft = path.resolve(left);
16
+ const resolvedRight = path.resolve(right);
17
+ return process.platform === 'win32'
18
+ ? resolvedLeft.toLowerCase() === resolvedRight.toLowerCase()
19
+ : resolvedLeft === resolvedRight;
20
+ }
21
+ function within(root, candidate) {
22
+ const relative = path.relative(path.resolve(root), path.resolve(candidate));
23
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
24
+ }
25
+ function overlaps(left, right) {
26
+ return within(left, right) || within(right, left);
27
+ }
28
+ function protectedRoots(environment) {
29
+ const home = path.resolve(environment.USERPROFILE || environment.HOME || homedir());
30
+ const roots = [path.join(home, '.aops')];
31
+ if (process.platform === 'win32') {
32
+ const localAppData = environment.LOCALAPPDATA;
33
+ if (!localAppData)
34
+ fail('local_app_data_missing');
35
+ roots.push(path.join(path.resolve(localAppData), 'AOPS'));
36
+ }
37
+ else if (process.platform === 'darwin') {
38
+ roots.push(path.join(home, 'Library', 'Application Support', 'AOPS'));
39
+ }
40
+ else {
41
+ roots.push(path.join(path.resolve(environment.XDG_DATA_HOME || path.join(home, '.local', 'share')), 'aops'));
42
+ roots.push(path.join(path.resolve(environment.XDG_STATE_HOME || path.join(home, '.local', 'state')), 'aops'));
43
+ roots.push(path.join(path.resolve(environment.XDG_CONFIG_HOME || path.join(home, '.config')), 'aops'));
44
+ }
45
+ return Object.freeze(roots.map((root) => path.resolve(root)));
46
+ }
47
+ function assertPlainCanonicalDirectory(candidate, reason) {
48
+ let stat;
49
+ try {
50
+ stat = lstatSync(candidate);
51
+ }
52
+ catch {
53
+ return fail(reason);
54
+ }
55
+ if (!stat.isDirectory() || stat.isSymbolicLink())
56
+ fail(reason);
57
+ const canonical = realpathSync.native(candidate);
58
+ if (!samePath(canonical, candidate))
59
+ fail(reason);
60
+ return canonical;
61
+ }
62
+ function assertAbsent(candidate) {
63
+ try {
64
+ lstatSync(candidate);
65
+ }
66
+ catch (error) {
67
+ if (error.code === 'ENOENT')
68
+ return;
69
+ fail('run_root_probe_failed');
70
+ }
71
+ fail('run_root_preexisting');
72
+ }
73
+ function claimMaterial(isolation) {
74
+ return `${JSON.stringify({
75
+ schemaVersion: isolation.schemaVersion,
76
+ objectType: isolation.objectType,
77
+ runId: isolation.runId,
78
+ runRoot: isolation.runRoot,
79
+ runRootProvenAbsent: isolation.runRootProvenAbsent,
80
+ hostClass: isolation.hostClass,
81
+ })}\n`;
82
+ }
83
+ export function claimCommercialDeveloperHostIsolation(options) {
84
+ const environment = options.environment ?? process.env;
85
+ if (!path.isAbsolute(options.runRoot))
86
+ fail('run_root_not_absolute');
87
+ const runRoot = path.resolve(options.runRoot);
88
+ if (protectedRoots(environment).some((protectedRoot) => overlaps(protectedRoot, runRoot))) {
89
+ fail('protected_root_refused');
90
+ }
91
+ const disposableRoot = assertPlainCanonicalDirectory(path.resolve(options.disposableRoot ?? tmpdir()), 'disposable_root_unsafe');
92
+ if (!samePath(path.dirname(runRoot), disposableRoot) || !RUN_NAME.test(path.basename(runRoot))) {
93
+ fail('run_root_not_disposable_child');
94
+ }
95
+ assertAbsent(runRoot);
96
+ try {
97
+ mkdirSync(runRoot, { recursive: false, mode: 0o700 });
98
+ }
99
+ catch {
100
+ return fail('run_root_claim_failed');
101
+ }
102
+ const canonical = assertPlainCanonicalDirectory(runRoot, 'run_root_claim_invalid');
103
+ const runId = randomUUID();
104
+ const isolation = Object.freeze({
105
+ schemaVersion: 1,
106
+ objectType: 'aops-commercial-developer-host-isolation-v1',
107
+ runId,
108
+ runRoot: canonical,
109
+ paths: Object.freeze({
110
+ installRoot: path.join(canonical, 'install'),
111
+ journalRoot: path.join(canonical, 'operation-journals'),
112
+ storeRoot: path.join(canonical, 'commercial-store'),
113
+ runtimeRoot: path.join(canonical, 'runtime'),
114
+ postgresRoot: path.join(canonical, 'postgres'),
115
+ }),
116
+ runRootProvenAbsent: true,
117
+ hostClass: 'developer-host',
118
+ });
119
+ const claimPath = path.join(canonical, CLAIM_FILE);
120
+ let descriptor;
121
+ try {
122
+ descriptor = openSync(claimPath, 'wx', 0o600);
123
+ writeFileSync(descriptor, claimMaterial(isolation), 'utf8');
124
+ }
125
+ catch {
126
+ if (descriptor !== undefined)
127
+ closeSync(descriptor);
128
+ rmSync(canonical, { recursive: true, force: false });
129
+ return fail('run_root_claim_file_failed');
130
+ }
131
+ closeSync(descriptor);
132
+ verifiedIsolations.add(isolation);
133
+ return isolation;
134
+ }
135
+ export function assertCommercialDeveloperHostIsolation(isolation) {
136
+ if (!verifiedIsolations.has(isolation))
137
+ fail('isolation_unverified');
138
+ const canonical = assertPlainCanonicalDirectory(isolation.runRoot, 'run_root_changed');
139
+ if (!samePath(canonical, isolation.runRoot))
140
+ fail('run_root_changed');
141
+ const claimPath = path.join(canonical, CLAIM_FILE);
142
+ let claim;
143
+ try {
144
+ claim = readFileSync(claimPath, 'utf8');
145
+ }
146
+ catch {
147
+ return fail('claim_missing');
148
+ }
149
+ if (claim !== claimMaterial(isolation))
150
+ fail('claim_changed');
151
+ return isolation;
152
+ }
153
+ function assertSafeTree(candidate) {
154
+ const stat = lstatSync(candidate);
155
+ if (stat.isSymbolicLink())
156
+ fail('cleanup_reparse_refused');
157
+ if (!stat.isDirectory())
158
+ return;
159
+ const names = readdirSync(candidate);
160
+ for (const name of names)
161
+ assertSafeTree(path.join(candidate, name));
162
+ }
163
+ export function cleanupCommercialDeveloperHostIsolation(isolation) {
164
+ const verified = assertCommercialDeveloperHostIsolation(isolation);
165
+ assertSafeTree(verified.runRoot);
166
+ rmSync(verified.runRoot, { recursive: true, force: false });
167
+ verifiedIsolations.delete(isolation);
168
+ }
169
+ function listenEphemeral() {
170
+ return new Promise((resolve, reject) => {
171
+ const server = createServer();
172
+ server.unref();
173
+ server.once('error', reject);
174
+ server.listen({ host: '127.0.0.1', port: 0, exclusive: true }, () => {
175
+ server.removeListener('error', reject);
176
+ const address = server.address();
177
+ if (!address || typeof address === 'string' || FORBIDDEN_PORTS.has(address.port)) {
178
+ server.close(() => reject(new Error('commercial_developer_host_smoke_forbidden_port_allocated')));
179
+ return;
180
+ }
181
+ resolve(Object.freeze({ server, port: address.port }));
182
+ });
183
+ });
184
+ }
185
+ function closeServer(server) {
186
+ return new Promise((resolve, reject) => {
187
+ if (!server.listening)
188
+ return resolve();
189
+ server.close((error) => error ? reject(error) : resolve());
190
+ });
191
+ }
192
+ export async function reserveCommercialDeveloperHostPorts(isolation) {
193
+ assertCommercialDeveloperHostIsolation(isolation);
194
+ const roles = ['postgres', 'server', 'cockpit', 'registry'];
195
+ const entries = new Map();
196
+ try {
197
+ for (const role of roles)
198
+ entries.set(role, await listenEphemeral());
199
+ }
200
+ catch (error) {
201
+ await Promise.allSettled([...entries.values()].map((entry) => closeServer(entry.server)));
202
+ throw error;
203
+ }
204
+ const released = new Set();
205
+ const lease = Object.freeze({
206
+ schemaVersion: 1,
207
+ objectType: 'aops-commercial-developer-host-port-lease-v1',
208
+ ports: Object.freeze(Object.fromEntries(roles.map((role) => [role, entries.get(role).port]))),
209
+ release: async (role) => {
210
+ if (!verifiedPortLeases.has(lease) || !roles.includes(role))
211
+ fail('port_lease_unverified');
212
+ if (released.has(role))
213
+ return;
214
+ await closeServer(entries.get(role).server);
215
+ released.add(role);
216
+ },
217
+ releaseAll: async () => {
218
+ if (!verifiedPortLeases.has(lease))
219
+ fail('port_lease_unverified');
220
+ await Promise.all(roles.map((role) => lease.release(role)));
221
+ verifiedPortLeases.delete(lease);
222
+ },
223
+ });
224
+ verifiedPortLeases.add(lease);
225
+ return lease;
226
+ }
227
+ export function assertCommercialDeveloperHostAllocatedPort(lease, role, observedPort) {
228
+ if (!verifiedPortLeases.has(lease))
229
+ fail('port_lease_unverified');
230
+ if (!Number.isSafeInteger(observedPort) || FORBIDDEN_PORTS.has(observedPort)) {
231
+ fail('forbidden_port_refused');
232
+ }
233
+ if (lease.ports[role] !== observedPort)
234
+ fail('port_not_allocated');
235
+ return observedPort;
236
+ }
@@ -0,0 +1,274 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { lstatSync, mkdirSync, writeFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { Client } from 'pg';
5
+ import { assertCommercialDeveloperHostAllocatedPort, assertCommercialDeveloperHostIsolation, } from './commercial-lifecycle-smoke-isolation.js';
6
+ function fail(reason) {
7
+ throw new Error(`commercial_managed_postgres_${reason}`);
8
+ }
9
+ function exactLabels(expected, observed) {
10
+ const expectedKeys = Object.keys(expected).sort();
11
+ const observedKeys = Object.keys(observed).filter((key) => key.startsWith('aops.')).sort();
12
+ return expectedKeys.length === observedKeys.length &&
13
+ expectedKeys.every((key, index) => key === observedKeys[index] && expected[key] === observed[key]);
14
+ }
15
+ function parseJson(value, reason) {
16
+ try {
17
+ return JSON.parse(value);
18
+ }
19
+ catch {
20
+ return fail(reason);
21
+ }
22
+ }
23
+ function defaultDockerRun(command) {
24
+ return (args) => new Promise((resolve, reject) => {
25
+ execFile(command, [...args], {
26
+ windowsHide: true,
27
+ encoding: 'utf8',
28
+ maxBuffer: 8 * 1024 * 1024,
29
+ env: Object.fromEntries(['SystemRoot', 'WINDIR', 'PATH', 'TEMP', 'TMP']
30
+ .flatMap((key) => process.env[key] ? [[key, process.env[key]]] : [])),
31
+ }, (error, stdout, stderr) => {
32
+ if (error && typeof error.code !== 'number') {
33
+ reject(new Error('commercial_managed_postgres_docker_execution_failed'));
34
+ return;
35
+ }
36
+ resolve(Object.freeze({
37
+ exitCode: typeof error?.code === 'number'
38
+ ? Number(error.code)
39
+ : 0,
40
+ stdout,
41
+ stderr,
42
+ }));
43
+ });
44
+ });
45
+ }
46
+ function defaultPortOwnerInspect(port) {
47
+ if (process.platform !== 'win32')
48
+ return Promise.resolve(null);
49
+ const systemRoot = process.env.SystemRoot ?? process.env.WINDIR ?? 'C:\\Windows';
50
+ const netstat = path.join(systemRoot, 'System32', 'netstat.exe');
51
+ return new Promise((resolve, reject) => {
52
+ execFile(netstat, ['-ano', '-p', 'tcp'], {
53
+ windowsHide: true,
54
+ encoding: 'utf8',
55
+ maxBuffer: 8 * 1024 * 1024,
56
+ }, (error, stdout) => {
57
+ if (error)
58
+ return reject(new Error('commercial_managed_postgres_port_owner_inspect_failed'));
59
+ const owners = new Set();
60
+ for (const line of stdout.split(/\r?\n/)) {
61
+ const match = /^\s*TCP\s+(\S+):(\d+)\s+\S+\s+LISTENING\s+(\d+)\s*$/i.exec(line);
62
+ if (match && Number(match[2]) === port)
63
+ owners.add(Number(match[3]));
64
+ }
65
+ if (owners.size > 1)
66
+ return reject(new Error('commercial_managed_postgres_port_owner_ambiguous'));
67
+ resolve([...owners][0] ?? null);
68
+ });
69
+ });
70
+ }
71
+ async function requiredRun(run, args, reason) {
72
+ const result = await run(args);
73
+ if (result.exitCode !== 0)
74
+ fail(reason);
75
+ return result;
76
+ }
77
+ async function inspectOptionalJson(run, args, reason) {
78
+ const result = await run(args);
79
+ if (result.exitCode !== 0) {
80
+ if (/No such (?:container|object|volume)/i.test(result.stderr))
81
+ return null;
82
+ fail(reason);
83
+ }
84
+ return parseJson(result.stdout.trim(), reason);
85
+ }
86
+ function assertSelectionBoundary(isolation, lease, selection) {
87
+ assertCommercialDeveloperHostIsolation(isolation);
88
+ if (selection.coordinates.host !== '127.0.0.1')
89
+ fail('non_loopback_refused');
90
+ if (selection.coordinates.port === 5432)
91
+ fail('ownership_refused_before_statement_live_port');
92
+ assertCommercialDeveloperHostAllocatedPort(lease, 'postgres', selection.coordinates.port);
93
+ }
94
+ function resourceLabels(value, reason) {
95
+ if (!value || typeof value !== 'object' || Array.isArray(value))
96
+ fail(reason);
97
+ const result = {};
98
+ for (const [key, candidate] of Object.entries(value)) {
99
+ if (typeof candidate !== 'string')
100
+ fail(reason);
101
+ result[key] = candidate;
102
+ }
103
+ return Object.freeze(result);
104
+ }
105
+ export function createCommercialManagedPostgresDockerAdapter(options) {
106
+ const isolation = assertCommercialDeveloperHostIsolation(options.isolation);
107
+ if (!path.isAbsolute(options.dockerCommand))
108
+ fail('docker_command_not_absolute');
109
+ const dockerStat = lstatSync(options.dockerCommand);
110
+ if (!dockerStat.isFile() || dockerStat.isSymbolicLink())
111
+ fail('docker_command_unsafe');
112
+ const run = options.dockerRun ?? defaultDockerRun(options.dockerCommand);
113
+ const inspectPortOwner = options.inspectPortOwner ?? defaultPortOwnerInspect;
114
+ const createClient = options.createClient ?? ((connectionString) => new Client({ connectionString }));
115
+ const inspectManaged = async (selection) => {
116
+ assertSelectionBoundary(isolation, options.portLease, selection);
117
+ const runtime = await run(['info', '--format', '{{json .ServerVersion}}']);
118
+ if (runtime.exitCode !== 0) {
119
+ return Object.freeze({ runtime: 'absent', portOwner: null, container: null, volume: null });
120
+ }
121
+ const containerInspect = await inspectOptionalJson(run, [
122
+ 'container', 'inspect', selection.resources.containerName,
123
+ ], 'container_inspect_failed');
124
+ const volumeInspect = await inspectOptionalJson(run, [
125
+ 'volume', 'inspect', selection.resources.volumeName,
126
+ ], 'volume_inspect_failed');
127
+ let container = null;
128
+ if (containerInspect) {
129
+ if (containerInspect.length !== 1)
130
+ fail('container_inspect_invalid');
131
+ const observed = containerInspect[0];
132
+ const bindings = observed?.HostConfig?.PortBindings?.['5432/tcp'];
133
+ const binding = Array.isArray(bindings) && bindings.length === 1 ? bindings[0] : null;
134
+ container = Object.freeze({
135
+ kind: 'container',
136
+ name: selection.resources.containerName,
137
+ labels: resourceLabels(observed?.Config?.Labels ?? {}, 'container_labels_invalid'),
138
+ imageRef: String(observed?.Config?.Image ?? ''),
139
+ imageDigest: selection.imageDigest,
140
+ bindHost: String(binding?.HostIp ?? ''),
141
+ hostPort: Number(binding?.HostPort),
142
+ volumeName: String(observed?.Mounts?.[0]?.Name ?? ''),
143
+ id: String(observed?.Id ?? ''),
144
+ });
145
+ }
146
+ let volume = null;
147
+ if (volumeInspect) {
148
+ if (volumeInspect.length !== 1)
149
+ fail('volume_inspect_invalid');
150
+ const observed = volumeInspect[0];
151
+ volume = Object.freeze({
152
+ kind: 'volume',
153
+ name: selection.resources.volumeName,
154
+ labels: resourceLabels(observed?.Labels ?? {}, 'volume_labels_invalid'),
155
+ });
156
+ }
157
+ const osPortOwner = await inspectPortOwner(selection.coordinates.port);
158
+ return Object.freeze({
159
+ runtime: 'ready',
160
+ portOwner: osPortOwner === null
161
+ ? null
162
+ : container && exactLabels(selection.resources.labels, container.labels)
163
+ ? selection.resources.containerName
164
+ : `pid:${osPortOwner}`,
165
+ container,
166
+ volume,
167
+ });
168
+ };
169
+ const assertOwnedBeforeStatement = async (selection) => {
170
+ const snapshot = await inspectManaged(selection);
171
+ if (snapshot.runtime !== 'ready' || snapshot.portOwner !== selection.resources.containerName ||
172
+ !snapshot.container || !snapshot.volume ||
173
+ !exactLabels(selection.resources.labels, snapshot.container.labels) ||
174
+ !exactLabels(selection.resources.labels, snapshot.volume.labels) ||
175
+ snapshot.container.imageRef !== selection.imageRef ||
176
+ snapshot.container.imageDigest !== selection.imageDigest ||
177
+ snapshot.container.bindHost !== '127.0.0.1' ||
178
+ snapshot.container.hostPort !== selection.coordinates.port ||
179
+ snapshot.container.volumeName !== selection.resources.volumeName)
180
+ fail('ownership_refused_before_statement');
181
+ };
182
+ return Object.freeze({
183
+ inspectManaged,
184
+ ensureManagedCredential: async (reference) => {
185
+ await options.credentials.ensure(reference.keyId);
186
+ const value = await options.credentials.read(reference.keyId);
187
+ if (!/^[A-Za-z0-9_-]{32,}$/.test(value))
188
+ fail('credential_invalid');
189
+ },
190
+ createManagedVolume: async (selection) => {
191
+ assertSelectionBoundary(isolation, options.portLease, selection);
192
+ await requiredRun(run, [
193
+ 'volume', 'create',
194
+ ...Object.entries(selection.resources.labels).flatMap(([key, value]) => ['--label', `${key}=${value}`]),
195
+ selection.resources.volumeName,
196
+ ], 'volume_create_failed');
197
+ const snapshot = await inspectManaged(selection);
198
+ if (!snapshot.volume || !exactLabels(selection.resources.labels, snapshot.volume.labels)) {
199
+ fail('volume_create_unverified');
200
+ }
201
+ },
202
+ createManagedContainer: async (selection) => {
203
+ assertSelectionBoundary(isolation, options.portLease, selection);
204
+ const image = await run(['image', 'inspect', selection.imageRef]);
205
+ if (image.exitCode !== 0) {
206
+ await requiredRun(run, ['image', 'pull', selection.imageRef], 'image_pull_failed');
207
+ }
208
+ await requiredRun(run, ['image', 'inspect', selection.imageRef], 'image_digest_unverified');
209
+ const password = await options.credentials.read(selection.coordinates.credential.keyId);
210
+ if (!/^[A-Za-z0-9_-]{32,}$/.test(password))
211
+ fail('credential_invalid');
212
+ mkdirSync(isolation.paths.postgresRoot, { recursive: true, mode: 0o700 });
213
+ const envFile = path.join(isolation.paths.postgresRoot, 'managed-postgres.env');
214
+ writeFileSync(envFile, `POSTGRES_DB=${selection.coordinates.database}\nPOSTGRES_USER=${selection.coordinates.user}\nPOSTGRES_PASSWORD=${password}\n`, { flag: 'wx', mode: 0o600 });
215
+ await requiredRun(run, [
216
+ 'container', 'create', '--name', selection.resources.containerName,
217
+ ...Object.entries(selection.resources.labels).flatMap(([key, value]) => ['--label', `${key}=${value}`]),
218
+ '--restart', 'no',
219
+ '--publish', `127.0.0.1:${selection.coordinates.port}:5432`,
220
+ '--mount', `type=volume,source=${selection.resources.volumeName},target=/var/lib/postgresql/data`,
221
+ '--env-file', envFile,
222
+ '--health-cmd', `pg_isready -U ${selection.coordinates.user} -d ${selection.coordinates.database}`,
223
+ '--health-interval', '1s', '--health-timeout', '5s', '--health-retries', '60',
224
+ selection.imageRef,
225
+ 'postgres', '-c', 'log_statement=all',
226
+ ], 'container_create_failed');
227
+ const snapshot = await inspectManaged(selection);
228
+ if (!snapshot.container || !exactLabels(selection.resources.labels, snapshot.container.labels)) {
229
+ fail('container_create_unverified');
230
+ }
231
+ },
232
+ startManagedContainer: async (selection) => {
233
+ assertSelectionBoundary(isolation, options.portLease, selection);
234
+ await requiredRun(run, ['container', 'start', selection.resources.containerName], 'container_start_failed');
235
+ const deadline = Date.now() + 90_000;
236
+ for (;;) {
237
+ const health = await requiredRun(run, [
238
+ 'container', 'inspect', '--format', '{{json .State.Health.Status}}',
239
+ selection.resources.containerName,
240
+ ], 'container_health_inspect_failed');
241
+ const status = parseJson(health.stdout.trim(), 'container_health_invalid');
242
+ if (status === 'healthy')
243
+ break;
244
+ if (status === 'unhealthy')
245
+ fail('container_unhealthy');
246
+ if (Date.now() >= deadline)
247
+ fail('container_ready_timeout');
248
+ await new Promise((resolve) => setTimeout(resolve, 250));
249
+ }
250
+ },
251
+ removeManagedContainer: async (selection) => {
252
+ await assertOwnedBeforeStatement(selection);
253
+ await requiredRun(run, ['container', 'rm', '--force', selection.resources.containerName], 'container_remove_failed');
254
+ },
255
+ openReadOnly: async (selection) => {
256
+ if (selection.mode !== 'aops-managed-container')
257
+ fail('managed_surface_only');
258
+ await assertOwnedBeforeStatement(selection);
259
+ const password = await options.credentials.read(selection.coordinates.credential.keyId);
260
+ const url = new URL('postgresql://127.0.0.1/');
261
+ url.username = selection.coordinates.user;
262
+ url.password = password;
263
+ url.port = String(selection.coordinates.port);
264
+ url.pathname = `/${selection.coordinates.database}`;
265
+ url.searchParams.set('sslmode', 'disable');
266
+ const client = createClient(url.toString());
267
+ await client.connect();
268
+ return Object.freeze({
269
+ query: async (text) => (await client.query(text)).rows,
270
+ close: async () => { await client.end(); },
271
+ });
272
+ },
273
+ });
274
+ }
@@ -0,0 +1,179 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { applyCommunityStrictPgSchema, } from '@aopslabs/aops-pg-bootstrap';
3
+ import { Client } from 'pg';
4
+ import { runCommunityNativeMigration, } from './community-native-migration.js';
5
+ const SHA256 = /^sha256:[a-f0-9]{64}$/;
6
+ function stableLockCoordinates(migrationSetDigest) {
7
+ if (!SHA256.test(migrationSetDigest))
8
+ throw new Error('commercial_setup_migration_digest_invalid');
9
+ const digest = createHash('sha256')
10
+ .update('aops-commercial-setup-production-migration-lock-v1\0', 'utf8')
11
+ .update(migrationSetDigest, 'utf8')
12
+ .digest();
13
+ return Object.freeze({
14
+ lockId: `migration:${digest.toString('hex')}`,
15
+ classId: digest.readInt32BE(0),
16
+ objectId: digest.readInt32BE(4),
17
+ migrationSetDigest,
18
+ });
19
+ }
20
+ export function productionMigrationLockCoordinates(migrationSetDigest) {
21
+ return stableLockCoordinates(migrationSetDigest);
22
+ }
23
+ export function createPgProductionMigrationSessionAuthority(options) {
24
+ return async (request) => {
25
+ let connectionString = '';
26
+ try {
27
+ connectionString = await options.readConnectionString();
28
+ if (!connectionString || /[\u0000\r\n]/.test(connectionString)) {
29
+ throw new Error('invalid-private-channel');
30
+ }
31
+ }
32
+ catch {
33
+ throw new Error('commercial_setup_postgres_credential_unavailable');
34
+ }
35
+ const client = options.createClient?.(connectionString) ??
36
+ new Client({ connectionString });
37
+ try {
38
+ await client.connect();
39
+ const lock = await client.query('SELECT pg_try_advisory_lock($1, $2) AS acquired', [request.classId, request.objectId]);
40
+ if (lock.rows[0]?.acquired !== true) {
41
+ await client.end().catch(() => undefined);
42
+ return null;
43
+ }
44
+ }
45
+ catch {
46
+ await client.end().catch(() => undefined);
47
+ throw new Error('commercial_setup_migration_lock_open_failed');
48
+ }
49
+ let released = false;
50
+ const release = async () => {
51
+ if (released)
52
+ throw new Error('commercial_setup_migration_session_already_released');
53
+ released = true;
54
+ try {
55
+ const unlocked = await client.query('SELECT pg_advisory_unlock($1, $2) AS unlocked', [request.classId, request.objectId]);
56
+ if (unlocked.rows[0]?.unlocked !== true) {
57
+ throw new Error('lock-not-held');
58
+ }
59
+ }
60
+ catch {
61
+ throw new Error('commercial_setup_migration_lock_release_failed');
62
+ }
63
+ finally {
64
+ await client.end().catch(() => undefined);
65
+ connectionString = '';
66
+ }
67
+ };
68
+ return Object.freeze({
69
+ lockId: request.lockId,
70
+ apply: async (applyRequest) => {
71
+ if (released)
72
+ throw new Error('commercial_setup_migration_session_not_held');
73
+ try {
74
+ return await options.runProductionMigrations(applyRequest, { client, connectionString });
75
+ }
76
+ catch {
77
+ throw new Error('commercial_setup_production_migration_failed');
78
+ }
79
+ },
80
+ release,
81
+ });
82
+ };
83
+ }
84
+ export function createCommunityNativeProductionMigrationRunner(options) {
85
+ return async (request, context) => {
86
+ const borrowedClient = Object.freeze({
87
+ connect: async () => undefined,
88
+ query: context.client.query.bind(context.client),
89
+ end: async () => undefined,
90
+ });
91
+ await runCommunityNativeMigration({
92
+ ...options.invocation,
93
+ repoUrl: context.connectionString,
94
+ }, {
95
+ applyStrictPgSchema: async (params) => applyCommunityStrictPgSchema({
96
+ ...params,
97
+ clientFactory: () => borrowedClient,
98
+ }),
99
+ });
100
+ return Object.freeze({
101
+ status: 'applied',
102
+ appliedMigrationIds: Object.freeze(request.pending.map((migration) => migration.id)),
103
+ reconciledSystemRowIds: Object.freeze(request.systemRows.map((row) => row.id)),
104
+ failedMigrationId: null,
105
+ });
106
+ };
107
+ }
108
+ export function createExistingOfficialCatalogReadinessAuthority(options) {
109
+ return async (request) => {
110
+ const result = await options.provider.reconcile({
111
+ fromRelease: options.fromRelease,
112
+ apiBaseUrl: options.apiBaseUrl,
113
+ timeoutMs: options.timeoutMs,
114
+ idempotencyKey: `setup-readiness:${request.planId}:${request.catalogDigest}`,
115
+ });
116
+ if (result.state !== 'current' || result.historyDeleteCount !== 0 || result.activationEffects.length !== 0) {
117
+ throw new Error('commercial_setup_catalog_reconcile_not_current');
118
+ }
119
+ return Object.freeze({ reconciledEntryIds: Object.freeze([...request.entryIds]) });
120
+ };
121
+ }
122
+ export function createExistingAgentAssetsReadinessAuthority(options) {
123
+ return async (request) => {
124
+ if (!options.provider.apply)
125
+ throw new Error('commercial_setup_agent_assets_provider_unavailable');
126
+ const result = await options.provider.apply({
127
+ action: 'install',
128
+ fromRelease: options.fromRelease,
129
+ target: 'all',
130
+ });
131
+ if (result.availability !== 'available' || result.state !== 'ready') {
132
+ throw new Error('commercial_setup_agent_assets_not_ready');
133
+ }
134
+ return Object.freeze({ installedBindingKeys: Object.freeze([...request.bindingKeys]) });
135
+ };
136
+ }
137
+ function sameLockRequest(left, right) {
138
+ return left.planId === right.planId &&
139
+ left.migrationSetDigest === right.migrationSetDigest;
140
+ }
141
+ export function createCommercialSetupReadinessAdapter(authorities) {
142
+ const active = new Map();
143
+ return Object.freeze({
144
+ inspectLiveReadiness: async (...request) => authorities.inspectLiveReadiness(...request),
145
+ tryAcquireProductionMigrationLock: async (request) => {
146
+ const coordinates = stableLockCoordinates(request.migrationSetDigest);
147
+ if (active.has(coordinates.lockId))
148
+ return null;
149
+ const session = await authorities.tryOpenProductionMigrationSession({
150
+ ...request,
151
+ ...coordinates,
152
+ });
153
+ if (!session)
154
+ return null;
155
+ if (session.lockId !== coordinates.lockId) {
156
+ await session.release().catch(() => undefined);
157
+ throw new Error('commercial_setup_migration_session_binding_mismatch');
158
+ }
159
+ active.set(coordinates.lockId, Object.freeze({ request: Object.freeze({ ...request }), session }));
160
+ return Object.freeze({ lockId: coordinates.lockId });
161
+ },
162
+ applyProductionMigrations: async (request) => {
163
+ const held = active.get(request.lock.lockId);
164
+ if (!held || !sameLockRequest(held.request, request)) {
165
+ throw new Error('commercial_setup_migration_session_not_held');
166
+ }
167
+ return held.session.apply(request);
168
+ },
169
+ releaseProductionMigrationLock: async (lock) => {
170
+ const held = active.get(lock.lockId);
171
+ if (!held)
172
+ throw new Error('commercial_setup_migration_session_not_held');
173
+ active.delete(lock.lockId);
174
+ await held.session.release();
175
+ },
176
+ reconcileOfficialCatalog: async (request) => authorities.reconcileOfficialCatalog(request),
177
+ installVerifiedAgentAssets: async (request) => authorities.installVerifiedAgentAssets(request),
178
+ });
179
+ }