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
|
@@ -61,24 +61,9 @@ export async function readCodexAssessment(root) {
|
|
|
61
61
|
});
|
|
62
62
|
if (errors.length)
|
|
63
63
|
throw new Error("generated resources");
|
|
64
|
-
const
|
|
64
|
+
const compatibility = await assessCodexCompatibility(root, {
|
|
65
65
|
effectiveSource: receipt.source,
|
|
66
|
-
|
|
67
|
-
requestedRef: provenance.requested_ref,
|
|
68
|
-
resolvedRef: provenance.resolved_ref,
|
|
69
|
-
resolutionKind: "raw-commit",
|
|
70
|
-
selectionOrigin: "package-default",
|
|
71
|
-
selectionMode: "default",
|
|
72
|
-
upstreamSourceOrigin: "package-default",
|
|
73
|
-
saved: {
|
|
74
|
-
saved_mode: "none",
|
|
75
|
-
saved_source: "",
|
|
76
|
-
saved_requested_ref: "",
|
|
77
|
-
saved_resolved_ref: "",
|
|
78
|
-
saved_commit: "",
|
|
79
|
-
},
|
|
80
|
-
};
|
|
81
|
-
const compatibility = await assessCodexCompatibility(root, selection);
|
|
66
|
+
});
|
|
82
67
|
return {
|
|
83
68
|
root,
|
|
84
69
|
commit: receipt.commit,
|
|
@@ -39,7 +39,7 @@ function decodePathBytes(bytes) {
|
|
|
39
39
|
* syscall, so a string carrying one would open a different file than the
|
|
40
40
|
* caller named. CPython raises `UnicodeEncodeError` instead.
|
|
41
41
|
*/
|
|
42
|
-
|
|
42
|
+
function hasUnpairedSurrogate(value) {
|
|
43
43
|
for (let index = 0; index < value.length; index += 1) {
|
|
44
44
|
const unit = value.charCodeAt(index);
|
|
45
45
|
if (unit >= 0xd800 && unit <= 0xdbff) {
|
|
@@ -1,268 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { codexInspect, codexInstall, codexRemove, } from "./adapter.js";
|
|
1
|
+
import { failureResult, successResult, } from "../../adapter-result.js";
|
|
2
|
+
import { codexInspectControl, codexInspectOwnership, codexInstall, } from "./adapter.js";
|
|
4
3
|
import { codexPreparationLocation, inspectCodexPrepared, prepareCodexCandidate, readCodexPrepared, validateCodexPreparationBeforeFetch, } from "./prepare.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
4
|
+
import { codexPresentation } from "./presentation.js";
|
|
5
|
+
import { codexHome, codexPaths } from "./paths.js";
|
|
6
|
+
import { installCodexMarketplace, removeCodexMarketplace, } from "./publication.js";
|
|
7
|
+
import { readCodexRecovery } from "./recovery.js";
|
|
8
|
+
import { inspectCodexInstallation } from "./state.js";
|
|
9
9
|
function preserveFailure(result) {
|
|
10
10
|
if (result.outcome.ok) {
|
|
11
11
|
throw new Error("cannot preserve a successful adapter result as failure");
|
|
12
12
|
}
|
|
13
13
|
return { status: result.status, outcome: result.outcome };
|
|
14
14
|
}
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
function invalidStatus(result, message) {
|
|
15
|
+
export function rejectSuccessfulNonzeroStatus(result, message) {
|
|
16
|
+
if (!result.outcome.ok || result.status === 0)
|
|
17
|
+
return result;
|
|
19
18
|
return failureResult(result.outcome.operation, "invalid-status", message, [], result.outcome.messages);
|
|
20
19
|
}
|
|
21
|
-
function resultRecord(result) {
|
|
22
|
-
const value = result.outcome.ok ? result.outcome.result : null;
|
|
23
|
-
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
24
|
-
? value
|
|
25
|
-
: null;
|
|
26
|
-
}
|
|
27
|
-
function stringField(result, key) {
|
|
28
|
-
const raw = result[key];
|
|
29
|
-
if (raw === null || raw === undefined)
|
|
30
|
-
return { ok: true, value: "" };
|
|
31
|
-
if (typeof raw === "string")
|
|
32
|
-
return { ok: true, value: raw };
|
|
33
|
-
return { ok: false };
|
|
34
|
-
}
|
|
35
|
-
function resourceFlag(result, key) {
|
|
36
|
-
const resources = result.resources;
|
|
37
|
-
const bag = typeof resources === "object" &&
|
|
38
|
-
resources !== null &&
|
|
39
|
-
!Array.isArray(resources)
|
|
40
|
-
? resources
|
|
41
|
-
: {};
|
|
42
|
-
const value = bag[key];
|
|
43
|
-
return typeof value === "boolean" ? value : null;
|
|
44
|
-
}
|
|
45
|
-
function conflictDescriptions(result) {
|
|
46
|
-
const raw = result.conflicts;
|
|
47
|
-
if (raw === null || raw === undefined)
|
|
48
|
-
return [];
|
|
49
|
-
if (!Array.isArray(raw))
|
|
50
|
-
return null;
|
|
51
|
-
const descriptions = [];
|
|
52
|
-
for (const item of raw) {
|
|
53
|
-
if (typeof item !== "string" ||
|
|
54
|
-
item.length === 0 ||
|
|
55
|
-
hasTerminalControl(item)) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
descriptions.push(item);
|
|
59
|
-
}
|
|
60
|
-
return descriptions;
|
|
61
|
-
}
|
|
62
|
-
function installDecision(legacy) {
|
|
63
|
-
switch (legacy.kind) {
|
|
64
|
-
case "ok":
|
|
65
|
-
return { kind: "allowed" };
|
|
66
|
-
case "blocked":
|
|
67
|
-
return {
|
|
68
|
-
kind: "blocked",
|
|
69
|
-
output: { stdout: [], stderr: legacy.lines },
|
|
70
|
-
};
|
|
71
|
-
case "unknown":
|
|
72
|
-
return {
|
|
73
|
-
kind: "blocked",
|
|
74
|
-
output: { stdout: [], stderr: [`error: ${legacy.message}`] },
|
|
75
|
-
};
|
|
76
|
-
case "report":
|
|
77
|
-
return {
|
|
78
|
-
kind: "blocked",
|
|
79
|
-
output: {
|
|
80
|
-
stdout: [],
|
|
81
|
-
stderr: ["error: unexpected legacy report during installation"],
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
export function normalizeCodexOwnership(result) {
|
|
87
|
-
if (!result.outcome.ok)
|
|
88
|
-
return preserveFailure(result);
|
|
89
|
-
if (result.status !== 0) {
|
|
90
|
-
return invalidStatus(result, "adapter reported a failure status for inspect --view ownership");
|
|
91
|
-
}
|
|
92
|
-
const record = resultRecord(result);
|
|
93
|
-
if (record === null) {
|
|
94
|
-
return malformed(result, "adapter returned a non-object result for inspect --view ownership");
|
|
95
|
-
}
|
|
96
|
-
const pluginPresent = resourceFlag(record, "plugin");
|
|
97
|
-
if (pluginPresent === null) {
|
|
98
|
-
return malformed(result, "expected a Boolean adapter result at resources.plugin");
|
|
99
|
-
}
|
|
100
|
-
const marketplacePresent = resourceFlag(record, "marketplace");
|
|
101
|
-
if (marketplacePresent === null) {
|
|
102
|
-
return malformed(result, "expected a Boolean adapter result at resources.marketplace");
|
|
103
|
-
}
|
|
104
|
-
const identity = stringField(record, "identity_state");
|
|
105
|
-
if (!identity.ok) {
|
|
106
|
-
return malformed(result, "adapter returned a non-string identity_state for inspect --view ownership");
|
|
107
|
-
}
|
|
108
|
-
const identityState = identity.value;
|
|
109
|
-
const conflicts = conflictDescriptions(record);
|
|
110
|
-
if (conflicts === null) {
|
|
111
|
-
return malformed(result, "expected an array of strings at conflicts");
|
|
112
|
-
}
|
|
113
|
-
const legacyEligibility = identityState.length === 0
|
|
114
|
-
? {
|
|
115
|
-
kind: "blocked",
|
|
116
|
-
output: {
|
|
117
|
-
stdout: [],
|
|
118
|
-
stderr: ["error: probe did not report adapter identity state"],
|
|
119
|
-
},
|
|
120
|
-
}
|
|
121
|
-
: installDecision(requireNoLegacyState(identityState));
|
|
122
|
-
const installEligibility = legacyEligibility.kind === "blocked" || conflicts.length === 0
|
|
123
|
-
? legacyEligibility
|
|
124
|
-
: {
|
|
125
|
-
kind: "blocked",
|
|
126
|
-
output: {
|
|
127
|
-
stdout: [],
|
|
128
|
-
stderr: [
|
|
129
|
-
"Conflicting unmanaged Superpowers Codex resources require manual resolution:",
|
|
130
|
-
...conflicts.map((conflict) => `- ${conflict}`),
|
|
131
|
-
"Remove or disable each resource manually, then retry.",
|
|
132
|
-
],
|
|
133
|
-
},
|
|
134
|
-
};
|
|
135
|
-
const verification = verifyUninstalledResources(result);
|
|
136
|
-
const legacyReport = reportLegacyState(identityState);
|
|
137
|
-
let removalVerification;
|
|
138
|
-
if (!verification.ok) {
|
|
139
|
-
removalVerification = {
|
|
140
|
-
kind: "blocked",
|
|
141
|
-
output: { stdout: [], stderr: [`error: ${verification.message}`] },
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
else if (legacyReport.kind === "unknown") {
|
|
145
|
-
removalVerification = {
|
|
146
|
-
kind: "blocked",
|
|
147
|
-
output: { stdout: [], stderr: [`error: ${legacyReport.message}`] },
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
else if (legacyReport.kind === "blocked") {
|
|
151
|
-
removalVerification = {
|
|
152
|
-
kind: "blocked",
|
|
153
|
-
output: {
|
|
154
|
-
stdout: [],
|
|
155
|
-
stderr: ["error: unexpected legacy block after removal"],
|
|
156
|
-
},
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
removalVerification = { kind: "allowed" };
|
|
161
|
-
}
|
|
162
|
-
const postRemovalOutput = legacyReport.kind === "report"
|
|
163
|
-
? { stdout: legacyReport.lines, stderr: [] }
|
|
164
|
-
: { stdout: [], stderr: [] };
|
|
165
|
-
return successResult(result.outcome.operation, {
|
|
166
|
-
installEligibility,
|
|
167
|
-
removalInput: { pluginPresent, marketplacePresent },
|
|
168
|
-
removalVerification,
|
|
169
|
-
postRemovalOutput,
|
|
170
|
-
presentationValue: identityState,
|
|
171
|
-
presentationConflicts: conflicts,
|
|
172
|
-
}, result.outcome.messages);
|
|
173
|
-
}
|
|
174
|
-
export function normalizeCodexControl(result) {
|
|
175
|
-
if (!result.outcome.ok)
|
|
176
|
-
return preserveFailure(result);
|
|
177
|
-
if (result.status !== 0) {
|
|
178
|
-
return invalidStatus(result, "adapter reported a failure status for inspect --view update-control");
|
|
179
|
-
}
|
|
180
|
-
const record = resultRecord(result);
|
|
181
|
-
if (record === null) {
|
|
182
|
-
return malformed(result, "adapter returned a non-object result for inspect --view update-control");
|
|
183
|
-
}
|
|
184
|
-
const control = stringField(record, "update_control");
|
|
185
|
-
if (!control.ok) {
|
|
186
|
-
return malformed(result, "adapter returned a non-string update_control for inspect --view update-control");
|
|
187
|
-
}
|
|
188
|
-
const updateControl = control.value;
|
|
189
|
-
const probeEligibility = updateControl.length === 0
|
|
190
|
-
? {
|
|
191
|
-
kind: "blocked",
|
|
192
|
-
output: {
|
|
193
|
-
stdout: [],
|
|
194
|
-
stderr: [
|
|
195
|
-
"error: probe did not report adapter update-control capability",
|
|
196
|
-
],
|
|
197
|
-
},
|
|
198
|
-
}
|
|
199
|
-
: { kind: "allowed" };
|
|
200
|
-
const managed = requireManagedUpdateControl(updateControl);
|
|
201
|
-
const mutationEligibility = probeEligibility.kind === "blocked"
|
|
202
|
-
? probeEligibility
|
|
203
|
-
: managed.ok
|
|
204
|
-
? { kind: "allowed" }
|
|
205
|
-
: {
|
|
206
|
-
kind: "blocked",
|
|
207
|
-
output: {
|
|
208
|
-
stdout: [],
|
|
209
|
-
stderr: [`error: ${managed.message}`],
|
|
210
|
-
},
|
|
211
|
-
};
|
|
212
|
-
return successResult(result.outcome.operation, { probeEligibility, mutationEligibility, presentationValue: updateControl }, result.outcome.messages);
|
|
213
|
-
}
|
|
214
|
-
export function normalizeCodexInstalled(result, desiredCommit) {
|
|
215
|
-
if (!result.outcome.ok)
|
|
216
|
-
return preserveFailure(result);
|
|
217
|
-
if (result.status !== 0) {
|
|
218
|
-
return invalidStatus(result, "adapter reported a failure status for inspect --view fingerprint");
|
|
219
|
-
}
|
|
220
|
-
const record = resultRecord(result);
|
|
221
|
-
if (record === null) {
|
|
222
|
-
return malformed(result, "adapter returned a non-object result for inspect --view fingerprint");
|
|
223
|
-
}
|
|
224
|
-
const fingerprint = stringField(record, "fingerprint");
|
|
225
|
-
if (!fingerprint.ok) {
|
|
226
|
-
return malformed(result, "adapter returned a non-string fingerprint for inspect --view fingerprint");
|
|
227
|
-
}
|
|
228
|
-
const observedIdentity = fingerprint.value;
|
|
229
|
-
const installed = observedIdentity.length === 0
|
|
230
|
-
? { kind: "absent", observedIdentity: "" }
|
|
231
|
-
: commitMatches(desiredCommit, observedIdentity)
|
|
232
|
-
? { kind: "current", observedIdentity }
|
|
233
|
-
: { kind: "mismatch", observedIdentity };
|
|
234
|
-
return successResult(result.outcome.operation, installed, result.outcome.messages);
|
|
235
|
-
}
|
|
236
|
-
function safeHint(value) {
|
|
237
|
-
return typeof value === "string" && !hasTerminalControl(value) ? value : "";
|
|
238
|
-
}
|
|
239
|
-
export function normalizeCodexInstall(result) {
|
|
240
|
-
if (!result.outcome.ok)
|
|
241
|
-
return preserveFailure(result);
|
|
242
|
-
if (result.status !== 0) {
|
|
243
|
-
return invalidStatus(result, "adapter reported a failure status for install");
|
|
244
|
-
}
|
|
245
|
-
const record = resultRecord(result);
|
|
246
|
-
if (record === null) {
|
|
247
|
-
return malformed(result, "adapter returned a non-object result for install");
|
|
248
|
-
}
|
|
249
|
-
const hints = record.verification_hints;
|
|
250
|
-
const bag = typeof hints === "object" && hints !== null && !Array.isArray(hints)
|
|
251
|
-
? hints
|
|
252
|
-
: {};
|
|
253
|
-
return successResult(result.outcome.operation, codexInstallReceipt(safeHint(bag.missing), safeHint(bag.mismatch)), result.outcome.messages);
|
|
254
|
-
}
|
|
255
|
-
// The canonical payload normalizer has no context parameter. Keep the native
|
|
256
|
-
// call's defensive success/nonzero-status guard beside the concrete adapter,
|
|
257
|
-
// where the exact legacy diagnostic can still name ctx.root.
|
|
258
|
-
export function normalizeCodexInstallForContext(result, ctx) {
|
|
259
|
-
if (!result.outcome.ok)
|
|
260
|
-
return preserveFailure(result);
|
|
261
|
-
if (result.status !== 0) {
|
|
262
|
-
return invalidStatus(result, codexPresentation.callFailure("install", ctx).invalidStatus);
|
|
263
|
-
}
|
|
264
|
-
return normalizeCodexInstall(result);
|
|
265
|
-
}
|
|
266
20
|
function requirements(command, env) {
|
|
267
21
|
if (command !== "probe" &&
|
|
268
22
|
command !== "install" &&
|
|
@@ -281,16 +35,34 @@ function requirements(command, env) {
|
|
|
281
35
|
];
|
|
282
36
|
}
|
|
283
37
|
async function mutationRoots(ctx) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
38
|
+
return [codexHome(ctx.env ?? {}, process.cwd())];
|
|
39
|
+
}
|
|
40
|
+
async function inspectCodexUpdateControl(ctx) {
|
|
41
|
+
const result = rejectSuccessfulNonzeroStatus(await codexInspectControl(ctx), "adapter reported a failure status for inspect --view update-control");
|
|
42
|
+
if (!result.outcome.ok)
|
|
43
|
+
return result;
|
|
44
|
+
const paths = codexPaths(ctx.env ?? {}, process.cwd());
|
|
45
|
+
let diagnostic = null;
|
|
46
|
+
try {
|
|
47
|
+
if ((await readCodexRecovery(paths)) !== null) {
|
|
48
|
+
diagnostic = `Codex recovery required; preserve material at ${paths.recoveryRoot} for manual resolution`;
|
|
49
|
+
}
|
|
288
50
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
throw new SafetyError("codex-harness", "cannot determine Codex state root without HOME");
|
|
51
|
+
catch {
|
|
52
|
+
diagnostic = `cannot inspect Codex recovery state at ${paths.recoveryRoot}`;
|
|
292
53
|
}
|
|
293
|
-
|
|
54
|
+
if (diagnostic === null)
|
|
55
|
+
return result;
|
|
56
|
+
const decision = {
|
|
57
|
+
kind: "blocked",
|
|
58
|
+
output: { stdout: [], stderr: [`error: ${diagnostic}`] },
|
|
59
|
+
};
|
|
60
|
+
return successResult(result.outcome.operation, {
|
|
61
|
+
probeEligibility: decision,
|
|
62
|
+
mutationEligibility: decision,
|
|
63
|
+
presentationValue: diagnostic,
|
|
64
|
+
recoveryState: "required",
|
|
65
|
+
}, result.outcome.messages);
|
|
294
66
|
}
|
|
295
67
|
export const codexHarness = {
|
|
296
68
|
preparationLocation: codexPreparationLocation,
|
|
@@ -299,18 +71,19 @@ export const codexHarness = {
|
|
|
299
71
|
prepareCandidate: prepareCodexCandidate,
|
|
300
72
|
inspectPrepared: inspectCodexPrepared,
|
|
301
73
|
readPrepared: readCodexPrepared,
|
|
302
|
-
inspectOwnership:
|
|
303
|
-
inspectUpdateControl:
|
|
304
|
-
inspectInstalled:
|
|
305
|
-
install: async (
|
|
306
|
-
|
|
307
|
-
|
|
74
|
+
inspectOwnership: codexInspectOwnership,
|
|
75
|
+
inspectUpdateControl: inspectCodexUpdateControl,
|
|
76
|
+
inspectInstalled: inspectCodexInstallation,
|
|
77
|
+
install: async (artifact, ctx) => installCodexMarketplace(artifact, ctx, async (root, context) => {
|
|
78
|
+
const result = await codexInstall(root, context);
|
|
79
|
+
return rejectSuccessfulNonzeroStatus(result, codexPresentation.callFailure("install", context).invalidStatus);
|
|
80
|
+
}),
|
|
308
81
|
remove: async (input, ctx) => {
|
|
309
|
-
const result = await
|
|
82
|
+
const result = await removeCodexMarketplace(input, ctx);
|
|
310
83
|
if (!result.outcome.ok)
|
|
311
84
|
return preserveFailure(result);
|
|
312
85
|
if (result.status !== 0) {
|
|
313
|
-
return
|
|
86
|
+
return rejectSuccessfulNonzeroStatus(result, codexPresentation.callFailure("remove", ctx, input).invalidStatus);
|
|
314
87
|
}
|
|
315
88
|
return successResult(result.outcome.operation, null, result.outcome.messages);
|
|
316
89
|
},
|
|
@@ -4,7 +4,7 @@ import { assertExistingContained, assertProspectiveContained, classifyPathNoFoll
|
|
|
4
4
|
import { SafetyError } from "../../safety-error.js";
|
|
5
5
|
import { parseStrictJson } from "../../strict-json.js";
|
|
6
6
|
// Annotated, not `as const`, matching
|
|
7
|
-
// `src/provenance.ts:20::
|
|
7
|
+
// `src/provenance.ts:20::const PROVENANCE_STRICT_PROFILE` and
|
|
8
8
|
// `src/selection-store.ts:19::const SELECTION_JSON_PROFILE`. The annotation
|
|
9
9
|
// rejects a misspelled or unsupported profile field here rather than at the
|
|
10
10
|
// parseStrictJson call.
|
|
@@ -66,9 +66,13 @@ export function codexInstalledPluginsFromJson(raw) {
|
|
|
66
66
|
enabled: typeof item.enabled === "boolean" ? item.enabled : null,
|
|
67
67
|
}));
|
|
68
68
|
}
|
|
69
|
-
export function marketplaceRootFromJson(raw, marketplaceName) {
|
|
69
|
+
export function marketplaceRootFromJson(raw, marketplaceName, requireUnique = false) {
|
|
70
70
|
const items = checkedItems(raw, ACCEPT_CONSTANTS, "marketplaces", "name");
|
|
71
|
-
const
|
|
71
|
+
const matches = items.filter((item) => item.name === marketplaceName);
|
|
72
|
+
if (requireUnique && matches.length > 1) {
|
|
73
|
+
fail("marketplace appears more than once");
|
|
74
|
+
}
|
|
75
|
+
const match = matches[0];
|
|
72
76
|
if (match === undefined)
|
|
73
77
|
return "";
|
|
74
78
|
if (typeof match.root !== "string" || match.root.length === 0) {
|
|
@@ -52,7 +52,6 @@ export function reportLegacyState(identityState) {
|
|
|
52
52
|
}
|
|
53
53
|
return unknownState(identityState);
|
|
54
54
|
}
|
|
55
|
-
export { verifyInstalledFingerprint, } from "./presentation.js";
|
|
56
55
|
// Ported from
|
|
57
56
|
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/lifecycle.sh:62-70::spw_require_managed_update_control`.
|
|
58
57
|
// Both refusals reached spw_die in the shell, so both carry the `error: ` prefix
|
|
@@ -71,65 +70,122 @@ export function requireManagedUpdateControl(value) {
|
|
|
71
70
|
message: `unknown adapter update-control capability: ${value}`,
|
|
72
71
|
};
|
|
73
72
|
}
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return { kind: "call-failed" };
|
|
80
|
-
const value = outcome.result;
|
|
81
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
82
|
-
return { kind: "unusable" };
|
|
83
|
-
}
|
|
84
|
-
return { kind: "object", value: value };
|
|
85
|
-
}
|
|
86
|
-
// Ported from
|
|
87
|
-
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/lifecycle.sh:126-141::spw_verify_uninstalled_resources`,
|
|
88
|
-
// with the Boolean coercion of
|
|
89
|
-
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/adapter.sh:58-73::spw_adapter_result_boolean`
|
|
90
|
-
// folded in: a non-Boolean is a hard failure, never a falsy "absent".
|
|
91
|
-
export function verifyUninstalledResources(inspectResult) {
|
|
92
|
-
const read = readResult(inspectResult);
|
|
93
|
-
if (read.kind !== "object") {
|
|
94
|
-
return {
|
|
95
|
-
ok: false,
|
|
96
|
-
message: "cannot read the adapter ownership inspection after removal",
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
const inspected = read.value;
|
|
100
|
-
// A missing or non-object `resources` falls THROUGH to the Boolean check
|
|
101
|
-
// rather than getting its own message.
|
|
102
|
-
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/adapter.sh:70::expected`
|
|
103
|
-
// emits "expected Boolean adapter result at resources.plugin" for input {} —
|
|
104
|
-
// the input tests/unit/harnesses/codex/lifecycle.test.ts's "a non-object resources falls
|
|
105
|
-
// through to the Boolean message" exercises — so a distinct "not an object"
|
|
106
|
-
// message here would be a port-only divergence. Parity, not divergence. Spec
|
|
107
|
-
// §6.2.3 item 3.
|
|
108
|
-
const resources = inspected.resources;
|
|
109
|
-
const bag = typeof resources === "object" &&
|
|
110
|
-
resources !== null &&
|
|
111
|
-
!Array.isArray(resources)
|
|
112
|
-
? resources
|
|
113
|
-
: {};
|
|
114
|
-
for (const key of ["plugin", "marketplace"]) {
|
|
115
|
-
if (typeof bag[key] !== "boolean") {
|
|
73
|
+
function installDecision(legacy) {
|
|
74
|
+
switch (legacy.kind) {
|
|
75
|
+
case "ok":
|
|
76
|
+
return { kind: "allowed" };
|
|
77
|
+
case "blocked":
|
|
116
78
|
return {
|
|
117
|
-
|
|
118
|
-
|
|
79
|
+
kind: "blocked",
|
|
80
|
+
output: { stdout: [], stderr: legacy.lines },
|
|
119
81
|
};
|
|
82
|
+
case "unknown":
|
|
83
|
+
return {
|
|
84
|
+
kind: "blocked",
|
|
85
|
+
output: { stdout: [], stderr: [`error: ${legacy.message}`] },
|
|
86
|
+
};
|
|
87
|
+
case "report":
|
|
88
|
+
return {
|
|
89
|
+
kind: "blocked",
|
|
90
|
+
output: {
|
|
91
|
+
stdout: [],
|
|
92
|
+
stderr: ["error: unexpected legacy report during installation"],
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export function codexOwnershipInspection(identityState, removalInput, conflicts) {
|
|
98
|
+
const { pluginPresent, marketplacePresent } = removalInput;
|
|
99
|
+
const legacyEligibility = identityState.length === 0
|
|
100
|
+
? {
|
|
101
|
+
kind: "blocked",
|
|
102
|
+
output: {
|
|
103
|
+
stdout: [],
|
|
104
|
+
stderr: ["error: probe did not report adapter identity state"],
|
|
105
|
+
},
|
|
120
106
|
}
|
|
107
|
+
: installDecision(requireNoLegacyState(identityState));
|
|
108
|
+
const installEligibility = legacyEligibility.kind === "blocked" || conflicts.length === 0
|
|
109
|
+
? legacyEligibility
|
|
110
|
+
: {
|
|
111
|
+
kind: "blocked",
|
|
112
|
+
output: {
|
|
113
|
+
stdout: [],
|
|
114
|
+
stderr: [
|
|
115
|
+
"Conflicting unmanaged Superpowers Codex resources require manual resolution:",
|
|
116
|
+
...conflicts.map((conflict) => `- ${conflict}`),
|
|
117
|
+
"Remove or disable each resource manually, then retry.",
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
const legacyReport = reportLegacyState(identityState);
|
|
122
|
+
let removalVerification;
|
|
123
|
+
if (pluginPresent || marketplacePresent) {
|
|
124
|
+
const message = pluginPresent
|
|
125
|
+
? "owned plugin resource is still installed after removal"
|
|
126
|
+
: "owned marketplace resource is still registered after removal";
|
|
127
|
+
removalVerification = {
|
|
128
|
+
kind: "blocked",
|
|
129
|
+
output: { stdout: [], stderr: ["error: " + message] },
|
|
130
|
+
};
|
|
121
131
|
}
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
132
|
+
else if (legacyReport.kind === "unknown") {
|
|
133
|
+
removalVerification = {
|
|
134
|
+
kind: "blocked",
|
|
135
|
+
output: { stdout: [], stderr: [`error: ${legacyReport.message}`] },
|
|
126
136
|
};
|
|
127
137
|
}
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
138
|
+
else if (legacyReport.kind === "blocked") {
|
|
139
|
+
removalVerification = {
|
|
140
|
+
kind: "blocked",
|
|
141
|
+
output: {
|
|
142
|
+
stdout: [],
|
|
143
|
+
stderr: ["error: unexpected legacy block after removal"],
|
|
144
|
+
},
|
|
132
145
|
};
|
|
133
146
|
}
|
|
134
|
-
|
|
147
|
+
else {
|
|
148
|
+
removalVerification = { kind: "allowed" };
|
|
149
|
+
}
|
|
150
|
+
const postRemovalOutput = legacyReport.kind === "report"
|
|
151
|
+
? { stdout: legacyReport.lines, stderr: [] }
|
|
152
|
+
: { stdout: [], stderr: [] };
|
|
153
|
+
return {
|
|
154
|
+
installEligibility,
|
|
155
|
+
removalInput,
|
|
156
|
+
removalVerification,
|
|
157
|
+
postRemovalOutput,
|
|
158
|
+
presentationValue: identityState,
|
|
159
|
+
presentationConflicts: conflicts,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
export function codexControlInspection(updateControl) {
|
|
163
|
+
const probeEligibility = updateControl.length === 0
|
|
164
|
+
? {
|
|
165
|
+
kind: "blocked",
|
|
166
|
+
output: {
|
|
167
|
+
stdout: [],
|
|
168
|
+
stderr: [
|
|
169
|
+
"error: probe did not report adapter update-control capability",
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
: { kind: "allowed" };
|
|
174
|
+
const managed = requireManagedUpdateControl(updateControl);
|
|
175
|
+
const mutationEligibility = probeEligibility.kind === "blocked"
|
|
176
|
+
? probeEligibility
|
|
177
|
+
: managed.ok
|
|
178
|
+
? { kind: "allowed" }
|
|
179
|
+
: {
|
|
180
|
+
kind: "blocked",
|
|
181
|
+
output: {
|
|
182
|
+
stdout: [],
|
|
183
|
+
stderr: [`error: ${managed.message}`],
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
return {
|
|
187
|
+
probeEligibility,
|
|
188
|
+
mutationEligibility,
|
|
189
|
+
presentationValue: updateControl,
|
|
190
|
+
};
|
|
135
191
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { escapeNonAscii
|
|
1
|
+
import { escapeNonAscii } from "../../python-json-format.js";
|
|
2
2
|
import { SafetyError } from "../../safety-error.js";
|
|
3
3
|
import { isRawNumber, isRawObject, parseStrictJsonPreservingNumbers, } from "../../strict-json.js";
|
|
4
4
|
const OVERLAY_PROFILE = {
|
|
@@ -9,14 +9,17 @@ const OVERLAY_PROFILE = {
|
|
|
9
9
|
/**
|
|
10
10
|
* Apply the manager's manifest overlay to a Codex plugin manifest.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* `tests/baseline/
|
|
16
|
-
*
|
|
17
|
-
* the fixtures under `tests/fixtures/baseline/overlay-parity/`. Known,
|
|
18
|
-
* deliberate divergences from strict byte parity are recorded in
|
|
12
|
+
* Preserves validated JSON numeric source tokens while applying the manager's
|
|
13
|
+
* manifest fields, with a trailing newline. The baseline contract lives in
|
|
14
|
+
* `tests/baseline/harnesses/codex/manifest-overlay-parity.test.ts` against the
|
|
15
|
+
* fixtures under `tests/fixtures/baseline/overlay-parity/`. The 5,000-digit
|
|
16
|
+
* integer widening is recorded in
|
|
19
17
|
* `tests/fixtures/baseline/overlay-parity/divergent/README.md`.
|
|
18
|
+
*
|
|
19
|
+
* Decimal and exponent tokens are range-checked before emission. The strict
|
|
20
|
+
* parser rejects non-standard constants before they reach the emitter. Object
|
|
21
|
+
* member order, string escaping, and unknown fields remain part of this
|
|
22
|
+
* overlay contract.
|
|
20
23
|
*/
|
|
21
24
|
export function applyManifestOverlay(source, version, path) {
|
|
22
25
|
let parsed;
|
|
@@ -43,14 +46,10 @@ export function applyManifestOverlay(source, version, path) {
|
|
|
43
46
|
throw rewrapNumberOutOfRange(cause, path);
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
// with no indication of which manifest produced the diagnostic, unlike every
|
|
51
|
-
// other overlay failure. This rewrap adds the path without touching
|
|
52
|
-
// formatPythonNumber's own contract, and is scoped to exactly this message so
|
|
53
|
-
// it cannot swallow or reword any other overlay throw.
|
|
49
|
+
// emitNumber has no manifest path, so it throws a bare
|
|
50
|
+
// `JSON number out of range: ${raw}`. This rewrap adds the path without
|
|
51
|
+
// touching the local emitter's own contract, and is scoped to exactly this
|
|
52
|
+
// message so it cannot swallow or reword any other overlay throw.
|
|
54
53
|
const NUMBER_OUT_OF_RANGE_PREFIX = "JSON number out of range: ";
|
|
55
54
|
function rewrapNumberOutOfRange(cause, path) {
|
|
56
55
|
if (cause instanceof SafetyError &&
|
|
@@ -60,6 +59,12 @@ function rewrapNumberOutOfRange(cause, path) {
|
|
|
60
59
|
}
|
|
61
60
|
return cause;
|
|
62
61
|
}
|
|
62
|
+
function emitNumber(raw) {
|
|
63
|
+
if (/[.eE]/.test(raw) && !Number.isFinite(Number(raw))) {
|
|
64
|
+
throw new SafetyError("manifest-overlay", `JSON number out of range: ${raw}`);
|
|
65
|
+
}
|
|
66
|
+
return raw;
|
|
67
|
+
}
|
|
63
68
|
function setMember(entries, key, value) {
|
|
64
69
|
const index = entries.findIndex(([existing]) => existing === key);
|
|
65
70
|
if (index === -1)
|
|
@@ -105,7 +110,7 @@ function emit(value, depth) {
|
|
|
105
110
|
// Order matters: isRawNumber before isRawObject, and both before Array —
|
|
106
111
|
// the branded records are objects too.
|
|
107
112
|
if (isRawNumber(value))
|
|
108
|
-
return
|
|
113
|
+
return emitNumber(value.source);
|
|
109
114
|
if (isRawObject(value))
|
|
110
115
|
return emitObject(value.entries, depth);
|
|
111
116
|
const inner = " ".repeat(depth + 1);
|