superpowers-manager 0.5.1 → 0.6.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.
- package/README.md +13 -11
- package/dist/cli.js +9 -14
- package/dist/commands/install.js +18 -9
- package/dist/commands/mutation.js +7 -0
- package/dist/commands/prepare.js +5 -51
- package/dist/commands/probe.js +8 -10
- package/dist/commands/uninstall.js +2 -2
- package/dist/effective-selection.js +3 -3
- package/dist/harnesses/codex/adapter.js +336 -457
- package/dist/harnesses/codex/compatibility.js +2 -17
- package/dist/harnesses/codex/generated-plugin.js +1 -1
- package/dist/harnesses/codex/harness.js +45 -272
- package/dist/harnesses/codex/hooks.js +1 -1
- package/dist/harnesses/codex/json.js +6 -2
- package/dist/harnesses/codex/lifecycle.js +110 -54
- package/dist/harnesses/codex/manifest-overlay.js +22 -17
- package/dist/harnesses/codex/marketplace.js +148 -0
- package/dist/harnesses/codex/paths.js +97 -0
- package/dist/harnesses/codex/prepare.js +34 -2
- package/dist/harnesses/codex/presentation.js +10 -17
- package/dist/harnesses/codex/publication.js +520 -0
- package/dist/harnesses/codex/recovery.js +338 -0
- package/dist/harnesses/codex/state.js +131 -0
- package/dist/harnesses/codex/stored-state.js +249 -0
- package/dist/provenance.js +3 -3
- package/dist/python-json-format.js +0 -69
- package/dist/python-json.js +2 -27
- package/dist/python-text.js +9 -69
- package/dist/validator.js +35 -72
- package/node_modules/smol-toml/LICENSE +24 -0
- package/node_modules/smol-toml/README.md +418 -0
- package/node_modules/smol-toml/dist/date.d.ts +41 -0
- package/node_modules/smol-toml/dist/date.js +127 -0
- package/node_modules/smol-toml/dist/error.d.ts +38 -0
- package/node_modules/smol-toml/dist/error.js +63 -0
- package/node_modules/smol-toml/dist/extract.js +69 -0
- package/node_modules/smol-toml/dist/index.cjs +734 -0
- package/node_modules/smol-toml/dist/index.d.ts +43 -0
- package/node_modules/smol-toml/dist/index.js +33 -0
- package/node_modules/smol-toml/dist/parse.d.ts +36 -0
- package/node_modules/smol-toml/dist/parse.js +149 -0
- package/node_modules/smol-toml/dist/primitive.js +238 -0
- package/node_modules/smol-toml/dist/stringify.d.ts +31 -0
- package/node_modules/smol-toml/dist/stringify.js +181 -0
- package/node_modules/smol-toml/dist/struct.js +179 -0
- package/node_modules/smol-toml/dist/util.d.ts +38 -0
- package/node_modules/smol-toml/dist/util.js +89 -0
- package/node_modules/smol-toml/package.json +68 -0
- package/package.json +10 -2
- package/dist/selection-state-cli.js +0 -85
- package/dist/upstream-cli.js +0 -110
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
import { lstat, rm } from "node:fs/promises";
|
|
2
|
+
import { failureResult, successResult, } from "../../adapter-result.js";
|
|
3
|
+
import { beginDirectoryPublication, } from "../../atomic.js";
|
|
4
|
+
import { digestArtifactTree } from "../../artifact-tree.js";
|
|
5
|
+
import { assertNoFollowType, classifyPathNoFollow } from "../../safe-path.js";
|
|
6
|
+
import { codexInspectControl, codexInspectOwnership, codexInstallRefreshMode, codexRemove, codexReadNativeState, } from "./adapter.js";
|
|
7
|
+
import { readCodexAssessment } from "./compatibility.js";
|
|
8
|
+
import { readCodexMarketplace, stageCodexMarketplace, } from "./marketplace.js";
|
|
9
|
+
import { assertCodexPreparationSeparate, codexPathFailureDetails, codexPaths, } from "./paths.js";
|
|
10
|
+
import { codexPreparationLocation } from "./prepare.js";
|
|
11
|
+
import { beginCodexRecovery, finishCodexRecovery, readCodexRecovery, verifyCodexRecovery, } from "./recovery.js";
|
|
12
|
+
import { hasFilesystemAccessFailure, pathsEqual } from "./state.js";
|
|
13
|
+
const DEFAULTS = {
|
|
14
|
+
readNative: codexReadNativeState,
|
|
15
|
+
inspectOwnership: codexInspectOwnership,
|
|
16
|
+
inspectControl: codexInspectControl,
|
|
17
|
+
beginPublication: beginDirectoryPublication,
|
|
18
|
+
};
|
|
19
|
+
const REMOVAL_DEFAULTS = {
|
|
20
|
+
readNative: codexReadNativeState,
|
|
21
|
+
removeNative: codexRemove,
|
|
22
|
+
};
|
|
23
|
+
function fail(code, message, messages) {
|
|
24
|
+
return failureResult("install-codex", code, message, [], messages);
|
|
25
|
+
}
|
|
26
|
+
function removalFailure(code, message, messages) {
|
|
27
|
+
return failureResult("uninstall-codex", code, message, [], messages);
|
|
28
|
+
}
|
|
29
|
+
function acceptedNative(result) {
|
|
30
|
+
if (result.status !== 0 || !result.outcome.ok) {
|
|
31
|
+
throw new Error("Codex native observation failed");
|
|
32
|
+
}
|
|
33
|
+
const value = result.outcome.result;
|
|
34
|
+
if (typeof value.marketplaceRoot !== "string" &&
|
|
35
|
+
value.marketplaceRoot !== null) {
|
|
36
|
+
throw new Error("Codex native observation is malformed");
|
|
37
|
+
}
|
|
38
|
+
if (typeof value.pluginPresent !== "boolean" ||
|
|
39
|
+
typeof value.pluginEnabled !== "boolean" ||
|
|
40
|
+
(typeof value.activeVersion !== "string" && value.activeVersion !== null) ||
|
|
41
|
+
(typeof value.activeRoot !== "string" && value.activeRoot !== null)) {
|
|
42
|
+
throw new Error("Codex native observation is malformed");
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
function requireOwnership(result) {
|
|
47
|
+
if (result.status !== 0 || !result.outcome.ok) {
|
|
48
|
+
throw new Error("Codex inspection failed");
|
|
49
|
+
}
|
|
50
|
+
if (result.outcome.result?.installEligibility?.kind !== "allowed") {
|
|
51
|
+
throw new Error("Codex ownership blocks publication");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function requireControl(result) {
|
|
55
|
+
if (result.status !== 0 || !result.outcome.ok) {
|
|
56
|
+
throw new Error("Codex inspection failed");
|
|
57
|
+
}
|
|
58
|
+
if (result.outcome.result?.mutationEligibility?.kind !== "allowed") {
|
|
59
|
+
throw new Error("Codex update control blocks publication");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function sameNative(left, right) {
|
|
63
|
+
return (left.marketplaceRoot === right.marketplaceRoot &&
|
|
64
|
+
left.pluginPresent === right.pluginPresent &&
|
|
65
|
+
left.pluginEnabled === right.pluginEnabled &&
|
|
66
|
+
left.activeVersion === right.activeVersion &&
|
|
67
|
+
left.activeRoot === right.activeRoot);
|
|
68
|
+
}
|
|
69
|
+
function sameIdentity(snapshot, identity) {
|
|
70
|
+
return snapshot === null
|
|
71
|
+
? identity === null
|
|
72
|
+
: identity !== null &&
|
|
73
|
+
snapshot.dev === identity.dev &&
|
|
74
|
+
snapshot.ino === identity.ino;
|
|
75
|
+
}
|
|
76
|
+
function sameMarketplace(left, right) {
|
|
77
|
+
return left === null
|
|
78
|
+
? right === null
|
|
79
|
+
: right !== null &&
|
|
80
|
+
left.dev === right.dev &&
|
|
81
|
+
left.ino === right.ino &&
|
|
82
|
+
left.digest === right.digest;
|
|
83
|
+
}
|
|
84
|
+
function nativeAbsent(state) {
|
|
85
|
+
return (state.marketplaceRoot === null &&
|
|
86
|
+
!state.pluginPresent &&
|
|
87
|
+
!state.pluginEnabled &&
|
|
88
|
+
state.activeVersion === null &&
|
|
89
|
+
state.activeRoot === null);
|
|
90
|
+
}
|
|
91
|
+
async function sameCapturedMarketplace(root, captured) {
|
|
92
|
+
try {
|
|
93
|
+
return sameMarketplace(await readCodexMarketplace(root), captured);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export async function removeCodexMarketplace(_input, ctx, dependencies = REMOVAL_DEFAULTS) {
|
|
100
|
+
const messages = [];
|
|
101
|
+
const paths = codexPaths(ctx.env ?? {}, process.cwd());
|
|
102
|
+
let pending;
|
|
103
|
+
let captured = null;
|
|
104
|
+
let nativeAttempted = false;
|
|
105
|
+
try {
|
|
106
|
+
if ((await readCodexRecovery(paths)) !== null) {
|
|
107
|
+
return removalFailure("recovery-required", `Codex recovery is required before mutation; preserve material at ${paths.recoveryRoot}`, messages);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return removalFailure("recovery-required", `cannot inspect Codex recovery state at ${paths.recoveryRoot}; recovery is required before removal`, messages);
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const priorNative = await observeNative(ctx, dependencies, messages);
|
|
115
|
+
captured = await readCodexMarketplace(paths.marketplaceRoot);
|
|
116
|
+
if (captured !== null) {
|
|
117
|
+
pending = await beginCodexRecovery(paths, {
|
|
118
|
+
operation: "uninstall",
|
|
119
|
+
marketplaceRoot: paths.marketplaceRoot,
|
|
120
|
+
priorNative,
|
|
121
|
+
oldDigest: captured.digest,
|
|
122
|
+
oldIdentity: { dev: captured.dev, ino: captured.ino },
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const beforeRemoval = await observeNative(ctx, dependencies, messages);
|
|
126
|
+
const marketplaceUnchanged = captured === null ||
|
|
127
|
+
(await sameCapturedMarketplace(paths.marketplaceRoot, captured));
|
|
128
|
+
if (!sameNative(beforeRemoval, priorNative) || !marketplaceUnchanged) {
|
|
129
|
+
if (pending !== undefined && marketplaceUnchanged) {
|
|
130
|
+
await finishCodexRecovery(pending);
|
|
131
|
+
}
|
|
132
|
+
return removalFailure("removal-refused", "Codex native or durable marketplace state changed before removal", messages);
|
|
133
|
+
}
|
|
134
|
+
const currentInput = {
|
|
135
|
+
pluginPresent: beforeRemoval.pluginPresent,
|
|
136
|
+
marketplacePresent: beforeRemoval.marketplaceRoot !== null,
|
|
137
|
+
};
|
|
138
|
+
if (pending !== undefined)
|
|
139
|
+
await verifyCodexRecovery(pending);
|
|
140
|
+
nativeAttempted = true;
|
|
141
|
+
let removed;
|
|
142
|
+
try {
|
|
143
|
+
removed = await dependencies.removeNative(currentInput, ctx);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
removed = failureResult("uninstall", "native-failed", "Codex native removal threw before returning a result", [], []);
|
|
147
|
+
}
|
|
148
|
+
messages.push(...removed.outcome.messages);
|
|
149
|
+
let afterRemoval;
|
|
150
|
+
try {
|
|
151
|
+
afterRemoval = await observeNative(ctx, dependencies, messages);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return removalFailure("recovery-required", pending === undefined
|
|
155
|
+
? "Codex deregistration could not be verified"
|
|
156
|
+
: `Codex deregistration could not be verified; preserve the marketplace and recovery material at ${paths.marketplaceRoot} and ${paths.recoveryRoot}`, messages);
|
|
157
|
+
}
|
|
158
|
+
if (removed.status !== 0 || !removed.outcome.ok) {
|
|
159
|
+
if (pending === undefined && !removed.outcome.ok) {
|
|
160
|
+
return failureResult(removed.outcome.operation, removed.outcome.error.code, removed.outcome.error.message, removed.outcome.error.hints, messages);
|
|
161
|
+
}
|
|
162
|
+
return removalFailure("native-failed", pending === undefined
|
|
163
|
+
? "Codex native removal failed"
|
|
164
|
+
: `Codex native removal failed; preserve the marketplace and recovery material at ${paths.marketplaceRoot} and ${paths.recoveryRoot}`, messages);
|
|
165
|
+
}
|
|
166
|
+
if (!nativeAbsent(afterRemoval)) {
|
|
167
|
+
return removalFailure("recovery-required", pending === undefined
|
|
168
|
+
? "Codex deregistration could not be verified"
|
|
169
|
+
: `Codex deregistration could not be verified; preserve the marketplace and recovery material at ${paths.marketplaceRoot} and ${paths.recoveryRoot}`, messages);
|
|
170
|
+
}
|
|
171
|
+
if (pending === undefined || captured === null) {
|
|
172
|
+
return successResult("uninstall", null, messages);
|
|
173
|
+
}
|
|
174
|
+
if (!(await sameCapturedMarketplace(paths.marketplaceRoot, captured))) {
|
|
175
|
+
return removalFailure("recovery-required", `Codex deregistered, but the durable marketplace changed; preserve recovery material at ${paths.recoveryRoot}`, messages);
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
await verifyCodexRecovery(pending);
|
|
179
|
+
await rm(paths.marketplaceRoot, { recursive: true });
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
if (await sameCapturedMarketplace(paths.marketplaceRoot, captured)) {
|
|
183
|
+
try {
|
|
184
|
+
await finishCodexRecovery(pending);
|
|
185
|
+
return removalFailure("cleanup-failed", `Codex deregistered, but the owned marketplace could not be removed at ${paths.marketplaceRoot}; retry uninstall to clean it`, messages);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Fall through to the unresolved-recovery result below.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return removalFailure("recovery-required", `Codex deregistered, but durable cleanup could not be completed safely; preserve recovery material at ${paths.recoveryRoot}`, messages);
|
|
192
|
+
}
|
|
193
|
+
await finishCodexRecovery(pending);
|
|
194
|
+
return successResult("uninstall", null, messages);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return removalFailure(pending !== undefined && nativeAttempted
|
|
198
|
+
? "recovery-required"
|
|
199
|
+
: "removal-refused", pending !== undefined
|
|
200
|
+
? `Codex removal could not be completed safely; preserve the marketplace and recovery material at ${paths.marketplaceRoot} and ${paths.recoveryRoot}`
|
|
201
|
+
: `cannot remove Codex marketplace; verify native and durable ownership at ${paths.marketplaceRoot}`, messages);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async function activeDigest(state) {
|
|
205
|
+
if (!state.pluginPresent || state.activeRoot === null)
|
|
206
|
+
return null;
|
|
207
|
+
let kind;
|
|
208
|
+
try {
|
|
209
|
+
kind = await classifyPathNoFollow(state.activeRoot);
|
|
210
|
+
}
|
|
211
|
+
catch (cause) {
|
|
212
|
+
throw new Error("cannot inspect prior active Codex plugin", { cause });
|
|
213
|
+
}
|
|
214
|
+
if (kind === "missing")
|
|
215
|
+
return null;
|
|
216
|
+
if (kind !== "directory") {
|
|
217
|
+
throw new Error("cannot inspect prior active Codex plugin");
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
await readCodexAssessment(state.activeRoot);
|
|
221
|
+
return await digestArtifactTree(state.activeRoot);
|
|
222
|
+
}
|
|
223
|
+
catch (cause) {
|
|
224
|
+
if (hasFilesystemAccessFailure(cause)) {
|
|
225
|
+
throw new Error("cannot inspect prior active Codex plugin", { cause });
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async function verifyPrepared(artifact, expectedRoot) {
|
|
231
|
+
if (artifact.root !== expectedRoot)
|
|
232
|
+
throw new Error("unexpected Codex prepared root");
|
|
233
|
+
const assessed = await readCodexAssessment(artifact.root);
|
|
234
|
+
if (assessed.root !== artifact.root ||
|
|
235
|
+
assessed.commit !== artifact.commit ||
|
|
236
|
+
assessed.compatibility.kind !== "supported") {
|
|
237
|
+
throw new Error("Codex prepared artifact changed");
|
|
238
|
+
}
|
|
239
|
+
return await digestArtifactTree(artifact.root);
|
|
240
|
+
}
|
|
241
|
+
async function observeNative(ctx, dependencies, messages) {
|
|
242
|
+
const result = await dependencies.readNative(ctx);
|
|
243
|
+
messages.push(...result.outcome.messages);
|
|
244
|
+
return acceptedNative(result);
|
|
245
|
+
}
|
|
246
|
+
async function requireEligibility(ctx, dependencies, messages) {
|
|
247
|
+
const ownership = await dependencies.inspectOwnership(ctx);
|
|
248
|
+
messages.push(...ownership.outcome.messages);
|
|
249
|
+
requireOwnership(ownership);
|
|
250
|
+
const control = await dependencies.inspectControl(ctx);
|
|
251
|
+
messages.push(...control.outcome.messages);
|
|
252
|
+
requireControl(control);
|
|
253
|
+
}
|
|
254
|
+
async function verifyPrevious(pending, previous) {
|
|
255
|
+
const current = await readCodexMarketplace(pending.paths.marketplaceRoot);
|
|
256
|
+
if (!sameMarketplace(current, previous)) {
|
|
257
|
+
throw new Error("previous Codex marketplace changed");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async function cleanupBeforePublication(pending, previous, priorNative, ctx, dependencies, messages) {
|
|
261
|
+
try {
|
|
262
|
+
if (!sameNative(await observeNative(ctx, dependencies, messages), priorNative)) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
await verifyPrevious(pending, previous);
|
|
266
|
+
if ((await assertNoFollowType(pending.backup, ["directory", "missing"])) !==
|
|
267
|
+
"missing") {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
const stageKind = await assertNoFollowType(pending.stage, [
|
|
271
|
+
"directory",
|
|
272
|
+
"missing",
|
|
273
|
+
]);
|
|
274
|
+
if (stageKind === "directory") {
|
|
275
|
+
const staged = await readCodexMarketplace(pending.stage);
|
|
276
|
+
if (staged === null ||
|
|
277
|
+
!sameIdentity(staged, pending.stageIdentity) ||
|
|
278
|
+
staged.digest !== pending.newDigest) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
await verifyCodexRecovery(pending);
|
|
282
|
+
await rm(pending.stage, { recursive: true });
|
|
283
|
+
}
|
|
284
|
+
await finishCodexRecovery(pending);
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async function nativeUnchangedAfterAttempt(prior, priorActiveDigest, current) {
|
|
292
|
+
if (!sameNative(prior, current))
|
|
293
|
+
return false;
|
|
294
|
+
if (!prior.pluginPresent)
|
|
295
|
+
return true;
|
|
296
|
+
if (prior.activeRoot === null || priorActiveDigest === null)
|
|
297
|
+
return false;
|
|
298
|
+
return (await activeDigest(current)) === priorActiveDigest;
|
|
299
|
+
}
|
|
300
|
+
async function rollbackPublication(pending, publication, previous, priorNative, priorActiveDigest, activationAttempted, ctx, dependencies) {
|
|
301
|
+
if (pending.settled) {
|
|
302
|
+
return fail("already-settled", "Codex installation transaction has already been settled", []);
|
|
303
|
+
}
|
|
304
|
+
const messages = [];
|
|
305
|
+
try {
|
|
306
|
+
const currentNative = await observeNative(ctx, dependencies, messages);
|
|
307
|
+
if (activationAttempted &&
|
|
308
|
+
!(await nativeUnchangedAfterAttempt(priorNative, priorActiveDigest, currentNative))) {
|
|
309
|
+
throw new Error("Codex native state may have changed");
|
|
310
|
+
}
|
|
311
|
+
if (!activationAttempted && !sameNative(currentNative, priorNative)) {
|
|
312
|
+
throw new Error("Codex native state changed before activation");
|
|
313
|
+
}
|
|
314
|
+
const published = await readCodexMarketplace(pending.paths.marketplaceRoot);
|
|
315
|
+
if (published === null ||
|
|
316
|
+
!sameIdentity(published, pending.publishedIdentity) ||
|
|
317
|
+
published.digest !== pending.newDigest) {
|
|
318
|
+
throw new Error("published Codex marketplace changed");
|
|
319
|
+
}
|
|
320
|
+
const backup = await readCodexMarketplace(pending.backup);
|
|
321
|
+
if (!sameMarketplace(backup, previous)) {
|
|
322
|
+
throw new Error("retained Codex marketplace changed");
|
|
323
|
+
}
|
|
324
|
+
await verifyCodexRecovery(pending);
|
|
325
|
+
await publication.rollback();
|
|
326
|
+
await verifyPrevious(pending, previous);
|
|
327
|
+
if (!sameNative(await observeNative(ctx, dependencies, messages), priorNative)) {
|
|
328
|
+
throw new Error("Codex native state changed during rollback");
|
|
329
|
+
}
|
|
330
|
+
await finishCodexRecovery(pending);
|
|
331
|
+
return successResult("rollback-codex", null, messages);
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
return fail("recovery-required", `Codex restoration could not be verified; preserve the marketplace and recovery material at ${pending.paths.recoveryRoot} and ${pending.backup}`, messages);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async function verifyActivated(pending, dependencies, ctx, messages) {
|
|
338
|
+
const published = await readCodexMarketplace(pending.paths.marketplaceRoot);
|
|
339
|
+
if (published === null ||
|
|
340
|
+
!sameIdentity(published, pending.publishedIdentity) ||
|
|
341
|
+
published.digest !== pending.newDigest) {
|
|
342
|
+
throw new Error("published Codex marketplace changed");
|
|
343
|
+
}
|
|
344
|
+
const native = await observeNative(ctx, dependencies, messages);
|
|
345
|
+
if (native.marketplaceRoot === null ||
|
|
346
|
+
!(await pathsEqual(native.marketplaceRoot, pending.paths.marketplaceRoot)) ||
|
|
347
|
+
!native.pluginPresent ||
|
|
348
|
+
!native.pluginEnabled ||
|
|
349
|
+
native.activeRoot === null) {
|
|
350
|
+
throw new Error("Codex activation changed");
|
|
351
|
+
}
|
|
352
|
+
const active = await readCodexAssessment(native.activeRoot);
|
|
353
|
+
if (active.commit !== published.artifact.commit ||
|
|
354
|
+
active.compatibility.kind !== "supported" ||
|
|
355
|
+
(await digestArtifactTree(active.root)) !==
|
|
356
|
+
(await digestArtifactTree(published.artifact.root))) {
|
|
357
|
+
throw new Error("active Codex plugin differs from publication");
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
async function finalizePublication(pending, publication, dependencies, ctx) {
|
|
361
|
+
if (pending.settled) {
|
|
362
|
+
return fail("already-settled", "Codex installation transaction has already been settled", []);
|
|
363
|
+
}
|
|
364
|
+
const messages = [];
|
|
365
|
+
let verified = false;
|
|
366
|
+
let backupRemoved = false;
|
|
367
|
+
try {
|
|
368
|
+
await verifyActivated(pending, dependencies, ctx, messages);
|
|
369
|
+
await verifyCodexRecovery(pending);
|
|
370
|
+
verified = true;
|
|
371
|
+
await publication.finalize();
|
|
372
|
+
backupRemoved = true;
|
|
373
|
+
await finishCodexRecovery(pending);
|
|
374
|
+
return successResult("finalize-codex", null, messages);
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
const detail = backupRemoved
|
|
378
|
+
? "activation was verified and backup cleanup completed, but journal retirement failed"
|
|
379
|
+
: verified
|
|
380
|
+
? "activation was verified, but backup or journal cleanup failed"
|
|
381
|
+
: "activation could not be verified";
|
|
382
|
+
return fail("recovery-required", `Codex ${detail}; preserve recovery material at ${pending.paths.recoveryRoot} and ${pending.backup}`, messages);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
export async function installCodexMarketplace(artifact, ctx, activate, dependencies = DEFAULTS) {
|
|
386
|
+
const messages = [];
|
|
387
|
+
const effectiveEnv = { ...process.env, ...ctx.env };
|
|
388
|
+
if (codexInstallRefreshMode(effectiveEnv) === null) {
|
|
389
|
+
return fail("invalid-arguments", `unsupported SUPERPOWERS_INSTALL_REFRESH_MODE: ${effectiveEnv.SUPERPOWERS_INSTALL_REFRESH_MODE}`, messages);
|
|
390
|
+
}
|
|
391
|
+
const paths = codexPaths(ctx.env ?? {}, process.cwd());
|
|
392
|
+
const preparedRoot = codexPreparationLocation(ctx).destinationRoot;
|
|
393
|
+
try {
|
|
394
|
+
await assertCodexPreparationSeparate(preparedRoot, paths);
|
|
395
|
+
}
|
|
396
|
+
catch (cause) {
|
|
397
|
+
const failure = codexPathFailureDetails(cause);
|
|
398
|
+
if (failure?.kind === "inspection") {
|
|
399
|
+
if (failure.root === "recovery") {
|
|
400
|
+
return fail("recovery-required", `cannot inspect Codex recovery state at ${paths.recoveryRoot}; recovery is required before mutation`, messages);
|
|
401
|
+
}
|
|
402
|
+
return fail("activation-refused", failure.root === "marketplace"
|
|
403
|
+
? `cannot inspect Codex marketplace storage at ${paths.marketplaceRoot}`
|
|
404
|
+
: `cannot inspect Codex preparation root at ${preparedRoot}`, messages);
|
|
405
|
+
}
|
|
406
|
+
return fail(failure?.kind === "overlap"
|
|
407
|
+
? "preparation-overlap"
|
|
408
|
+
: "activation-refused", failure?.kind === "overlap"
|
|
409
|
+
? `Codex preparation overlaps Codex published or recovery storage: ${preparedRoot}`
|
|
410
|
+
: `cannot validate Codex preparation storage separation at ${preparedRoot}`, messages);
|
|
411
|
+
}
|
|
412
|
+
let pending;
|
|
413
|
+
let publication;
|
|
414
|
+
let previous = null;
|
|
415
|
+
let priorNative;
|
|
416
|
+
let priorActiveDigest = null;
|
|
417
|
+
let activationAttempted = false;
|
|
418
|
+
try {
|
|
419
|
+
if ((await readCodexRecovery(paths)) !== null) {
|
|
420
|
+
return fail("recovery-required", `Codex recovery is required before mutation; preserve material at ${paths.recoveryRoot}`, messages);
|
|
421
|
+
}
|
|
422
|
+
const preparedDigest = await verifyPrepared(artifact, preparedRoot);
|
|
423
|
+
previous = await readCodexMarketplace(paths.marketplaceRoot);
|
|
424
|
+
priorNative = await observeNative(ctx, dependencies, messages);
|
|
425
|
+
priorActiveDigest = await activeDigest(priorNative);
|
|
426
|
+
await requireEligibility(ctx, dependencies, messages);
|
|
427
|
+
pending = await beginCodexRecovery(paths, {
|
|
428
|
+
operation: "install",
|
|
429
|
+
marketplaceRoot: paths.marketplaceRoot,
|
|
430
|
+
priorNative,
|
|
431
|
+
oldDigest: previous?.digest ?? null,
|
|
432
|
+
oldIdentity: previous === null ? null : { dev: previous.dev, ino: previous.ino },
|
|
433
|
+
});
|
|
434
|
+
const staged = await stageCodexMarketplace(artifact, ctx.root, pending.stage);
|
|
435
|
+
pending.newDigest = staged.digest;
|
|
436
|
+
pending.stageIdentity = { dev: staged.dev, ino: staged.ino };
|
|
437
|
+
await verifyCodexRecovery(pending);
|
|
438
|
+
if ((await verifyPrepared(artifact, preparedRoot)) !== preparedDigest) {
|
|
439
|
+
throw new Error("Codex prepared artifact changed");
|
|
440
|
+
}
|
|
441
|
+
await verifyPrevious(pending, previous);
|
|
442
|
+
if (!sameNative(await observeNative(ctx, dependencies, messages), priorNative)) {
|
|
443
|
+
throw new Error("Codex native state changed");
|
|
444
|
+
}
|
|
445
|
+
await verifyCodexRecovery(pending);
|
|
446
|
+
publication = await dependencies.beginPublication(pending.stage, paths.marketplaceRoot, { backupPath: pending.backup });
|
|
447
|
+
if (publication.live !== paths.marketplaceRoot ||
|
|
448
|
+
publication.backup !== (previous === null ? null : pending.backup)) {
|
|
449
|
+
throw new Error("Codex publication returned inconsistent paths");
|
|
450
|
+
}
|
|
451
|
+
const publishedStat = await lstat(paths.marketplaceRoot);
|
|
452
|
+
pending.publishedIdentity = {
|
|
453
|
+
dev: publishedStat.dev,
|
|
454
|
+
ino: publishedStat.ino,
|
|
455
|
+
};
|
|
456
|
+
await verifyCodexRecovery(pending);
|
|
457
|
+
activationAttempted = true;
|
|
458
|
+
let activated;
|
|
459
|
+
try {
|
|
460
|
+
activated = await activate(paths.marketplaceRoot, ctx);
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
activated = failureResult("install", "native-failed", "Codex native activation threw before returning a receipt", [], []);
|
|
464
|
+
}
|
|
465
|
+
messages.push(...activated.outcome.messages);
|
|
466
|
+
if (activated.status !== 0 || !activated.outcome.ok) {
|
|
467
|
+
const settled = await rollbackPublication(pending, publication, previous, priorNative, priorActiveDigest, true, ctx, dependencies);
|
|
468
|
+
messages.push(...settled.outcome.messages);
|
|
469
|
+
if (settled.outcome.ok) {
|
|
470
|
+
return fail("activation-failed", "Codex activation failed; the previous marketplace and native state were restored", messages);
|
|
471
|
+
}
|
|
472
|
+
return fail("recovery-required", `Codex activation may have changed native state; preserve recovery material at ${paths.recoveryRoot} and ${pending.backup}`, messages);
|
|
473
|
+
}
|
|
474
|
+
await verifyCodexRecovery(pending);
|
|
475
|
+
const owned = pending;
|
|
476
|
+
const retained = publication;
|
|
477
|
+
let transactionSettled = false;
|
|
478
|
+
const settleOnce = async (action) => {
|
|
479
|
+
if (transactionSettled) {
|
|
480
|
+
return fail("already-settled", "Codex installation transaction has already been settled", []);
|
|
481
|
+
}
|
|
482
|
+
transactionSettled = true;
|
|
483
|
+
return await action();
|
|
484
|
+
};
|
|
485
|
+
return successResult(activated.outcome.operation, {
|
|
486
|
+
...activated.outcome.result,
|
|
487
|
+
transaction: {
|
|
488
|
+
finalize: () => settleOnce(() => finalizePublication(owned, retained, dependencies, ctx)),
|
|
489
|
+
rollback: () => settleOnce(() => rollbackPublication(owned, retained, previous, priorNative, priorActiveDigest, activationAttempted, ctx, dependencies)),
|
|
490
|
+
},
|
|
491
|
+
}, messages);
|
|
492
|
+
}
|
|
493
|
+
catch {
|
|
494
|
+
if (pending !== undefined && priorNative !== undefined) {
|
|
495
|
+
let settled = false;
|
|
496
|
+
if (publication === undefined) {
|
|
497
|
+
settled = await cleanupBeforePublication(pending, previous, priorNative, ctx, dependencies, messages);
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
const result = await rollbackPublication(pending, publication, previous, priorNative, priorActiveDigest, activationAttempted, ctx, dependencies);
|
|
501
|
+
messages.push(...result.outcome.messages);
|
|
502
|
+
settled = result.outcome.ok;
|
|
503
|
+
}
|
|
504
|
+
if (settled) {
|
|
505
|
+
return fail("activation-refused", `cannot activate Codex artifact ${artifact.root}; previous state was preserved`, messages);
|
|
506
|
+
}
|
|
507
|
+
return fail("recovery-required", `Codex mutation requires manual recovery; preserve material at ${paths.recoveryRoot} and ${pending.backup}`, messages);
|
|
508
|
+
}
|
|
509
|
+
let unresolved = false;
|
|
510
|
+
try {
|
|
511
|
+
unresolved = (await readCodexRecovery(paths)) !== null;
|
|
512
|
+
}
|
|
513
|
+
catch {
|
|
514
|
+
unresolved = true;
|
|
515
|
+
}
|
|
516
|
+
return fail(unresolved ? "recovery-required" : "activation-refused", unresolved
|
|
517
|
+
? `Codex recovery is required before mutation; preserve material at ${paths.recoveryRoot}`
|
|
518
|
+
: `cannot activate Codex artifact ${artifact.root}; verify prepared content, ownership, and native state`, messages);
|
|
519
|
+
}
|
|
520
|
+
}
|