@nggaigc/cli 0.1.16 → 0.1.17
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 +1 -1
- package/dist/bin.js +0 -0
- package/dist/independent-skill.js +197 -44
- package/package.json +4 -5
- package/dist/archive.test.js +0 -168
- package/dist/bin.test.js +0 -99
- package/dist/independent-skill.test.js +0 -965
- package/dist/install.test.js +0 -264
- package/dist/production-trust.test.js +0 -53
- package/dist/runtime.test.js +0 -53
- package/dist/seedream-root-compat.test.js +0 -158
- package/dist/windows-secure-store.integration.test.js +0 -122
|
@@ -1,965 +0,0 @@
|
|
|
1
|
-
import { createHash, generateKeyPairSync, randomBytes, sign, } from "node:crypto";
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import { createServer } from "node:http";
|
|
4
|
-
import { copyFile, mkdtemp, mkdir, readFile, readdir, rename, rm, symlink, writeFile, } from "node:fs/promises";
|
|
5
|
-
import os from "node:os";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
8
|
-
import { main } from "./bin.js";
|
|
9
|
-
import { runSkillStartCommand } from "./skill-command.js";
|
|
10
|
-
import { prepareIndependentSkill, projectActiveSkillCatalog, rollbackIndependentSkill, startIndependentSkill, } from "./independent-skill.js";
|
|
11
|
-
const keypair = generateKeyPairSync("ed25519");
|
|
12
|
-
const publicKeyPem = keypair.publicKey
|
|
13
|
-
.export({ type: "spki", format: "pem" })
|
|
14
|
-
.toString();
|
|
15
|
-
const trust = {
|
|
16
|
-
keyId: "ngg-test-independent-skill-1",
|
|
17
|
-
publicKeyPem,
|
|
18
|
-
spkiSha256: createHash("sha256")
|
|
19
|
-
.update(keypair.publicKey.export({ type: "spki", format: "der" }))
|
|
20
|
-
.digest("hex"),
|
|
21
|
-
};
|
|
22
|
-
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
23
|
-
const managedRootFiles = [
|
|
24
|
-
"skill-sources/nggaigc/SKILL.md",
|
|
25
|
-
"skill-sources/nggaigc/agents/openai.yaml",
|
|
26
|
-
"skill-sources/nggaigc/references/catalog-and-updates.md",
|
|
27
|
-
"skill-sources/nggaigc/references/source.json",
|
|
28
|
-
"skill-sources/nggaigc/scripts/ngg_client.mjs",
|
|
29
|
-
"plugins/nggaigc/runtime/bootstrap-public.json",
|
|
30
|
-
"plugins/nggaigc/runtime/launch-ngg-client.cjs",
|
|
31
|
-
"plugins/nggaigc/runtime/ngg-session.mjs",
|
|
32
|
-
"plugins/nggaigc/runtime/windows-credential.ps1",
|
|
33
|
-
];
|
|
34
|
-
async function managedRootDigest() {
|
|
35
|
-
const root = path.resolve(import.meta.dirname, "../../..");
|
|
36
|
-
const parts = [];
|
|
37
|
-
for (const relative of managedRootFiles)
|
|
38
|
-
parts.push(Buffer.from(relative), await readFile(path.join(root, relative)));
|
|
39
|
-
return sha256(Buffer.concat(parts));
|
|
40
|
-
}
|
|
41
|
-
function canonical(value) {
|
|
42
|
-
if (Array.isArray(value))
|
|
43
|
-
return `[${value.map(canonical).join(",")}]`;
|
|
44
|
-
if (value !== null && typeof value === "object")
|
|
45
|
-
return `{${Object.entries(value)
|
|
46
|
-
.sort(([a], [b]) => a.localeCompare(b, "en"))
|
|
47
|
-
.map(([key, child]) => `${JSON.stringify(key)}:${canonical(child)}`)
|
|
48
|
-
.join(",")}}`;
|
|
49
|
-
return JSON.stringify(value);
|
|
50
|
-
}
|
|
51
|
-
function credentialFrame(value) {
|
|
52
|
-
const payload = Buffer.from(JSON.stringify(value));
|
|
53
|
-
const bytes = Buffer.alloc(payload.length + 4);
|
|
54
|
-
bytes.writeUInt32BE(payload.length, 0);
|
|
55
|
-
payload.copy(bytes, 4);
|
|
56
|
-
payload.fill(0);
|
|
57
|
-
return bytes;
|
|
58
|
-
}
|
|
59
|
-
async function testCredentialOperation(distribution, target, operation, input, epoch) {
|
|
60
|
-
const powershell = path.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
61
|
-
const args = [
|
|
62
|
-
"-NoLogo",
|
|
63
|
-
"-NoProfile",
|
|
64
|
-
"-NonInteractive",
|
|
65
|
-
"-File",
|
|
66
|
-
path.join(distribution, "windows-credential.ps1"),
|
|
67
|
-
"-Distribution",
|
|
68
|
-
distribution,
|
|
69
|
-
"-Operation",
|
|
70
|
-
operation,
|
|
71
|
-
"-NodePath",
|
|
72
|
-
process.execPath,
|
|
73
|
-
"-Target",
|
|
74
|
-
target,
|
|
75
|
-
];
|
|
76
|
-
if (epoch)
|
|
77
|
-
args.push("-ExpectedEpoch", epoch);
|
|
78
|
-
const child = spawn(powershell, args, {
|
|
79
|
-
windowsHide: true,
|
|
80
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
81
|
-
env: {
|
|
82
|
-
SystemRoot: process.env.SystemRoot,
|
|
83
|
-
LOCALAPPDATA: process.env.LOCALAPPDATA,
|
|
84
|
-
USERPROFILE: process.env.USERPROFILE,
|
|
85
|
-
PATH: process.env.PATH,
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
const chunks = [];
|
|
89
|
-
child.stdout.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
90
|
-
child.stderr.resume();
|
|
91
|
-
child.stdin.end(input);
|
|
92
|
-
const code = await new Promise((resolve, reject) => {
|
|
93
|
-
child.once("error", reject);
|
|
94
|
-
child.once("exit", (value) => resolve(value ?? 1));
|
|
95
|
-
});
|
|
96
|
-
const value = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
97
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
98
|
-
throw new Error("TEST_CREDENTIAL_RESPONSE_INVALID");
|
|
99
|
-
return { code, value: value };
|
|
100
|
-
}
|
|
101
|
-
function crc32(bytes) {
|
|
102
|
-
let crc = 0xffffffff;
|
|
103
|
-
for (const byte of bytes) {
|
|
104
|
-
crc ^= byte;
|
|
105
|
-
for (let bit = 0; bit < 8; bit++)
|
|
106
|
-
crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
|
|
107
|
-
}
|
|
108
|
-
return (crc ^ 0xffffffff) >>> 0;
|
|
109
|
-
}
|
|
110
|
-
function storedZip(entries) {
|
|
111
|
-
const local = [];
|
|
112
|
-
const central = [];
|
|
113
|
-
let offset = 0;
|
|
114
|
-
for (const [name, body] of Object.entries(entries)) {
|
|
115
|
-
const label = Buffer.from(name, "utf8");
|
|
116
|
-
const head = Buffer.alloc(30);
|
|
117
|
-
const checksum = crc32(body);
|
|
118
|
-
head.writeUInt32LE(0x04034b50, 0);
|
|
119
|
-
head.writeUInt16LE(20, 4);
|
|
120
|
-
head.writeUInt32LE(checksum, 14);
|
|
121
|
-
head.writeUInt32LE(body.length, 18);
|
|
122
|
-
head.writeUInt32LE(body.length, 22);
|
|
123
|
-
head.writeUInt16LE(label.length, 26);
|
|
124
|
-
local.push(head, label, body);
|
|
125
|
-
const dir = Buffer.alloc(46);
|
|
126
|
-
dir.writeUInt32LE(0x02014b50, 0);
|
|
127
|
-
dir.writeUInt16LE(0x0314, 4);
|
|
128
|
-
dir.writeUInt16LE(20, 6);
|
|
129
|
-
dir.writeUInt32LE(checksum, 16);
|
|
130
|
-
dir.writeUInt32LE(body.length, 20);
|
|
131
|
-
dir.writeUInt32LE(body.length, 24);
|
|
132
|
-
dir.writeUInt16LE(label.length, 28);
|
|
133
|
-
dir.writeUInt32LE((0o100644 << 16) >>> 0, 38);
|
|
134
|
-
dir.writeUInt32LE(offset, 42);
|
|
135
|
-
central.push(dir, label);
|
|
136
|
-
offset += head.length + label.length + body.length;
|
|
137
|
-
}
|
|
138
|
-
const middle = Buffer.concat(central);
|
|
139
|
-
const end = Buffer.alloc(22);
|
|
140
|
-
end.writeUInt32LE(0x06054b50, 0);
|
|
141
|
-
end.writeUInt16LE(Object.keys(entries).length, 8);
|
|
142
|
-
end.writeUInt16LE(Object.keys(entries).length, 10);
|
|
143
|
-
end.writeUInt32LE(middle.length, 12);
|
|
144
|
-
end.writeUInt32LE(offset, 16);
|
|
145
|
-
return Buffer.concat([...local, middle, end]);
|
|
146
|
-
}
|
|
147
|
-
function fixture(id, version = "1.0.0", files = {
|
|
148
|
-
"SKILL.md": `---\nname: ${id.slice(6)}\ndescription: A local test ability.\n---\n\nCreate a local test marker only when requested.\n`,
|
|
149
|
-
}, requires = []) {
|
|
150
|
-
const compatibility = {
|
|
151
|
-
rootSkill: { minimum: "1.0.0", maximumExclusive: "2.0.0" },
|
|
152
|
-
platform: { minimum: "0.1.0", maximumExclusive: "1.0.0" },
|
|
153
|
-
};
|
|
154
|
-
const payload = {
|
|
155
|
-
schemaVersion: "ngg-skill-package/v1",
|
|
156
|
-
artifactId: id,
|
|
157
|
-
packageVersion: version,
|
|
158
|
-
compatibility,
|
|
159
|
-
files: Object.entries(files).map(([filePath, content]) => ({
|
|
160
|
-
path: filePath,
|
|
161
|
-
sha256: sha256(content),
|
|
162
|
-
sizeBytes: Buffer.byteLength(content),
|
|
163
|
-
executable: false,
|
|
164
|
-
})),
|
|
165
|
-
};
|
|
166
|
-
const bytes = storedZip({
|
|
167
|
-
...Object.fromEntries(Object.entries(files).map(([name, content]) => [
|
|
168
|
-
name,
|
|
169
|
-
Buffer.from(content),
|
|
170
|
-
])),
|
|
171
|
-
"skill-package.v1.json": Buffer.from(JSON.stringify(payload)),
|
|
172
|
-
});
|
|
173
|
-
const manifest = {
|
|
174
|
-
id,
|
|
175
|
-
version,
|
|
176
|
-
lifecycle: { status: "published" },
|
|
177
|
-
requires,
|
|
178
|
-
package: {
|
|
179
|
-
sha256: sha256(bytes),
|
|
180
|
-
sizeBytes: bytes.length,
|
|
181
|
-
signature: {
|
|
182
|
-
algorithm: "ed25519",
|
|
183
|
-
keyId: trust.keyId,
|
|
184
|
-
value: sign(null, bytes, keypair.privateKey).toString("base64"),
|
|
185
|
-
},
|
|
186
|
-
compatibility,
|
|
187
|
-
},
|
|
188
|
-
};
|
|
189
|
-
const entry = {
|
|
190
|
-
id,
|
|
191
|
-
version,
|
|
192
|
-
state: "active",
|
|
193
|
-
manifestSha256: sha256(canonical(manifest)),
|
|
194
|
-
manifest,
|
|
195
|
-
};
|
|
196
|
-
return { entry, bytes };
|
|
197
|
-
}
|
|
198
|
-
const workdirs = [];
|
|
199
|
-
afterEach(async () => {
|
|
200
|
-
for (const directory of workdirs.splice(0))
|
|
201
|
-
await rm(directory, { recursive: true, force: true });
|
|
202
|
-
});
|
|
203
|
-
async function setup(...items) {
|
|
204
|
-
const directory = await mkdtemp(path.join(os.tmpdir(), "nggaigc-independent-skill-"));
|
|
205
|
-
workdirs.push(directory);
|
|
206
|
-
const index = {
|
|
207
|
-
schemaVersion: "nggaigc-independent-skill-index/v1",
|
|
208
|
-
releaseId: "release.test.1",
|
|
209
|
-
entries: items.map((item) => item.entry),
|
|
210
|
-
};
|
|
211
|
-
let calls = 0;
|
|
212
|
-
const fetcher = ((url) => {
|
|
213
|
-
calls++;
|
|
214
|
-
const item = items.find((candidate) => new URL(String(url)).pathname.endsWith(`/${candidate.entry.id}/${candidate.entry.version}`));
|
|
215
|
-
if (!item)
|
|
216
|
-
return Promise.resolve(new Response(null, { status: 404 }));
|
|
217
|
-
const response = new Response(Uint8Array.from(item.bytes), { status: 200 });
|
|
218
|
-
Object.defineProperty(response, "url", { value: String(url) });
|
|
219
|
-
return Promise.resolve(response);
|
|
220
|
-
});
|
|
221
|
-
const prepare = (skillId, skillVersion = "1.0.0", override = {}) => prepareIndependentSkill({
|
|
222
|
-
index,
|
|
223
|
-
skillId,
|
|
224
|
-
skillVersion,
|
|
225
|
-
rootSkillVersion: "1.1.14",
|
|
226
|
-
platformVersion: "0.1.12",
|
|
227
|
-
codexHome: path.join(directory, ".codex"),
|
|
228
|
-
trust,
|
|
229
|
-
fetcher,
|
|
230
|
-
...override,
|
|
231
|
-
});
|
|
232
|
-
return {
|
|
233
|
-
directory,
|
|
234
|
-
index,
|
|
235
|
-
prepare,
|
|
236
|
-
fetcher,
|
|
237
|
-
get calls() {
|
|
238
|
-
return calls;
|
|
239
|
-
},
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
describe("independent skill package stage 1", () => {
|
|
243
|
-
it("maps the W3 independent identity to the safe hyphenated local slug without touching root", async () => {
|
|
244
|
-
const rootDigest = await managedRootDigest();
|
|
245
|
-
const source = path.resolve(import.meta.dirname, "../../../skill-sources/w3-multimodal-video");
|
|
246
|
-
const ability = fixture("skill.w3-multimodal-video", "1.0.0", {
|
|
247
|
-
"SKILL.md": await readFile(path.join(source, "SKILL.md"), "utf8"),
|
|
248
|
-
"references/workflow.md": await readFile(path.join(source, "references/workflow.md"), "utf8"),
|
|
249
|
-
});
|
|
250
|
-
const test = await setup(ability);
|
|
251
|
-
const ready = await test.prepare(ability.entry.id);
|
|
252
|
-
expect(ready.skillPath).toBe(path.join(test.directory, ".codex/skills/w3-multimodal-video/SKILL.md"));
|
|
253
|
-
expect(await readFile(ready.skillPath, "utf8")).toContain("name: w3-multimodal-video");
|
|
254
|
-
expect(await managedRootDigest()).toBe(rootDigest);
|
|
255
|
-
});
|
|
256
|
-
it("hands off the actual W3 docs-only package through the generic start-skill command in a synthetic loopback catalog", async () => {
|
|
257
|
-
const sourceRoot = path.resolve(import.meta.dirname, "../../..");
|
|
258
|
-
const skillSource = path.join(sourceRoot, "skill-sources/w3-multimodal-video");
|
|
259
|
-
const files = {
|
|
260
|
-
"SKILL.md": await readFile(path.join(skillSource, "SKILL.md"), "utf8"),
|
|
261
|
-
"references/workflow.md": await readFile(path.join(skillSource, "references/workflow.md"), "utf8"),
|
|
262
|
-
};
|
|
263
|
-
const ability = fixture("skill.w3-multimodal-video", "1.0.0", files);
|
|
264
|
-
const syntheticTrust = {
|
|
265
|
-
...trust,
|
|
266
|
-
keyId: "ngg-signing-synthetic-w3-only",
|
|
267
|
-
};
|
|
268
|
-
const rootManifest = JSON.parse(await readFile(path.join(sourceRoot, "release/catalog-bundles/nggaigc-root-and-wan-1.1.10/manifests/skill.nggaigc.1.1.10.json"), "utf8"));
|
|
269
|
-
const manifest = structuredClone(rootManifest);
|
|
270
|
-
manifest.schemaVersion = "1.1";
|
|
271
|
-
manifest.id = ability.entry.id;
|
|
272
|
-
manifest.version = ability.entry.version;
|
|
273
|
-
manifest.role = "independent_skill";
|
|
274
|
-
manifest.metadata = {
|
|
275
|
-
name: "W3 多模态视频生成",
|
|
276
|
-
summary: "以已验证素材生成视频的独立 Codex 引导。",
|
|
277
|
-
};
|
|
278
|
-
manifest.dependencies = [
|
|
279
|
-
{
|
|
280
|
-
kind: "capability",
|
|
281
|
-
id: "capability.w3.multimodal-video",
|
|
282
|
-
version: "1.0.0",
|
|
283
|
-
required: true,
|
|
284
|
-
},
|
|
285
|
-
];
|
|
286
|
-
manifest.execution = {
|
|
287
|
-
mode: "local_codex",
|
|
288
|
-
entrypoint: "SKILL.md",
|
|
289
|
-
clientProtocol: 1,
|
|
290
|
-
};
|
|
291
|
-
manifest.documentation = Object.fromEntries(["overview", "usage", "pricing", "input", "output"].map((field) => [
|
|
292
|
-
field,
|
|
293
|
-
"docs/api/w3-video-generations.md",
|
|
294
|
-
]));
|
|
295
|
-
delete manifest.catalogResolution;
|
|
296
|
-
manifest.package = {
|
|
297
|
-
version: ability.entry.version,
|
|
298
|
-
artifactPath: "packages/skills/w3-multimodal-video-1.0.0.zip",
|
|
299
|
-
sha256: sha256(ability.bytes),
|
|
300
|
-
sizeBytes: ability.bytes.length,
|
|
301
|
-
signature: {
|
|
302
|
-
algorithm: "ed25519",
|
|
303
|
-
keyId: syntheticTrust.keyId,
|
|
304
|
-
value: sign(null, ability.bytes, keypair.privateKey).toString("base64"),
|
|
305
|
-
},
|
|
306
|
-
compatibility: ability.entry.manifest.package.compatibility,
|
|
307
|
-
};
|
|
308
|
-
const capability = JSON.parse(await readFile(path.join(sourceRoot, "release/catalog-candidates/w3-video-1.0.0/capability.w3.multimodal-video.1.0.0.json"), "utf8"));
|
|
309
|
-
capability.lifecycle = { status: "published" };
|
|
310
|
-
const root = structuredClone(rootManifest);
|
|
311
|
-
root.version = "1.1.14";
|
|
312
|
-
const releaseId = "synthetic-w3-host-fixture-only";
|
|
313
|
-
const snapshot = {
|
|
314
|
-
releaseId,
|
|
315
|
-
catalogVersion: "3.0.0",
|
|
316
|
-
releaseSha256: "d".repeat(64),
|
|
317
|
-
entries: [capability, manifest, root].map((item) => ({
|
|
318
|
-
kind: item.kind,
|
|
319
|
-
id: item.id,
|
|
320
|
-
version: item.version,
|
|
321
|
-
manifestSha256: sha256(canonical(item)),
|
|
322
|
-
manifest: item,
|
|
323
|
-
})),
|
|
324
|
-
};
|
|
325
|
-
const working = await setup();
|
|
326
|
-
const codexHome = path.join(working.directory, ".codex");
|
|
327
|
-
const installedRoot = path.join(codexHome, "skills", "nggaigc");
|
|
328
|
-
const rootFiles = [
|
|
329
|
-
["skill-sources/nggaigc/SKILL.md", "SKILL.md"],
|
|
330
|
-
["skill-sources/nggaigc/agents/openai.yaml", "agents/openai.yaml"],
|
|
331
|
-
[
|
|
332
|
-
"skill-sources/nggaigc/references/catalog-and-updates.md",
|
|
333
|
-
"references/catalog-and-updates.md",
|
|
334
|
-
],
|
|
335
|
-
[
|
|
336
|
-
"skill-sources/nggaigc/references/source.json",
|
|
337
|
-
"references/source.json",
|
|
338
|
-
],
|
|
339
|
-
[
|
|
340
|
-
"skill-sources/nggaigc/scripts/ngg_client.mjs",
|
|
341
|
-
"scripts/ngg_client.mjs",
|
|
342
|
-
],
|
|
343
|
-
[
|
|
344
|
-
"plugins/nggaigc/runtime/bootstrap-public.json",
|
|
345
|
-
"scripts/bootstrap-public.json",
|
|
346
|
-
],
|
|
347
|
-
[
|
|
348
|
-
"plugins/nggaigc/runtime/launch-ngg-client.cjs",
|
|
349
|
-
"scripts/launch-ngg-client.cjs",
|
|
350
|
-
],
|
|
351
|
-
["plugins/nggaigc/runtime/ngg-session.mjs", "scripts/ngg-session.mjs"],
|
|
352
|
-
[
|
|
353
|
-
"plugins/nggaigc/runtime/windows-credential.ps1",
|
|
354
|
-
"scripts/windows-credential.ps1",
|
|
355
|
-
],
|
|
356
|
-
];
|
|
357
|
-
for (const [from, to] of rootFiles) {
|
|
358
|
-
const destination = path.join(installedRoot, to);
|
|
359
|
-
await mkdir(path.dirname(destination), { recursive: true });
|
|
360
|
-
await copyFile(path.join(sourceRoot, from), destination);
|
|
361
|
-
}
|
|
362
|
-
const rootPackageFiles = {};
|
|
363
|
-
for (const [, name] of rootFiles)
|
|
364
|
-
rootPackageFiles[name] = await readFile(path.join(installedRoot, name), "utf8");
|
|
365
|
-
const rootPackage = fixture("skill.nggaigc", "1.1.14", rootPackageFiles);
|
|
366
|
-
root.package = {
|
|
367
|
-
version: root.version,
|
|
368
|
-
artifactPath: "packages/skills/nggaigc-1.1.14.zip",
|
|
369
|
-
sha256: sha256(rootPackage.bytes),
|
|
370
|
-
sizeBytes: rootPackage.bytes.length,
|
|
371
|
-
signature: {
|
|
372
|
-
algorithm: "ed25519",
|
|
373
|
-
keyId: syntheticTrust.keyId,
|
|
374
|
-
value: sign(null, rootPackage.bytes, keypair.privateKey).toString("base64"),
|
|
375
|
-
},
|
|
376
|
-
compatibility: rootPackage.entry.manifest.package.compatibility,
|
|
377
|
-
};
|
|
378
|
-
snapshot.entries[2].manifestSha256 = sha256(canonical(root));
|
|
379
|
-
const requests = [];
|
|
380
|
-
let corruptPackage = false;
|
|
381
|
-
let wrongAdmission = false;
|
|
382
|
-
const server = createServer((request, response) => {
|
|
383
|
-
const url = request.url ?? "";
|
|
384
|
-
requests.push(`${request.method} ${url}`);
|
|
385
|
-
if (url === "/v1/catalog") {
|
|
386
|
-
response.writeHead(200, { "Content-Type": "application/json" });
|
|
387
|
-
response.end(JSON.stringify(snapshot));
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
if (url === "/v1/skill-start/admit") {
|
|
391
|
-
const chunks = [];
|
|
392
|
-
request.on("data", (chunk) => chunks.push(chunk));
|
|
393
|
-
request.on("end", () => {
|
|
394
|
-
const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
395
|
-
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
396
|
-
throw new Error("FIXTURE_REQUEST_INVALID");
|
|
397
|
-
response.writeHead(201, { "Content-Type": "application/json" });
|
|
398
|
-
response.end(JSON.stringify({
|
|
399
|
-
status: "admitted",
|
|
400
|
-
...body,
|
|
401
|
-
releaseId: wrongAdmission ? "wrong-release" : releaseId,
|
|
402
|
-
}));
|
|
403
|
-
});
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
if (url.startsWith("/v1/skill-packages/")) {
|
|
407
|
-
const bytes = corruptPackage ? Buffer.from("tampered") : ability.bytes;
|
|
408
|
-
response.writeHead(200, {
|
|
409
|
-
"Content-Type": "application/zip",
|
|
410
|
-
"Content-Length": String(bytes.length),
|
|
411
|
-
});
|
|
412
|
-
response.end(bytes);
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
response.writeHead(404);
|
|
416
|
-
response.end();
|
|
417
|
-
});
|
|
418
|
-
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
419
|
-
const address = server.address();
|
|
420
|
-
if (!address || typeof address === "string")
|
|
421
|
-
throw new Error("FIXTURE_ADDRESS");
|
|
422
|
-
const localOrigin = `http://127.0.0.1:${address.port}`;
|
|
423
|
-
const launcher = {
|
|
424
|
-
main: async (args) => {
|
|
425
|
-
if (args[0] === "catalog-snapshot")
|
|
426
|
-
return (await (await fetch(`${localOrigin}/v1/catalog`)).json());
|
|
427
|
-
if (args[0] === "skill-admit")
|
|
428
|
-
return (await (await fetch(`${localOrigin}/v1/skill-start/admit`, {
|
|
429
|
-
method: "POST",
|
|
430
|
-
headers: { "Content-Type": "application/json" },
|
|
431
|
-
body: JSON.stringify({
|
|
432
|
-
kind: args[1],
|
|
433
|
-
id: args[2],
|
|
434
|
-
version: args[3],
|
|
435
|
-
manifestSha256: args[4],
|
|
436
|
-
releaseId: args[5],
|
|
437
|
-
}),
|
|
438
|
-
})).json());
|
|
439
|
-
throw new Error("UNSUPPORTED_SYNTHETIC_COMMAND");
|
|
440
|
-
},
|
|
441
|
-
};
|
|
442
|
-
const fetcher = (async (url) => {
|
|
443
|
-
const requested = new URL(String(url));
|
|
444
|
-
const answer = await fetch(`${localOrigin}${requested.pathname}${requested.search}`);
|
|
445
|
-
Object.defineProperty(answer, "url", { value: requested.href });
|
|
446
|
-
return answer;
|
|
447
|
-
});
|
|
448
|
-
const options = {
|
|
449
|
-
codexHome,
|
|
450
|
-
launcher,
|
|
451
|
-
trust: syntheticTrust,
|
|
452
|
-
fetcher,
|
|
453
|
-
rootSkillVersion: "1.1.14",
|
|
454
|
-
platformVersion: "0.1.12",
|
|
455
|
-
};
|
|
456
|
-
try {
|
|
457
|
-
const output = [];
|
|
458
|
-
const errors = [];
|
|
459
|
-
const invoke = (home = codexHome) => main(["start-skill", ability.entry.id, ability.entry.version], {
|
|
460
|
-
startSkill: runSkillStartCommand,
|
|
461
|
-
skillOptions: { ...options, codexHome: home },
|
|
462
|
-
output: (line) => output.push(line),
|
|
463
|
-
error: (line) => errors.push(line),
|
|
464
|
-
});
|
|
465
|
-
expect(await invoke()).toBe(0);
|
|
466
|
-
const ready = JSON.parse(output.at(-1));
|
|
467
|
-
expect(ready).toMatchObject({ status: "ready", reused: false });
|
|
468
|
-
expect(ready.skillPath).toBe(path.join(codexHome, "skills/w3-multimodal-video/SKILL.md"));
|
|
469
|
-
expect(await readFile(ready.skillPath, "utf8")).toBe(files["SKILL.md"]);
|
|
470
|
-
expect(await readFile(path.join(path.dirname(ready.skillPath), "references/workflow.md"), "utf8")).toBe(files["references/workflow.md"]);
|
|
471
|
-
expect(await invoke()).toBe(0);
|
|
472
|
-
expect(JSON.parse(output.at(-1)).reused).toBe(true);
|
|
473
|
-
expect(requests.filter((item) => item.includes("/v1/skill-packages/"))).toHaveLength(1);
|
|
474
|
-
wrongAdmission = true;
|
|
475
|
-
expect(await invoke(path.join(working.directory, "wrong-release-home"))).toBe(1);
|
|
476
|
-
expect(JSON.parse(errors.at(-1)).error.code).toBe("START_ADMISSION_REJECTED");
|
|
477
|
-
wrongAdmission = false;
|
|
478
|
-
corruptPackage = true;
|
|
479
|
-
expect(await invoke(path.join(working.directory, "tampered-home"))).toBe(1);
|
|
480
|
-
expect(JSON.parse(errors.at(-1)).error.code).toBe("PACKAGE_VERIFICATION_FAILED");
|
|
481
|
-
if (process.env.NGG_W3_KEEP_FIXTURE === "1") {
|
|
482
|
-
const rootFileSha256 = Object.fromEntries(await Promise.all(rootFiles.map(async ([, name]) => [
|
|
483
|
-
name,
|
|
484
|
-
sha256(await readFile(path.join(installedRoot, name))),
|
|
485
|
-
])));
|
|
486
|
-
await writeFile(path.join(working.directory, "w3-synthetic.zip"), ability.bytes);
|
|
487
|
-
await writeFile(path.join(working.directory, "root-synthetic.zip"), rootPackage.bytes);
|
|
488
|
-
await writeFile(path.join(working.directory, "catalog-snapshot.json"), JSON.stringify(snapshot, null, 2));
|
|
489
|
-
await writeFile(path.join(working.directory, "evidence.json"), JSON.stringify({
|
|
490
|
-
syntheticOnly: true,
|
|
491
|
-
noRealCredentialsOrPaidRequests: true,
|
|
492
|
-
testTrust: syntheticTrust,
|
|
493
|
-
releaseId,
|
|
494
|
-
packageSha256: sha256(ability.bytes),
|
|
495
|
-
rootPackageSha256: sha256(rootPackage.bytes),
|
|
496
|
-
rootManifestSha256: sha256(canonical(root)),
|
|
497
|
-
skillManifestSha256: sha256(canonical(manifest)),
|
|
498
|
-
capabilityManifestSha256: sha256(canonical(capability)),
|
|
499
|
-
files: Object.fromEntries(Object.entries(files).map(([name, content]) => [
|
|
500
|
-
name,
|
|
501
|
-
sha256(content),
|
|
502
|
-
])),
|
|
503
|
-
rootFileSha256,
|
|
504
|
-
ready,
|
|
505
|
-
reused: true,
|
|
506
|
-
wrongReleaseRejected: true,
|
|
507
|
-
tamperedPackageRejected: true,
|
|
508
|
-
loopbackRequests: requests,
|
|
509
|
-
}, null, 2));
|
|
510
|
-
workdirs.splice(workdirs.indexOf(working.directory), 1);
|
|
511
|
-
console.log(`W3_SYNTHETIC_FIXTURE=${working.directory}`);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
finally {
|
|
515
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
516
|
-
}
|
|
517
|
-
});
|
|
518
|
-
it("discovers and installs a new local Skill without changing root bytes, then reuses the signed cache", async () => {
|
|
519
|
-
const rootDigest = await managedRootDigest();
|
|
520
|
-
const ability = fixture("skill.local-marker");
|
|
521
|
-
const test = await setup(ability);
|
|
522
|
-
const first = await test.prepare(ability.entry.id);
|
|
523
|
-
expect(first.reused).toBe(false);
|
|
524
|
-
expect(await readFile(first.skillPath, "utf8")).toContain("local test marker");
|
|
525
|
-
expect((await test.prepare(ability.entry.id)).reused).toBe(true);
|
|
526
|
-
expect(test.calls).toBe(1);
|
|
527
|
-
const laterAbility = fixture("skill.later-published");
|
|
528
|
-
const laterCatalog = await setup(laterAbility);
|
|
529
|
-
const added = await laterCatalog.prepare(laterAbility.entry.id);
|
|
530
|
-
expect(await readFile(added.skillPath, "utf8")).toContain("local test marker");
|
|
531
|
-
expect(await managedRootDigest()).toBe(rootDigest);
|
|
532
|
-
});
|
|
533
|
-
it("installs required dependencies in order and fails closed for missing or cyclic requirements", async () => {
|
|
534
|
-
const dependency = fixture("skill.local-dependency");
|
|
535
|
-
const ability = fixture("skill.local-marker", "1.0.0", undefined, [
|
|
536
|
-
{ id: dependency.entry.id, version: dependency.entry.version },
|
|
537
|
-
]);
|
|
538
|
-
const test = await setup(ability, dependency);
|
|
539
|
-
await test.prepare(ability.entry.id);
|
|
540
|
-
expect(test.calls).toBe(2);
|
|
541
|
-
expect(await readFile(path.join(test.directory, ".codex/skills/local-dependency/SKILL.md"), "utf8")).toContain("local test marker");
|
|
542
|
-
const missing = await setup(ability);
|
|
543
|
-
await expect(missing.prepare(ability.entry.id)).rejects.toMatchObject({
|
|
544
|
-
code: "DEPENDENCY_UNAVAILABLE",
|
|
545
|
-
});
|
|
546
|
-
expect(missing.calls).toBe(0);
|
|
547
|
-
const cycleA = fixture("skill.local-a", "1.0.0", undefined, [
|
|
548
|
-
{ id: "skill.local-b", version: "1.0.0" },
|
|
549
|
-
]);
|
|
550
|
-
const cycleB = fixture("skill.local-b", "1.0.0", undefined, [
|
|
551
|
-
{ id: "skill.local-a", version: "1.0.0" },
|
|
552
|
-
]);
|
|
553
|
-
const cycle = await setup(cycleA, cycleB);
|
|
554
|
-
await expect(cycle.prepare(cycleA.entry.id)).rejects.toMatchObject({
|
|
555
|
-
code: "DEPENDENCY_CYCLE",
|
|
556
|
-
});
|
|
557
|
-
expect(cycle.calls).toBe(0);
|
|
558
|
-
});
|
|
559
|
-
it("does not hand off the target when a later required package fails, while retaining an earlier verified shared dependency", async () => {
|
|
560
|
-
const first = fixture("skill.required-first");
|
|
561
|
-
const later = fixture("skill.required-later");
|
|
562
|
-
const target = fixture("skill.target", "1.0.0", undefined, [
|
|
563
|
-
{ id: first.entry.id, version: first.entry.version },
|
|
564
|
-
{ id: later.entry.id, version: later.entry.version },
|
|
565
|
-
]);
|
|
566
|
-
const test = await setup(first, later, target);
|
|
567
|
-
const fetcher = ((url, init) => new URL(String(url)).pathname.endsWith("/skill.required-later/1.0.0")
|
|
568
|
-
? Promise.reject(new Error("injected later dependency interruption"))
|
|
569
|
-
: test.fetcher(url, init));
|
|
570
|
-
await expect(startIndependentSkill({
|
|
571
|
-
index: test.index,
|
|
572
|
-
skillId: target.entry.id,
|
|
573
|
-
skillVersion: target.entry.version,
|
|
574
|
-
rootSkillVersion: "1.1.14",
|
|
575
|
-
platformVersion: "0.1.12",
|
|
576
|
-
codexHome: path.join(test.directory, ".codex"),
|
|
577
|
-
trust,
|
|
578
|
-
fetcher,
|
|
579
|
-
admit: (identity) => Promise.resolve({ ...identity, status: "admitted" }),
|
|
580
|
-
})).rejects.toMatchObject({ code: "PACKAGE_DOWNLOAD_FAILED" });
|
|
581
|
-
expect(await readFile(path.join(test.directory, ".codex/skills/required-first/SKILL.md"), "utf8")).toContain("local test marker");
|
|
582
|
-
expect(await readdir(path.join(test.directory, ".codex/skills"))).toEqual([
|
|
583
|
-
"required-first",
|
|
584
|
-
]);
|
|
585
|
-
});
|
|
586
|
-
it("rejects tampered bytes, wrong trust key, wrong identity and unsafe paths before installation", async () => {
|
|
587
|
-
const ability = fixture("skill.local-marker");
|
|
588
|
-
const test = await setup(ability);
|
|
589
|
-
const wrongKey = generateKeyPairSync("ed25519")
|
|
590
|
-
.publicKey.export({ type: "spki", format: "pem" })
|
|
591
|
-
.toString();
|
|
592
|
-
await expect(test.prepare(ability.entry.id, "1.0.0", {
|
|
593
|
-
trust: { ...trust, publicKeyPem: wrongKey },
|
|
594
|
-
})).rejects.toMatchObject({ code: "PACKAGE_VERIFICATION_FAILED" });
|
|
595
|
-
await expect(test.prepare(ability.entry.id, "1.0.0", {
|
|
596
|
-
fetcher: ((url) => {
|
|
597
|
-
const response = new Response(Buffer.from("tampered"));
|
|
598
|
-
Object.defineProperty(response, "url", { value: String(url) });
|
|
599
|
-
return Promise.resolve(response);
|
|
600
|
-
}),
|
|
601
|
-
})).rejects.toMatchObject({ code: "PACKAGE_VERIFICATION_FAILED" });
|
|
602
|
-
const wrongIdentity = fixture("skill.other-marker");
|
|
603
|
-
await expect(test.prepare(ability.entry.id, "1.0.0", {
|
|
604
|
-
fetcher: ((url) => {
|
|
605
|
-
const response = new Response(Uint8Array.from(wrongIdentity.bytes));
|
|
606
|
-
Object.defineProperty(response, "url", { value: String(url) });
|
|
607
|
-
return Promise.resolve(response);
|
|
608
|
-
}),
|
|
609
|
-
})).rejects.toMatchObject({ code: "PACKAGE_VERIFICATION_FAILED" });
|
|
610
|
-
const unsafe = fixture("skill.unsafe-marker", "1.0.0", {
|
|
611
|
-
"SKILL.md": "safe",
|
|
612
|
-
"references/../escape.md": "bad",
|
|
613
|
-
});
|
|
614
|
-
const unsafeTest = await setup(unsafe);
|
|
615
|
-
await expect(unsafeTest.prepare(unsafe.entry.id)).rejects.toMatchObject({
|
|
616
|
-
code: "PACKAGE_VERIFICATION_FAILED",
|
|
617
|
-
});
|
|
618
|
-
});
|
|
619
|
-
it("keeps the existing version on download failure or invalid update and rejects a foreign target", async () => {
|
|
620
|
-
const old = fixture("skill.local-marker", "1.0.0");
|
|
621
|
-
const next = fixture("skill.local-marker", "1.1.0", {
|
|
622
|
-
"SKILL.md": "new version",
|
|
623
|
-
});
|
|
624
|
-
const other = fixture("skill.other-marker");
|
|
625
|
-
const test = await setup(old, next, other);
|
|
626
|
-
const first = await test.prepare(old.entry.id);
|
|
627
|
-
const otherResult = await test.prepare(other.entry.id);
|
|
628
|
-
const userFile = path.join(test.directory, "user-work.txt");
|
|
629
|
-
const credentialSentinel = path.join(test.directory, "credential-sentinel.bin");
|
|
630
|
-
await writeFile(userFile, "independent user content");
|
|
631
|
-
await writeFile(credentialSentinel, randomBytes(32));
|
|
632
|
-
const protectedPaths = [
|
|
633
|
-
otherResult.skillPath,
|
|
634
|
-
userFile,
|
|
635
|
-
credentialSentinel,
|
|
636
|
-
];
|
|
637
|
-
const protectedDigests = await Promise.all(protectedPaths.map(async (file) => sha256(await readFile(file))));
|
|
638
|
-
const expectProtected = async () => expect(await Promise.all(protectedPaths.map(async (file) => sha256(await readFile(file))))).toEqual(protectedDigests);
|
|
639
|
-
await expect(test.prepare(next.entry.id, next.entry.version, {
|
|
640
|
-
fetcher: () => Promise.reject(new Error("offline")),
|
|
641
|
-
})).rejects.toMatchObject({ code: "PACKAGE_DOWNLOAD_FAILED" });
|
|
642
|
-
expect(await readFile(first.skillPath, "utf8")).toContain("local test marker");
|
|
643
|
-
await expectProtected();
|
|
644
|
-
await expect(test.prepare(next.entry.id, next.entry.version, {
|
|
645
|
-
fetcher: ((url) => {
|
|
646
|
-
const response = new Response(Buffer.from("bad"));
|
|
647
|
-
Object.defineProperty(response, "url", { value: String(url) });
|
|
648
|
-
return Promise.resolve(response);
|
|
649
|
-
}),
|
|
650
|
-
})).rejects.toMatchObject({ code: "PACKAGE_VERIFICATION_FAILED" });
|
|
651
|
-
expect(await readFile(first.skillPath, "utf8")).toContain("local test marker");
|
|
652
|
-
await expectProtected();
|
|
653
|
-
await expect(test.prepare(next.entry.id, next.entry.version, {
|
|
654
|
-
beforeActivate: () => Promise.reject(new Error("injected activation interruption")),
|
|
655
|
-
})).rejects.toMatchObject({ code: "INSTALL_FAILED" });
|
|
656
|
-
expect(await readFile(first.skillPath, "utf8")).toContain("local test marker");
|
|
657
|
-
await expectProtected();
|
|
658
|
-
await expect(test.prepare(next.entry.id, next.entry.version, {
|
|
659
|
-
afterActivate: async (target) => {
|
|
660
|
-
await writeFile(path.join(target, "SKILL.md"), "tampered after rename");
|
|
661
|
-
},
|
|
662
|
-
})).rejects.toMatchObject({ code: "INSTALL_FAILED" });
|
|
663
|
-
expect(await readFile(first.skillPath, "utf8")).toContain("local test marker");
|
|
664
|
-
await expectProtected();
|
|
665
|
-
const second = await test.prepare(next.entry.id, next.entry.version);
|
|
666
|
-
expect(await readFile(second.skillPath, "utf8")).toBe("new version");
|
|
667
|
-
await expectProtected();
|
|
668
|
-
const foreign = await setup(fixture("skill.foreign"));
|
|
669
|
-
const target = path.join(foreign.directory, ".codex/skills/foreign");
|
|
670
|
-
await writeFile(path.join(foreign.directory, "foreign.txt"), "untouched");
|
|
671
|
-
await expect(foreign.prepare("skill.foreign", "1.0.0", {
|
|
672
|
-
origin: "https://example.com/",
|
|
673
|
-
})).rejects.toMatchObject({ code: "PACKAGE_SOURCE_REJECTED" });
|
|
674
|
-
expect(await readFile(path.join(foreign.directory, "foreign.txt"), "utf8")).toBe("untouched");
|
|
675
|
-
expect(target).toContain("foreign");
|
|
676
|
-
});
|
|
677
|
-
it("keeps backup and staging state outside discoverable skills and rejects tampered crash recovery", async () => {
|
|
678
|
-
const old = fixture("skill.local-marker", "1.0.0");
|
|
679
|
-
const next = fixture("skill.local-marker", "1.1.0", {
|
|
680
|
-
"SKILL.md": "new version",
|
|
681
|
-
});
|
|
682
|
-
const test = await setup(old, next);
|
|
683
|
-
await test.prepare(old.entry.id);
|
|
684
|
-
const codexHome = path.join(test.directory, ".codex");
|
|
685
|
-
const target = path.join(codexHome, "skills/local-marker");
|
|
686
|
-
const backup = path.join(codexHome, ".nggaigc-managed/.nggaigc-backup-local-marker");
|
|
687
|
-
await rename(target, backup);
|
|
688
|
-
await writeFile(path.join(backup, "SKILL.md"), "tampered");
|
|
689
|
-
await expect(test.prepare(next.entry.id, next.entry.version)).rejects.toMatchObject({
|
|
690
|
-
code: "ROLLBACK_PENDING",
|
|
691
|
-
});
|
|
692
|
-
expect(test.calls).toBe(1);
|
|
693
|
-
expect(await readdir(path.join(codexHome, "skills"))).toEqual([]);
|
|
694
|
-
const clean = await setup(old, next);
|
|
695
|
-
await clean.prepare(old.entry.id);
|
|
696
|
-
const cleanHome = path.join(clean.directory, ".codex");
|
|
697
|
-
await rename(path.join(cleanHome, "skills/local-marker"), path.join(cleanHome, ".nggaigc-managed/.nggaigc-backup-local-marker"));
|
|
698
|
-
const recovered = await clean.prepare(next.entry.id, next.entry.version);
|
|
699
|
-
expect(await readFile(recovered.skillPath, "utf8")).toBe("new version");
|
|
700
|
-
expect(await readdir(path.join(cleanHome, "skills"))).toEqual([
|
|
701
|
-
"local-marker",
|
|
702
|
-
]);
|
|
703
|
-
expect(await readFile(path.join(cleanHome, ".nggaigc-managed/.nggaigc-backup-local-marker/SKILL.md"), "utf8")).toContain("local test marker");
|
|
704
|
-
});
|
|
705
|
-
it("rolls back only the target Skill to a still active signed version", async () => {
|
|
706
|
-
const old = fixture("skill.local-marker", "1.0.0");
|
|
707
|
-
const next = fixture("skill.local-marker", "1.1.0", {
|
|
708
|
-
"SKILL.md": "new version",
|
|
709
|
-
});
|
|
710
|
-
const other = fixture("skill.other-marker");
|
|
711
|
-
const test = await setup(old, next, other);
|
|
712
|
-
await test.prepare(old.entry.id);
|
|
713
|
-
const otherResult = await test.prepare(other.entry.id);
|
|
714
|
-
const userFile = path.join(test.directory, "user-owned.txt");
|
|
715
|
-
const credentialSentinel = path.join(test.directory, "isolated-credential.bin");
|
|
716
|
-
await writeFile(userFile, "do not modify");
|
|
717
|
-
await writeFile(credentialSentinel, randomBytes(32));
|
|
718
|
-
const protectedPaths = [
|
|
719
|
-
otherResult.skillPath,
|
|
720
|
-
userFile,
|
|
721
|
-
credentialSentinel,
|
|
722
|
-
];
|
|
723
|
-
const before = await Promise.all(protectedPaths.map(async (file) => sha256(await readFile(file))));
|
|
724
|
-
await test.prepare(next.entry.id, next.entry.version);
|
|
725
|
-
const restored = await rollbackIndependentSkill({
|
|
726
|
-
index: test.index,
|
|
727
|
-
skillId: old.entry.id,
|
|
728
|
-
skillVersion: old.entry.version,
|
|
729
|
-
installedVersion: next.entry.version,
|
|
730
|
-
rootSkillVersion: "1.1.14",
|
|
731
|
-
platformVersion: "0.1.12",
|
|
732
|
-
codexHome: path.join(test.directory, ".codex"),
|
|
733
|
-
trust,
|
|
734
|
-
});
|
|
735
|
-
expect(await readFile(restored.skillPath, "utf8")).toContain("local test marker");
|
|
736
|
-
expect(await readFile(otherResult.skillPath, "utf8")).toContain("local test marker");
|
|
737
|
-
expect(await Promise.all(protectedPaths.map(async (file) => sha256(await readFile(file))))).toEqual(before);
|
|
738
|
-
const disabledIndex = {
|
|
739
|
-
...test.index,
|
|
740
|
-
entries: test.index.entries.map((entry) => entry.id === old.entry.id && entry.version === old.entry.version
|
|
741
|
-
? { ...entry, state: "disabled" }
|
|
742
|
-
: entry),
|
|
743
|
-
};
|
|
744
|
-
await expect(rollbackIndependentSkill({
|
|
745
|
-
index: disabledIndex,
|
|
746
|
-
skillId: old.entry.id,
|
|
747
|
-
skillVersion: old.entry.version,
|
|
748
|
-
installedVersion: next.entry.version,
|
|
749
|
-
rootSkillVersion: "1.1.14",
|
|
750
|
-
platformVersion: "0.1.12",
|
|
751
|
-
codexHome: path.join(test.directory, ".codex"),
|
|
752
|
-
trust,
|
|
753
|
-
})).rejects.toMatchObject({ code: "CATALOG_INVALID" });
|
|
754
|
-
});
|
|
755
|
-
it.skipIf(process.platform !== "win32")("keeps A, B, user work and the exact isolated credential unchanged across interrupted A update and A rollback", async () => {
|
|
756
|
-
const old = fixture("skill.local-a", "1.0.0");
|
|
757
|
-
const next = fixture("skill.local-a", "1.1.0", {
|
|
758
|
-
"SKILL.md": "updated A",
|
|
759
|
-
});
|
|
760
|
-
const other = fixture("skill.local-b");
|
|
761
|
-
const test = await setup(old, next, other);
|
|
762
|
-
const oldA = await test.prepare(old.entry.id);
|
|
763
|
-
const installedB = await test.prepare(other.entry.id);
|
|
764
|
-
const skillA = path.dirname(oldA.skillPath);
|
|
765
|
-
const skillB = path.dirname(installedB.skillPath);
|
|
766
|
-
const digestDirectory = async (directory) => {
|
|
767
|
-
const parts = [];
|
|
768
|
-
for (const name of (await readdir(directory)).sort())
|
|
769
|
-
parts.push(Buffer.from(name), await readFile(path.join(directory, name)));
|
|
770
|
-
return sha256(Buffer.concat(parts));
|
|
771
|
-
};
|
|
772
|
-
const userFile = path.join(test.directory, "user-owned.txt");
|
|
773
|
-
await writeFile(userFile, "user work must remain exact");
|
|
774
|
-
const userHash = sha256(await readFile(userFile));
|
|
775
|
-
const oldAHash = await digestDirectory(skillA);
|
|
776
|
-
const bHash = await digestDirectory(skillB);
|
|
777
|
-
const distribution = path.join(test.directory, "isolated-bridge");
|
|
778
|
-
await mkdir(distribution);
|
|
779
|
-
const workspace = path.resolve(import.meta.dirname, "../../..");
|
|
780
|
-
for (const [source, name] of [
|
|
781
|
-
[
|
|
782
|
-
"plugins/nggaigc/runtime/windows-credential.ps1",
|
|
783
|
-
"windows-credential.ps1",
|
|
784
|
-
],
|
|
785
|
-
["plugins/nggaigc/runtime/ngg-session.mjs", "ngg-session.mjs"],
|
|
786
|
-
[
|
|
787
|
-
"plugins/nggaigc/runtime/bootstrap-public.json",
|
|
788
|
-
"bootstrap-public.json",
|
|
789
|
-
],
|
|
790
|
-
["skill-sources/nggaigc/scripts/ngg_client.mjs", "ngg_client.mjs"],
|
|
791
|
-
])
|
|
792
|
-
await copyFile(path.join(workspace, source), path.join(distribution, name));
|
|
793
|
-
const target = `NGGAIGC/Codex/test-${randomBytes(16).toString("hex")}`;
|
|
794
|
-
const token = `ngg_v1_${randomBytes(32).toString("base64url")}`;
|
|
795
|
-
let epoch;
|
|
796
|
-
try {
|
|
797
|
-
const setup = await testCredentialOperation(distribution, target, "setup", credentialFrame({
|
|
798
|
-
schemaVersion: "ngg-credential-input/v1",
|
|
799
|
-
origin: "https://api.nggaigc.com",
|
|
800
|
-
token,
|
|
801
|
-
}));
|
|
802
|
-
expect(setup.code).toBe(0);
|
|
803
|
-
epoch = setup.value.credentialEpoch;
|
|
804
|
-
expect(epoch).toMatch(/^[a-f0-9]{32}$/);
|
|
805
|
-
const status = await testCredentialOperation(distribution, target, "status");
|
|
806
|
-
expect(status.code).toBe(0);
|
|
807
|
-
expect(status.value.credentialEpoch).toBe(epoch);
|
|
808
|
-
const protectedState = async () => {
|
|
809
|
-
expect(await digestDirectory(skillB)).toBe(bHash);
|
|
810
|
-
expect(sha256(await readFile(userFile))).toBe(userHash);
|
|
811
|
-
const current = await testCredentialOperation(distribution, target, "status");
|
|
812
|
-
expect(current).toEqual(status);
|
|
813
|
-
};
|
|
814
|
-
await expect(test.prepare(next.entry.id, next.entry.version, {
|
|
815
|
-
beforeActivate: () => Promise.reject(new Error("injected interruption after A backup")),
|
|
816
|
-
})).rejects.toMatchObject({ code: "INSTALL_FAILED" });
|
|
817
|
-
expect(await digestDirectory(skillA)).toBe(oldAHash);
|
|
818
|
-
await protectedState();
|
|
819
|
-
const updated = await test.prepare(next.entry.id, next.entry.version);
|
|
820
|
-
expect(await readFile(updated.skillPath, "utf8")).toBe("updated A");
|
|
821
|
-
await protectedState();
|
|
822
|
-
const restored = await rollbackIndependentSkill({
|
|
823
|
-
index: test.index,
|
|
824
|
-
skillId: old.entry.id,
|
|
825
|
-
skillVersion: old.entry.version,
|
|
826
|
-
installedVersion: next.entry.version,
|
|
827
|
-
rootSkillVersion: "1.1.14",
|
|
828
|
-
platformVersion: "0.1.12",
|
|
829
|
-
codexHome: path.join(test.directory, ".codex"),
|
|
830
|
-
trust,
|
|
831
|
-
});
|
|
832
|
-
expect(restored.skillPath).toBe(oldA.skillPath);
|
|
833
|
-
expect(await digestDirectory(skillA)).toBe(oldAHash);
|
|
834
|
-
await protectedState();
|
|
835
|
-
}
|
|
836
|
-
finally {
|
|
837
|
-
if (epoch) {
|
|
838
|
-
const deleted = await testCredentialOperation(distribution, target, "delete-exact", undefined, epoch);
|
|
839
|
-
expect(deleted.code).toBe(0);
|
|
840
|
-
expect(deleted.value.status).toBe("credential-deleted");
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
});
|
|
844
|
-
it("rejects a catalog manifest hash mismatch and incompatible root", async () => {
|
|
845
|
-
const ability = fixture("skill.local-marker");
|
|
846
|
-
const test = await setup(ability);
|
|
847
|
-
const wrongIndex = {
|
|
848
|
-
...test.index,
|
|
849
|
-
entries: [{ ...ability.entry, manifestSha256: "0".repeat(64) }],
|
|
850
|
-
};
|
|
851
|
-
await expect(test.prepare(ability.entry.id, "1.0.0", { index: wrongIndex })).rejects.toMatchObject({ code: "CATALOG_INVALID" });
|
|
852
|
-
await expect(test.prepare(ability.entry.id, "1.0.0", { rootSkillVersion: "2.0.0" })).rejects.toMatchObject({ code: "PACKAGE_INCOMPATIBLE" });
|
|
853
|
-
expect(test.calls).toBe(0);
|
|
854
|
-
});
|
|
855
|
-
it("rejects duplicate identity, incompatible platform and tampered signed cache", async () => {
|
|
856
|
-
const ability = fixture("skill.local-marker");
|
|
857
|
-
const duplicate = await setup(ability, ability);
|
|
858
|
-
await expect(duplicate.prepare(ability.entry.id)).rejects.toMatchObject({
|
|
859
|
-
code: "DEPENDENCY_UNAVAILABLE",
|
|
860
|
-
});
|
|
861
|
-
const test = await setup(ability);
|
|
862
|
-
await expect(test.prepare(ability.entry.id, "1.0.0", { platformVersion: "2.0.0" })).rejects.toMatchObject({ code: "PACKAGE_INCOMPATIBLE" });
|
|
863
|
-
const result = await test.prepare(ability.entry.id);
|
|
864
|
-
await writeFile(result.skillPath, "tampered");
|
|
865
|
-
await expect(test.prepare(ability.entry.id)).rejects.toMatchObject({
|
|
866
|
-
code: "INSTALL_CONFLICT",
|
|
867
|
-
});
|
|
868
|
-
});
|
|
869
|
-
it("does not follow redirects, send credentials or write through a foreign symlink", async () => {
|
|
870
|
-
const ability = fixture("skill.local-marker");
|
|
871
|
-
const test = await setup(ability);
|
|
872
|
-
const seen = [];
|
|
873
|
-
await expect(test.prepare(ability.entry.id, "1.0.0", {
|
|
874
|
-
fetcher: ((url, init) => {
|
|
875
|
-
seen.push({ url: String(url), init });
|
|
876
|
-
return Promise.resolve(new Response(null, {
|
|
877
|
-
status: 302,
|
|
878
|
-
headers: { location: "https://example.com/evil" },
|
|
879
|
-
}));
|
|
880
|
-
}),
|
|
881
|
-
})).rejects.toMatchObject({ code: "PACKAGE_DOWNLOAD_FAILED" });
|
|
882
|
-
expect(seen).toHaveLength(1);
|
|
883
|
-
expect(seen[0]?.url).toBe("https://api.nggaigc.com/v1/skill-packages/skill.local-marker/1.0.0?releaseId=release.test.1");
|
|
884
|
-
expect(seen[0]?.init).toMatchObject({
|
|
885
|
-
redirect: "error",
|
|
886
|
-
credentials: "omit",
|
|
887
|
-
cache: "no-store",
|
|
888
|
-
});
|
|
889
|
-
expect(JSON.stringify(seen[0]?.init)).not.toContain("Authorization");
|
|
890
|
-
const outside = path.join(test.directory, "outside");
|
|
891
|
-
await mkdir(outside);
|
|
892
|
-
const target = path.join(test.directory, ".codex", "skills", "local-marker");
|
|
893
|
-
await symlink(outside, target, "junction");
|
|
894
|
-
await expect(test.prepare(ability.entry.id)).rejects.toMatchObject({
|
|
895
|
-
code: "INSTALL_CONFLICT",
|
|
896
|
-
});
|
|
897
|
-
expect(await readFile(path.join(outside, "SKILL.md"), "utf8").catch(() => "missing")).toBe("missing");
|
|
898
|
-
});
|
|
899
|
-
it("projects the existing active catalog shape instead of introducing a second release source", async () => {
|
|
900
|
-
const folder = path.resolve(import.meta.dirname, "../../../release/catalog-bundles/seedream-5-lite-1.1.0/manifests");
|
|
901
|
-
const manifest = JSON.parse(await readFile(path.join(folder, "skill.seedream-5-lite.image-generation.1.0.0.json"), "utf8"));
|
|
902
|
-
const capability = JSON.parse(await readFile(path.join(folder, "capability.seedream-5-lite.image-generation.1.0.0.json"), "utf8"));
|
|
903
|
-
const index = projectActiveSkillCatalog({
|
|
904
|
-
releaseId: "release.existing",
|
|
905
|
-
entries: [
|
|
906
|
-
{
|
|
907
|
-
kind: "skill",
|
|
908
|
-
id: "skill.seedream-5-lite.image-generation",
|
|
909
|
-
version: "1.0.0",
|
|
910
|
-
manifestSha256: sha256(canonical(manifest)),
|
|
911
|
-
manifest,
|
|
912
|
-
},
|
|
913
|
-
{
|
|
914
|
-
kind: "capability",
|
|
915
|
-
id: "capability.seedream-5-lite.image-generation",
|
|
916
|
-
version: "1.0.0",
|
|
917
|
-
manifestSha256: sha256(canonical(capability)),
|
|
918
|
-
manifest: capability,
|
|
919
|
-
},
|
|
920
|
-
],
|
|
921
|
-
});
|
|
922
|
-
expect(index.entries).toHaveLength(1);
|
|
923
|
-
expect(index.entries[0]?.id).toBe("skill.seedream-5-lite.image-generation");
|
|
924
|
-
expect(index.entries[0]?.manifestSha256).toBe(sha256(canonical(manifest)));
|
|
925
|
-
});
|
|
926
|
-
it("binds server admission to the exact release before free local preparation", async () => {
|
|
927
|
-
const ability = fixture("skill.local-marker");
|
|
928
|
-
const test = await setup(ability);
|
|
929
|
-
const base = {
|
|
930
|
-
index: test.index,
|
|
931
|
-
skillId: ability.entry.id,
|
|
932
|
-
skillVersion: ability.entry.version,
|
|
933
|
-
rootSkillVersion: "1.1.14",
|
|
934
|
-
platformVersion: "0.1.12",
|
|
935
|
-
codexHome: path.join(test.directory, ".codex"),
|
|
936
|
-
trust,
|
|
937
|
-
};
|
|
938
|
-
await expect(startIndependentSkill({
|
|
939
|
-
...base,
|
|
940
|
-
admit: () => Promise.reject(Object.assign(new Error("insufficient"), {
|
|
941
|
-
code: "INSUFFICIENT_CREDITS",
|
|
942
|
-
})),
|
|
943
|
-
})).rejects.toMatchObject({ code: "INSUFFICIENT_CREDITS" });
|
|
944
|
-
expect(test.calls).toBe(0);
|
|
945
|
-
await expect(startIndependentSkill({
|
|
946
|
-
...base,
|
|
947
|
-
admit: (identity) => Promise.resolve({
|
|
948
|
-
...identity,
|
|
949
|
-
releaseId: "wrong",
|
|
950
|
-
status: "admitted",
|
|
951
|
-
}),
|
|
952
|
-
})).rejects.toMatchObject({ code: "START_ADMISSION_REJECTED" });
|
|
953
|
-
expect(test.calls).toBe(0);
|
|
954
|
-
const result = await startIndependentSkill({
|
|
955
|
-
...base,
|
|
956
|
-
fetcher: ((url) => {
|
|
957
|
-
const response = new Response(Uint8Array.from(ability.bytes));
|
|
958
|
-
Object.defineProperty(response, "url", { value: String(url) });
|
|
959
|
-
return Promise.resolve(response);
|
|
960
|
-
}),
|
|
961
|
-
admit: (identity) => Promise.resolve({ ...identity, status: "admitted" }),
|
|
962
|
-
});
|
|
963
|
-
expect(await readFile(result.skillPath, "utf8")).toContain("local test marker");
|
|
964
|
-
});
|
|
965
|
-
});
|