@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,425 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { closeSync, constants, existsSync, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, realpathSync, rmSync, writeFileSync, } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { compareCanonicalText } from '@aopslabs/artifact-trust-contracts';
5
+ 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;
6
+ const SHA256 = /^sha256:[a-f0-9]{64}$/;
7
+ const RAW_SHA256 = /^[a-f0-9]{64}$/;
8
+ const INSTANCE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?$/;
9
+ const MAX_RECORD_BYTES = 1024 * 1024;
10
+ function sha256Json(value) {
11
+ return `sha256:${createHash('sha256').update(JSON.stringify(value)).digest('hex')}`;
12
+ }
13
+ function samePath(left, right) {
14
+ const a = path.resolve(left);
15
+ const b = path.resolve(right);
16
+ return process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b;
17
+ }
18
+ function fsyncDirectoryBestEffortOnWindows(directory) {
19
+ let descriptor;
20
+ try {
21
+ descriptor = openSync(directory, 'r');
22
+ fsyncSync(descriptor);
23
+ }
24
+ catch (error) {
25
+ const code = String(error?.code ?? '');
26
+ if (process.platform !== 'win32' || !['EACCES', 'EBADF', 'EINVAL', 'EISDIR', 'EPERM'].includes(code))
27
+ throw error;
28
+ }
29
+ finally {
30
+ if (descriptor !== undefined)
31
+ closeSync(descriptor);
32
+ }
33
+ }
34
+ function ensureRecordRoot(paths) {
35
+ const root = path.resolve(paths.applicationUpdateRoot);
36
+ if (!samePath(path.dirname(root), paths.runtimeRoot)) {
37
+ throw new Error('community_native_application_update_root_unsafe');
38
+ }
39
+ const existed = existsSync(root);
40
+ mkdirSync(root, { recursive: true, mode: 0o700 });
41
+ const stats = lstatSync(root);
42
+ if (!stats.isDirectory() || stats.isSymbolicLink() || !samePath(realpathSync.native(root), root)) {
43
+ throw new Error('community_native_application_update_root_unsafe');
44
+ }
45
+ if (!existed)
46
+ fsyncDirectoryBestEffortOnWindows(paths.runtimeRoot);
47
+ return root;
48
+ }
49
+ function writeExclusive(paths, fileName, value) {
50
+ const root = ensureRecordRoot(paths);
51
+ const target = path.join(root, fileName);
52
+ if (!samePath(path.dirname(target), root) || existsSync(target)) {
53
+ throw new Error('community_native_application_recovery_record_already_exists');
54
+ }
55
+ const temp = path.join(root, `.${fileName}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`);
56
+ let descriptor;
57
+ try {
58
+ descriptor = openSync(temp, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
59
+ writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8' });
60
+ fsyncSync(descriptor);
61
+ closeSync(descriptor);
62
+ descriptor = undefined;
63
+ try {
64
+ linkSync(temp, target);
65
+ }
66
+ catch (error) {
67
+ if (error.code === 'EEXIST') {
68
+ throw new Error('community_native_application_recovery_record_already_exists');
69
+ }
70
+ throw error;
71
+ }
72
+ fsyncDirectoryBestEffortOnWindows(root);
73
+ rmSync(temp, { force: true });
74
+ fsyncDirectoryBestEffortOnWindows(root);
75
+ return target;
76
+ }
77
+ finally {
78
+ if (descriptor !== undefined)
79
+ closeSync(descriptor);
80
+ rmSync(temp, { force: true });
81
+ }
82
+ }
83
+ function readRecord(paths, fileName) {
84
+ const root = ensureRecordRoot(paths);
85
+ const target = path.join(root, fileName);
86
+ if (!existsSync(target))
87
+ return null;
88
+ const stats = lstatSync(target, { bigint: true });
89
+ if (!stats.isFile() || stats.isSymbolicLink() || stats.size < 1n || stats.size > BigInt(MAX_RECORD_BYTES) ||
90
+ !samePath(realpathSync.native(target), target) || !samePath(path.dirname(target), root)) {
91
+ throw new Error('community_native_application_recovery_record_unsafe');
92
+ }
93
+ let descriptor;
94
+ try {
95
+ const noFollow = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
96
+ descriptor = openSync(target, constants.O_RDONLY | noFollow);
97
+ const before = fstatSync(descriptor, { bigint: true });
98
+ if (before.dev !== stats.dev || before.ino !== stats.ino || before.size !== stats.size || before.nlink !== 1n) {
99
+ throw new Error('community_native_application_recovery_record_unsafe');
100
+ }
101
+ const value = JSON.parse(readFileSync(descriptor, 'utf8'));
102
+ const after = fstatSync(descriptor, { bigint: true });
103
+ if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size ||
104
+ after.nlink !== before.nlink || after.mtimeNs !== before.mtimeNs) {
105
+ throw new Error('community_native_application_recovery_record_unsafe');
106
+ }
107
+ return value;
108
+ }
109
+ catch {
110
+ throw new Error('community_native_application_recovery_record_invalid');
111
+ }
112
+ finally {
113
+ if (descriptor !== undefined)
114
+ closeSync(descriptor);
115
+ }
116
+ }
117
+ function exactKeys(value, expected, code) {
118
+ const keys = Object.keys(value).sort();
119
+ const wanted = [...expected].sort();
120
+ if (keys.length !== wanted.length || keys.some((key, index) => key !== wanted[index]))
121
+ throw new Error(code);
122
+ }
123
+ function isRecord(value) {
124
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
125
+ }
126
+ function assertReference(value) {
127
+ if (!isRecord(value))
128
+ throw new Error('community_native_application_reference_invalid');
129
+ exactKeys(value, ['sourceRoot', 'sourceFingerprint', 'buildFingerprint', 'content'], 'community_native_application_reference_invalid');
130
+ if (!path.isAbsolute(String(value.sourceRoot)) || !SHA256.test(String(value.sourceFingerprint)) ||
131
+ !SHA256.test(String(value.buildFingerprint)) || !isRecord(value.content)) {
132
+ throw new Error('community_native_application_reference_invalid');
133
+ }
134
+ exactKeys(value.content, [
135
+ 'schemaVersion', 'releaseVersion', 'packageSha256', 'lockfileSha256', 'sourceFileCount',
136
+ 'sourceInventorySha256', 'hostEntrySha256', 'handlerEntrySha256', 'cockpitIndexSha256',
137
+ 'runtimeFileCount', 'runtimeInventorySha256', 'applicationContentSha256',
138
+ ], 'community_native_application_content_invalid');
139
+ if (value.content.schemaVersion !== 1 || typeof value.content.releaseVersion !== 'string' ||
140
+ !Number.isSafeInteger(value.content.sourceFileCount) || value.content.sourceFileCount < 1 ||
141
+ !Number.isSafeInteger(value.content.runtimeFileCount) || value.content.runtimeFileCount < 1 ||
142
+ ![
143
+ value.content.packageSha256, value.content.lockfileSha256, value.content.sourceInventorySha256,
144
+ value.content.hostEntrySha256, value.content.handlerEntrySha256, value.content.cockpitIndexSha256,
145
+ value.content.runtimeInventorySha256, value.content.applicationContentSha256,
146
+ ].every((item) => SHA256.test(String(item)))) {
147
+ throw new Error('community_native_application_content_invalid');
148
+ }
149
+ const content = { ...value.content };
150
+ const claimed = content.applicationContentSha256;
151
+ delete content.applicationContentSha256;
152
+ if (sha256Json(content) !== claimed)
153
+ throw new Error('community_native_application_content_hash_mismatch');
154
+ return value;
155
+ }
156
+ function migrationSummary(receipt) {
157
+ return {
158
+ action: receipt.action,
159
+ acceptedPlanSha256: receipt.acceptedPlanSha256,
160
+ sourceMigrationStateFingerprintSha256: receipt.sourceMigrationStateFingerprintSha256,
161
+ resultMigrationStateFingerprintSha256: receipt.resultMigrationStateFingerprintSha256,
162
+ snapshotEvidenceKind: receipt.snapshotEvidenceKind,
163
+ snapshotEvidenceSha256: receipt.snapshotEvidenceSha256,
164
+ };
165
+ }
166
+ function assertMigrationSummary(value) {
167
+ if (!isRecord(value))
168
+ throw new Error('community_native_application_migration_summary_invalid');
169
+ exactKeys(value, [
170
+ 'action', 'acceptedPlanSha256', 'sourceMigrationStateFingerprintSha256',
171
+ 'resultMigrationStateFingerprintSha256', 'snapshotEvidenceKind', 'snapshotEvidenceSha256',
172
+ ], 'community_native_application_migration_summary_invalid');
173
+ if (!['migrate', 'verify-only'].includes(String(value.action)) ||
174
+ ![value.acceptedPlanSha256, value.sourceMigrationStateFingerprintSha256,
175
+ value.resultMigrationStateFingerprintSha256].every((item) => RAW_SHA256.test(String(item))) ||
176
+ !['managed-verified-backup', 'external-snapshot-attestation', null].includes(value.snapshotEvidenceKind) ||
177
+ (value.snapshotEvidenceSha256 !== null && !RAW_SHA256.test(String(value.snapshotEvidenceSha256))) ||
178
+ (value.snapshotEvidenceKind === null) !== (value.snapshotEvidenceSha256 === null)) {
179
+ throw new Error('community_native_application_migration_summary_invalid');
180
+ }
181
+ return value;
182
+ }
183
+ function assertUpdateOutcome(value, updateId) {
184
+ if (!isRecord(value))
185
+ throw new Error('community_native_application_update_outcome_invalid');
186
+ exactKeys(value, ['schemaVersion', 'status', 'updateId', 'completedAt', 'migration', 'failure'], 'community_native_application_update_outcome_invalid');
187
+ if (value.schemaVersion !== 1 || value.updateId !== updateId ||
188
+ !['community-native-application-update-succeeded', 'community-native-application-update-failed'].includes(String(value.status)) ||
189
+ Number.isNaN(Date.parse(String(value.completedAt)))) {
190
+ throw new Error('community_native_application_update_outcome_invalid');
191
+ }
192
+ if (value.status === 'community-native-application-update-succeeded') {
193
+ if (value.failure !== null)
194
+ throw new Error('community_native_application_update_outcome_invalid');
195
+ return { ...value, migration: assertMigrationSummary(value.migration) };
196
+ }
197
+ if (typeof value.failure !== 'string' || value.failure.length < 1 || value.failure.length > 300) {
198
+ throw new Error('community_native_application_update_outcome_invalid');
199
+ }
200
+ return {
201
+ ...value,
202
+ migration: value.migration === null ? null : assertMigrationSummary(value.migration),
203
+ };
204
+ }
205
+ function assertRollbackPrepared(value, updateId) {
206
+ if (!isRecord(value))
207
+ throw new Error('community_native_application_rollback_prepared_invalid');
208
+ exactKeys(value, ['schemaVersion', 'status', 'updateId', 'rollbackId', 'createdAt', 'candidate', 'databasePlan'], 'community_native_application_rollback_prepared_invalid');
209
+ if (value.schemaVersion !== 1 || value.status !== 'community-native-application-rollback-prepared' ||
210
+ value.updateId !== updateId || !UUID.test(String(value.rollbackId)) ||
211
+ Number.isNaN(Date.parse(String(value.createdAt))) || !isRecord(value.databasePlan)) {
212
+ throw new Error('community_native_application_rollback_prepared_invalid');
213
+ }
214
+ exactKeys(value.databasePlan, [
215
+ 'action', 'acceptedPlanSha256', 'sourceMigrationStateFingerprintSha256', 'stateFingerprintSha256',
216
+ ], 'community_native_application_rollback_prepared_invalid');
217
+ if (value.databasePlan.action !== 'verify-only' ||
218
+ ![value.databasePlan.acceptedPlanSha256, value.databasePlan.sourceMigrationStateFingerprintSha256,
219
+ value.databasePlan.stateFingerprintSha256].every((item) => RAW_SHA256.test(String(item)))) {
220
+ throw new Error('community_native_application_rollback_prepared_invalid');
221
+ }
222
+ return {
223
+ ...value,
224
+ candidate: assertReference(value.candidate),
225
+ };
226
+ }
227
+ function assertRollbackOutcome(value, updateId, rollbackId) {
228
+ if (!isRecord(value))
229
+ throw new Error('community_native_application_rollback_outcome_invalid');
230
+ exactKeys(value, ['schemaVersion', 'status', 'updateId', 'rollbackId', 'completedAt', 'migration', 'failure'], 'community_native_application_rollback_outcome_invalid');
231
+ if (value.schemaVersion !== 1 || value.updateId !== updateId || value.rollbackId !== rollbackId ||
232
+ !['community-native-application-rolled-back', 'community-native-application-rollback-failed'].includes(String(value.status)) ||
233
+ Number.isNaN(Date.parse(String(value.completedAt)))) {
234
+ throw new Error('community_native_application_rollback_outcome_invalid');
235
+ }
236
+ if (value.status === 'community-native-application-rolled-back') {
237
+ if (value.failure !== null)
238
+ throw new Error('community_native_application_rollback_outcome_invalid');
239
+ return { ...value, migration: assertMigrationSummary(value.migration) };
240
+ }
241
+ if (typeof value.failure !== 'string' || value.failure.length < 1 || value.failure.length > 300) {
242
+ throw new Error('community_native_application_rollback_outcome_invalid');
243
+ }
244
+ return {
245
+ ...value,
246
+ migration: value.migration === null ? null : assertMigrationSummary(value.migration),
247
+ };
248
+ }
249
+ export function createCommunityNativeApplicationReferenceV1(input) {
250
+ const contentBase = {
251
+ schemaVersion: 1,
252
+ releaseVersion: input.source.releaseVersion,
253
+ packageSha256: input.source.packageSha256,
254
+ lockfileSha256: input.source.lockfileSha256,
255
+ sourceFileCount: input.source.sourceFileCount,
256
+ sourceInventorySha256: input.source.sourceInventorySha256,
257
+ hostEntrySha256: input.build.hostEntrySha256,
258
+ handlerEntrySha256: input.build.handlerEntrySha256,
259
+ cockpitIndexSha256: input.build.cockpitIndexSha256,
260
+ runtimeFileCount: input.build.runtimeFileCount,
261
+ runtimeInventorySha256: input.build.runtimeInventorySha256,
262
+ };
263
+ return assertReference({
264
+ sourceRoot: input.source.root,
265
+ sourceFingerprint: input.source.sourceFingerprint,
266
+ buildFingerprint: input.build.buildFingerprint,
267
+ content: { ...contentBase, applicationContentSha256: sha256Json(contentBase) },
268
+ });
269
+ }
270
+ export function sameCommunityNativeApplicationContent(left, right) {
271
+ return left.content.applicationContentSha256 === right.content.applicationContentSha256;
272
+ }
273
+ export function writeCommunityNativeApplicationUpdatePrepared(params) {
274
+ if (!UUID.test(params.updateId) || params.prior.instanceName !== params.target.instanceName ||
275
+ params.prior.installId !== params.target.installId) {
276
+ throw new Error('community_native_application_update_identity_invalid');
277
+ }
278
+ const prepared = {
279
+ schemaVersion: 1,
280
+ status: 'community-native-application-update-prepared',
281
+ updateId: params.updateId,
282
+ instanceName: params.target.instanceName,
283
+ installId: params.target.installId,
284
+ createdAt: (params.now ?? (() => new Date()))().toISOString(),
285
+ prior: createCommunityNativeApplicationReferenceV1(params.prior),
286
+ target: createCommunityNativeApplicationReferenceV1(params.target),
287
+ };
288
+ if (sameCommunityNativeApplicationContent(prepared.prior, prepared.target)) {
289
+ throw new Error('community_native_application_update_content_unchanged');
290
+ }
291
+ writeExclusive(params.paths, `${params.updateId}.prepared.json`, prepared);
292
+ return prepared;
293
+ }
294
+ export function writeCommunityNativeApplicationUpdateOutcome(params) {
295
+ const succeeded = params.error === undefined;
296
+ if (!UUID.test(params.updateId) || (succeeded && !params.receipt) || (!succeeded && params.error === undefined)) {
297
+ throw new Error('community_native_application_update_outcome_invalid');
298
+ }
299
+ const outcome = {
300
+ schemaVersion: 1,
301
+ status: succeeded
302
+ ? 'community-native-application-update-succeeded'
303
+ : 'community-native-application-update-failed',
304
+ updateId: params.updateId,
305
+ completedAt: (params.now ?? (() => new Date()))().toISOString(),
306
+ migration: params.receipt ? migrationSummary(params.receipt) : null,
307
+ failure: succeeded ? null :
308
+ (String(params.error instanceof Error ? params.error.message : params.error).slice(0, 300) ||
309
+ 'community_native_application_update_failed'),
310
+ };
311
+ writeExclusive(params.paths, `${params.updateId}.${succeeded ? 'succeeded' : 'failed'}.json`, outcome);
312
+ return outcome;
313
+ }
314
+ export function writeCommunityNativeApplicationRollbackPrepared(params) {
315
+ if (!UUID.test(params.updateId) || !UUID.test(params.rollbackId) || params.databasePlan.action !== 'verify-only' ||
316
+ ![params.databasePlan.acceptedPlanSha256, params.databasePlan.sourceMigrationStateFingerprintSha256,
317
+ params.databasePlan.stateFingerprintSha256].every((value) => RAW_SHA256.test(value))) {
318
+ throw new Error('community_native_application_rollback_prepared_invalid');
319
+ }
320
+ const prepared = {
321
+ schemaVersion: 1,
322
+ status: 'community-native-application-rollback-prepared',
323
+ updateId: params.updateId,
324
+ rollbackId: params.rollbackId,
325
+ createdAt: (params.now ?? (() => new Date()))().toISOString(),
326
+ candidate: assertReference(params.candidate),
327
+ databasePlan: params.databasePlan,
328
+ };
329
+ writeExclusive(params.paths, `${params.updateId}.${params.rollbackId}.rollback-prepared.json`, prepared);
330
+ return prepared;
331
+ }
332
+ export function writeCommunityNativeApplicationRollbackOutcome(params) {
333
+ const succeeded = params.error === undefined;
334
+ if (!UUID.test(params.updateId) || !UUID.test(params.rollbackId) ||
335
+ (succeeded && !params.receipt) || (!succeeded && params.error === undefined)) {
336
+ throw new Error('community_native_application_rollback_outcome_invalid');
337
+ }
338
+ const outcome = {
339
+ schemaVersion: 1,
340
+ status: succeeded
341
+ ? 'community-native-application-rolled-back'
342
+ : 'community-native-application-rollback-failed',
343
+ updateId: params.updateId,
344
+ rollbackId: params.rollbackId,
345
+ completedAt: (params.now ?? (() => new Date()))().toISOString(),
346
+ migration: params.receipt ? migrationSummary(params.receipt) : null,
347
+ failure: succeeded ? null :
348
+ (String(params.error instanceof Error ? params.error.message : params.error).slice(0, 300) ||
349
+ 'community_native_application_rollback_failed'),
350
+ };
351
+ writeExclusive(params.paths, `${params.updateId}.${params.rollbackId}.${succeeded ? 'rolled-back' : 'rollback-failed'}.json`, outcome);
352
+ return outcome;
353
+ }
354
+ export function readCommunityNativeApplicationUpdate(paths, updateId) {
355
+ if (!UUID.test(updateId))
356
+ throw new Error('community_native_application_update_id_invalid');
357
+ const rawPrepared = readRecord(paths, `${updateId}.prepared.json`);
358
+ if (!isRecord(rawPrepared))
359
+ throw new Error('community_native_application_update_not_found');
360
+ exactKeys(rawPrepared, ['schemaVersion', 'status', 'updateId', 'instanceName', 'installId', 'createdAt', 'prior', 'target'], 'community_native_application_update_record_invalid');
361
+ if (rawPrepared.schemaVersion !== 1 || rawPrepared.status !== 'community-native-application-update-prepared' ||
362
+ rawPrepared.updateId !== updateId || !INSTANCE_NAME.test(String(rawPrepared.instanceName)) ||
363
+ !UUID.test(String(rawPrepared.installId)) ||
364
+ Number.isNaN(Date.parse(String(rawPrepared.createdAt)))) {
365
+ throw new Error('community_native_application_update_record_invalid');
366
+ }
367
+ const prepared = { ...rawPrepared, prior: assertReference(rawPrepared.prior), target: assertReference(rawPrepared.target) };
368
+ if (sameCommunityNativeApplicationContent(prepared.prior, prepared.target)) {
369
+ throw new Error('community_native_application_update_content_unchanged');
370
+ }
371
+ const succeeded = readRecord(paths, `${updateId}.succeeded.json`);
372
+ const failed = readRecord(paths, `${updateId}.failed.json`);
373
+ if (succeeded && failed)
374
+ throw new Error('community_native_application_update_outcome_conflict');
375
+ const rawOutcome = succeeded ?? failed;
376
+ const outcome = rawOutcome === null ? null : assertUpdateOutcome(rawOutcome, updateId);
377
+ const rollbackCandidates = existsSync(paths.applicationUpdateRoot)
378
+ ? readdirSync(paths.applicationUpdateRoot)
379
+ .filter((name) => name.startsWith(`${updateId}.`) && name.endsWith('.rollback-prepared.json'))
380
+ : [];
381
+ if (rollbackCandidates.length > 1)
382
+ throw new Error('community_native_application_rollback_ambiguous');
383
+ const rollbackPrepared = rollbackCandidates[0]
384
+ ? assertRollbackPrepared(readRecord(paths, rollbackCandidates[0]), updateId)
385
+ : null;
386
+ if (rollbackPrepared && !sameCommunityNativeApplicationContent(rollbackPrepared.candidate, prepared.prior)) {
387
+ throw new Error('community_native_application_rollback_candidate_mismatch');
388
+ }
389
+ let rollbackOutcome = null;
390
+ if (rollbackPrepared) {
391
+ const rolledBack = readRecord(paths, `${updateId}.${rollbackPrepared.rollbackId}.rolled-back.json`);
392
+ const rollbackFailed = readRecord(paths, `${updateId}.${rollbackPrepared.rollbackId}.rollback-failed.json`);
393
+ if (rolledBack && rollbackFailed)
394
+ throw new Error('community_native_application_rollback_outcome_conflict');
395
+ const rawRollbackOutcome = rolledBack ?? rollbackFailed;
396
+ rollbackOutcome = rawRollbackOutcome === null
397
+ ? null
398
+ : assertRollbackOutcome(rawRollbackOutcome, updateId, rollbackPrepared.rollbackId);
399
+ }
400
+ return { prepared, outcome, rollbackPrepared, rollbackOutcome };
401
+ }
402
+ export function inspectCommunityNativeApplicationRecoveryStatus(paths) {
403
+ if (!existsSync(paths.applicationUpdateRoot))
404
+ return null;
405
+ const root = ensureRecordRoot(paths);
406
+ const records = readdirSync(root)
407
+ .filter((name) => UUID.test(name.slice(0, -'.prepared.json'.length)) && name.endsWith('.prepared.json'))
408
+ .map((name) => readCommunityNativeApplicationUpdate(paths, name.slice(0, -'.prepared.json'.length)))
409
+ .sort((left, right) => {
410
+ const byTime = Date.parse(right.prepared.createdAt) - Date.parse(left.prepared.createdAt);
411
+ return byTime === 0 ? compareCanonicalText(right.prepared.updateId, left.prepared.updateId) : byTime;
412
+ });
413
+ const latest = records[0];
414
+ if (!latest)
415
+ return null;
416
+ return {
417
+ updateId: latest.prepared.updateId,
418
+ preparedAt: latest.prepared.createdAt,
419
+ updateStatus: latest.outcome?.status ?? latest.prepared.status,
420
+ updateCompletedAt: latest.outcome?.completedAt ?? null,
421
+ rollbackId: latest.rollbackPrepared?.rollbackId ?? null,
422
+ rollbackStatus: latest.rollbackOutcome?.status ?? latest.rollbackPrepared?.status ?? null,
423
+ rollbackCompletedAt: latest.rollbackOutcome?.completedAt ?? null,
424
+ };
425
+ }
@@ -0,0 +1,240 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { spawn } from 'node:child_process';
3
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
4
+ import path from 'node:path';
5
+ import { pathToFileURL } from 'node:url';
6
+ export const COMMUNITY_NATIVE_CHILD_PROTOCOL = 'aops-community-native-child-v1';
7
+ export const COMMUNITY_NATIVE_CONTROL_PROTOCOL = 'aops-community-native-control-v1';
8
+ 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;
9
+ const SHA256 = /^sha256:[a-f0-9]{64}$/;
10
+ const HOST_STOP_GRACE_MS = 2_000;
11
+ function requiredEnv(env, key) {
12
+ const value = String(env[key] ?? '').trim();
13
+ if (!value)
14
+ throw new Error(`community_native_child_env_required:${key}`);
15
+ return value;
16
+ }
17
+ function parsePort(value) {
18
+ if (!/^[1-9]\d{0,4}$/.test(value) || Number(value) > 65_535) {
19
+ throw new Error('community_native_child_port_invalid');
20
+ }
21
+ return Number(value);
22
+ }
23
+ function atomicJsonWrite(targetPath, value) {
24
+ const parent = path.dirname(targetPath);
25
+ if (!existsSync(parent))
26
+ throw new Error('community_native_child_identity_parent_missing');
27
+ const tempPath = path.join(parent, `.${path.basename(targetPath)}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`);
28
+ try {
29
+ writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
30
+ renameSync(tempPath, targetPath);
31
+ }
32
+ finally {
33
+ rmSync(tempPath, { force: true });
34
+ }
35
+ }
36
+ export function buildCommunityNativeChildIdentity(env = process.env, pid = process.pid, hostPid = pid) {
37
+ const launchId = requiredEnv(env, 'AOPS_NATIVE_LAUNCH_ID');
38
+ const startedAt = requiredEnv(env, 'AOPS_NATIVE_STARTED_AT');
39
+ const sourceFingerprint = requiredEnv(env, 'AOPS_NATIVE_SOURCE_FINGERPRINT');
40
+ const hostEntry = path.resolve(requiredEnv(env, 'AOPS_NATIVE_HOST_ENTRY'));
41
+ const controlPathInput = requiredEnv(env, 'AOPS_NATIVE_CONTROL_PATH');
42
+ if (!path.isAbsolute(controlPathInput))
43
+ throw new Error('community_native_child_control_path_invalid');
44
+ const controlPath = path.resolve(controlPathInput);
45
+ if (!UUID.test(launchId))
46
+ throw new Error('community_native_child_launch_id_invalid');
47
+ if (!Number.isSafeInteger(pid) || pid < 1 || !Number.isSafeInteger(hostPid) || hostPid < 1) {
48
+ throw new Error('community_native_child_pid_invalid');
49
+ }
50
+ if (Number.isNaN(Date.parse(startedAt)))
51
+ throw new Error('community_native_child_started_at_invalid');
52
+ if (!SHA256.test(sourceFingerprint))
53
+ throw new Error('community_native_child_source_fingerprint_invalid');
54
+ if (!path.isAbsolute(hostEntry))
55
+ throw new Error('community_native_child_host_entry_invalid');
56
+ return {
57
+ schemaVersion: 1,
58
+ protocol: COMMUNITY_NATIVE_CHILD_PROTOCOL,
59
+ launchId,
60
+ pid,
61
+ hostPid,
62
+ startedAt,
63
+ sourceFingerprint,
64
+ hostEntry,
65
+ controlPath,
66
+ port: parsePort(requiredEnv(env, 'AOPS_NATIVE_PORT')),
67
+ };
68
+ }
69
+ function removeIdentityIfOwned(identityPath, launchId) {
70
+ try {
71
+ const parsed = JSON.parse(readFileSync(identityPath, 'utf8'));
72
+ if (parsed.protocol === COMMUNITY_NATIVE_CHILD_PROTOCOL && parsed.launchId === launchId) {
73
+ rmSync(identityPath, { force: true });
74
+ }
75
+ }
76
+ catch {
77
+ }
78
+ }
79
+ function readOwnedStopRequest(controlPath, launchId) {
80
+ try {
81
+ const parsed = JSON.parse(readFileSync(controlPath, 'utf8'));
82
+ if (parsed.schemaVersion !== 1 || parsed.protocol !== COMMUNITY_NATIVE_CONTROL_PROTOCOL ||
83
+ parsed.launchId !== launchId || parsed.command !== 'stop' ||
84
+ Number.isNaN(Date.parse(String(parsed.requestedAt))) ||
85
+ Object.keys(parsed).sort().join(',') !== 'command,launchId,protocol,requestedAt,schemaVersion')
86
+ return null;
87
+ return parsed;
88
+ }
89
+ catch {
90
+ return null;
91
+ }
92
+ }
93
+ function removeControlIfOwned(controlPath, launchId) {
94
+ if (readOwnedStopRequest(controlPath, launchId))
95
+ rmSync(controlPath, { force: true });
96
+ }
97
+ function spawnCommunityNativeHost(hostEntry, env) {
98
+ return spawn(process.execPath, [hostEntry], {
99
+ cwd: process.cwd(),
100
+ env,
101
+ detached: false,
102
+ windowsHide: true,
103
+ stdio: 'inherit',
104
+ });
105
+ }
106
+ async function waitForHostSpawn(host) {
107
+ await new Promise((resolve, reject) => {
108
+ const onSpawn = () => {
109
+ host.off('error', onError);
110
+ resolve();
111
+ };
112
+ const onError = (error) => {
113
+ host.off('spawn', onSpawn);
114
+ reject(error);
115
+ };
116
+ host.once('spawn', onSpawn);
117
+ host.once('error', onError);
118
+ });
119
+ }
120
+ function observeHostOutcome(host) {
121
+ if (host.exitCode !== null || host.signalCode !== null) {
122
+ return Promise.resolve({
123
+ kind: 'exit',
124
+ value: { exitCode: host.exitCode, signal: host.signalCode },
125
+ });
126
+ }
127
+ return new Promise((resolve) => {
128
+ const onExit = (exitCode, signal) => {
129
+ host.off('error', onError);
130
+ resolve({ kind: 'exit', value: { exitCode, signal } });
131
+ };
132
+ const onError = (error) => {
133
+ host.off('exit', onExit);
134
+ resolve({ kind: 'error', error });
135
+ };
136
+ host.once('exit', onExit);
137
+ host.once('error', onError);
138
+ });
139
+ }
140
+ async function terminateAndReapHost(host) {
141
+ if (host.exitCode !== null || host.signalCode !== null)
142
+ return;
143
+ const exited = new Promise((resolve) => {
144
+ host.once('exit', () => resolve());
145
+ });
146
+ host.kill('SIGKILL');
147
+ await exited;
148
+ }
149
+ export async function runCommunityNativeChild(env = process.env, runtime = {}) {
150
+ const identityPathInput = requiredEnv(env, 'AOPS_NATIVE_IDENTITY_PATH');
151
+ if (!path.isAbsolute(identityPathInput))
152
+ throw new Error('community_native_child_identity_path_invalid');
153
+ const identityPath = path.resolve(identityPathInput);
154
+ const controlPathInput = requiredEnv(env, 'AOPS_NATIVE_CONTROL_PATH');
155
+ if (!path.isAbsolute(controlPathInput))
156
+ throw new Error('community_native_child_control_path_invalid');
157
+ const controlPath = path.resolve(controlPathInput);
158
+ const hostEntry = path.resolve(requiredEnv(env, 'AOPS_NATIVE_HOST_ENTRY'));
159
+ if (!existsSync(hostEntry))
160
+ throw new Error('community_native_child_host_entry_missing');
161
+ mkdirSync(path.dirname(identityPath), { recursive: true, mode: 0o700 });
162
+ const host = (runtime.spawnHost ?? spawnCommunityNativeHost)(hostEntry, env);
163
+ await waitForHostSpawn(host);
164
+ const hostOutcome = observeHostOutcome(host);
165
+ let cleanup;
166
+ let controlTimer;
167
+ let forceStopTimer;
168
+ let stopping = false;
169
+ const forward = (signal) => {
170
+ if (stopping)
171
+ return;
172
+ stopping = true;
173
+ try {
174
+ host.kill(signal);
175
+ }
176
+ catch { }
177
+ forceStopTimer = setTimeout(() => {
178
+ if (host.exitCode === null && host.signalCode === null) {
179
+ try {
180
+ host.kill('SIGKILL');
181
+ }
182
+ catch { }
183
+ }
184
+ }, HOST_STOP_GRACE_MS);
185
+ forceStopTimer.unref();
186
+ };
187
+ const onSigint = () => forward('SIGINT');
188
+ const onSigterm = () => forward('SIGTERM');
189
+ try {
190
+ if (!host.pid)
191
+ throw new Error('community_native_child_host_pid_missing');
192
+ const identity = buildCommunityNativeChildIdentity(env, process.pid, host.pid);
193
+ atomicJsonWrite(identityPath, identity);
194
+ cleanup = () => {
195
+ removeIdentityIfOwned(identityPath, identity.launchId);
196
+ removeControlIfOwned(controlPath, identity.launchId);
197
+ };
198
+ process.once('exit', cleanup);
199
+ process.once('SIGINT', onSigint);
200
+ process.once('SIGTERM', onSigterm);
201
+ controlTimer = setInterval(() => {
202
+ if (readOwnedStopRequest(controlPath, identity.launchId)) {
203
+ removeControlIfOwned(controlPath, identity.launchId);
204
+ forward('SIGTERM');
205
+ }
206
+ }, 100);
207
+ const outcome = await hostOutcome;
208
+ if (outcome.kind === 'error')
209
+ throw outcome.error;
210
+ if (!stopping && outcome.value.exitCode !== 0) {
211
+ throw new Error(`community_native_child_host_failed:${outcome.value.exitCode ?? outcome.value.signal ?? 'unknown'}`);
212
+ }
213
+ }
214
+ catch (error) {
215
+ await terminateAndReapHost(host);
216
+ throw error;
217
+ }
218
+ finally {
219
+ process.off('SIGINT', onSigint);
220
+ process.off('SIGTERM', onSigterm);
221
+ if (cleanup)
222
+ process.off('exit', cleanup);
223
+ if (controlTimer)
224
+ clearInterval(controlTimer);
225
+ if (forceStopTimer)
226
+ clearTimeout(forceStopTimer);
227
+ cleanup?.();
228
+ }
229
+ }
230
+ export function isCommunityNativeChildEntry(moduleUrl = import.meta.url, argvPath = process.argv[1]) {
231
+ return typeof argvPath === 'string' &&
232
+ /^community-native-child\.[cm]?[jt]s$/i.test(path.basename(argvPath)) &&
233
+ moduleUrl === pathToFileURL(argvPath).href;
234
+ }
235
+ if (isCommunityNativeChildEntry()) {
236
+ runCommunityNativeChild().catch((error) => {
237
+ process.stderr.write(`[aops-community] native child failed: ${error instanceof Error ? error.message : String(error)}\n`);
238
+ process.exitCode = 1;
239
+ });
240
+ }