gentle-pi 2.3.0 → 2.4.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 +30 -4
- package/assets/agents/sdd-explore.md +1 -0
- package/assets/orchestrator-delegation.md +2 -1
- package/assets/orchestrator.md +6 -10
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
- package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/README.md +10 -0
- package/contracts/review-provider-contract-mirror/v1.2.0/bundle/manifest.json +74 -0
- package/contracts/review-provider-contract-mirror/v1.2.0/bundle/orchestration/pi.md +53 -0
- package/contracts/review-provider-contract-mirror/v1.2.0/bundle/schemas/targeted-validator.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-capabilities.baseline.json +9 -2
- package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-roles.baseline.json +2 -2
- package/docs/review-integration.md +1 -1
- package/extensions/gentle-ai.ts +530 -71
- package/lib/agent-home.ts +8 -0
- package/lib/native-review-cli.ts +15 -0
- package/lib/provider-contract-bundle.ts +88 -6
- package/lib/review-host-relay.ts +146 -60
- package/lib/review-integration-v2.ts +34 -5
- package/lib/sdd-preflight.ts +2 -2
- package/package.json +1 -1
- package/runtime/native-review-cli.mjs +15 -0
- package/runtime/review-integration-v2.mjs +34 -5
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/verify-package-files.mjs +14 -13
- package/skills/_shared/review-ledger-contract.md +6 -0
- package/skills/issue-creation/SKILL.md +53 -93
- package/tests/fixtures/provider-contract-bundle/v1.2.0/README.md +22 -0
- package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/manifest.json +11 -2
- package/tests/fixtures/provider-contract-bundle/v1.2.0/orchestration/pi.md +97 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/targeted-validator.json +1 -0
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-installer.test.ts +46 -46
- package/tests/gentle-ai.test.ts +3 -2
- package/tests/issue-creation-skill.test.ts +103 -0
- package/tests/native-review-capability-contract.test.ts +18 -1
- package/tests/orchestrator-budget.test.ts +2 -2
- package/tests/orchestrator-rdd-ownership.test.ts +1 -1
- package/tests/package-manifest.test.ts +123 -9
- package/tests/provider-contract-bundle.test.ts +76 -0
- package/tests/provider-contract-mirror.test.ts +19 -0
- package/tests/review-agent-end-preflight.test.ts +408 -0
- package/tests/review-contract-prompt.test.ts +139 -0
- package/tests/review-controller.test.ts +1 -1
- package/tests/review-host-relay-routing.test.ts +211 -9
- package/tests/review-host-relay.test.ts +195 -7
- package/tests/review-integration-v2-forward.test.ts +47 -0
- package/tests/review-ledger-contract.test.ts +1 -1
- package/tests/runtime-harness.mjs +1 -0
- package/tests/writer-edit-surface-scope.test.ts +153 -17
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/targeted-validator.json +0 -0
- /package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/schemas/targeted-validator.schema.json +0 -0
|
@@ -9,11 +9,12 @@ import {
|
|
|
9
9
|
rmSync,
|
|
10
10
|
writeFileSync,
|
|
11
11
|
} from "node:fs";
|
|
12
|
-
import { tmpdir } from "node:os";
|
|
12
|
+
import { homedir, tmpdir } from "node:os";
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
14
|
import test from "node:test";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
import { applyModelConfig } from "../extensions/gentle-ai.ts";
|
|
17
|
+
import { resolveGentlePiAgentHome } from "../lib/agent-home.ts";
|
|
17
18
|
import { installSddAssets } from "../lib/sdd-preflight.ts";
|
|
18
19
|
|
|
19
20
|
const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
@@ -261,20 +262,20 @@ test("package manifest installs pi-pretty through a wrapper without bundling nat
|
|
|
261
262
|
);
|
|
262
263
|
});
|
|
263
264
|
|
|
264
|
-
test("package verification binds the published Gentle AI v2.
|
|
265
|
+
test("package verification binds the published Gentle AI v2.6.0 runtime pin", () => {
|
|
265
266
|
const installer = readFileSync(join(PACKAGE_ROOT, "scripts", "gentle-ai-installer.mjs"), "utf8");
|
|
266
267
|
const binary = readFileSync(join(PACKAGE_ROOT, "lib", "gentle-ai-binary.ts"), "utf8");
|
|
267
268
|
const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8");
|
|
268
269
|
|
|
269
|
-
assert.match(installer, /INSTALLER_VERSION = "2\.
|
|
270
|
+
assert.match(installer, /INSTALLER_VERSION = "2\.6\.0"/);
|
|
270
271
|
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_PACKAGE.*GENTLE_AI_WINDOWS_SOURCE_MODULE/);
|
|
271
|
-
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:
|
|
272
|
+
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:scGoZYnPHh4oCVqlcfQAOSpm22Ii5lmB0kBbiBKvNZk="/);
|
|
272
273
|
assert.match(installer, /GOTOOLCHAIN: "local"/);
|
|
273
274
|
assert.match(installer, /GOSUMDB: "sum\.golang\.org"/);
|
|
274
275
|
assert.match(binary, /GENTLE_AI_VERSION = INSTALLER_VERSION/);
|
|
275
276
|
assert.match(binary, /GO_SUMDB_SOURCE_BUILD/);
|
|
276
277
|
assert.match(binary, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM/);
|
|
277
|
-
assert.match(verifier, /v2\.
|
|
278
|
+
assert.match(verifier, /v2\.6\.0/);
|
|
278
279
|
});
|
|
279
280
|
|
|
280
281
|
|
|
@@ -895,6 +896,22 @@ test("jd-fix-agent packaged allowlist includes write tools", () => {
|
|
|
895
896
|
}
|
|
896
897
|
});
|
|
897
898
|
|
|
899
|
+
test("sdd-explore packages its CodeGraph-enabled exploration allowlist", () => {
|
|
900
|
+
const agentPath = join(PACKAGE_ROOT, "assets", "agents", "sdd-explore.md");
|
|
901
|
+
const { name, tools } = readAgentDefinition(agentPath);
|
|
902
|
+
|
|
903
|
+
assert.equal(name, "sdd-explore");
|
|
904
|
+
assert.deepEqual(tools, [
|
|
905
|
+
"read",
|
|
906
|
+
"grep",
|
|
907
|
+
"find",
|
|
908
|
+
"codegraph",
|
|
909
|
+
"edit",
|
|
910
|
+
"write",
|
|
911
|
+
"mem_save",
|
|
912
|
+
]);
|
|
913
|
+
});
|
|
914
|
+
|
|
898
915
|
test("gentle-ai-worker packages the exact scoped writer contract", () => {
|
|
899
916
|
const agentsDir = join(PACKAGE_ROOT, "assets", "agents");
|
|
900
917
|
const agentPath = join(agentsDir, "gentle-ai-worker.md");
|
|
@@ -1055,6 +1072,103 @@ test("installSddAssets installs gentle-ai-worker with a loader-compatible scoped
|
|
|
1055
1072
|
);
|
|
1056
1073
|
});
|
|
1057
1074
|
|
|
1075
|
+
test("agent home resolver centralizes Gentle and Pi agent-dir precedence", () => {
|
|
1076
|
+
const explicitGentleHome = mkdtempSync(join(tmpdir(), "gentle-pi-resolver-explicit-"));
|
|
1077
|
+
const piAgentDir = mkdtempSync(join(tmpdir(), "gentle-pi-resolver-pi-dir-"));
|
|
1078
|
+
|
|
1079
|
+
try {
|
|
1080
|
+
assert.equal(
|
|
1081
|
+
resolveGentlePiAgentHome({
|
|
1082
|
+
GENTLE_PI_AGENT_HOME: explicitGentleHome,
|
|
1083
|
+
PI_CODING_AGENT_DIR: piAgentDir,
|
|
1084
|
+
}),
|
|
1085
|
+
explicitGentleHome,
|
|
1086
|
+
);
|
|
1087
|
+
assert.equal(resolveGentlePiAgentHome({ PI_CODING_AGENT_DIR: piAgentDir }), piAgentDir);
|
|
1088
|
+
assert.equal(resolveGentlePiAgentHome({}), join(homedir(), ".pi", "agent"));
|
|
1089
|
+
assert.equal(
|
|
1090
|
+
resolveGentlePiAgentHome({ GENTLE_PI_AGENT_HOME: "", PI_CODING_AGENT_DIR: piAgentDir }),
|
|
1091
|
+
piAgentDir,
|
|
1092
|
+
"an empty explicit override falls through like Pi Subagents does",
|
|
1093
|
+
);
|
|
1094
|
+
assert.equal(
|
|
1095
|
+
resolveGentlePiAgentHome({ PI_CODING_AGENT_DIR: "" }),
|
|
1096
|
+
join(homedir(), ".pi", "agent"),
|
|
1097
|
+
"an empty PI_CODING_AGENT_DIR falls through like Pi Subagents does",
|
|
1098
|
+
);
|
|
1099
|
+
} finally {
|
|
1100
|
+
rmSync(explicitGentleHome, { recursive: true, force: true });
|
|
1101
|
+
rmSync(piAgentDir, { recursive: true, force: true });
|
|
1102
|
+
}
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
test("asset installation uses PI_CODING_AGENT_DIR as the Pi agent home when no explicit Gentle override is set", () => {
|
|
1106
|
+
const previousAgentHome = process.env.GENTLE_PI_AGENT_HOME;
|
|
1107
|
+
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
|
|
1108
|
+
const temporaryPiAgentDir = mkdtempSync(join(tmpdir(), "gentle-pi-agent-dir-"));
|
|
1109
|
+
const explicitGentleHome = mkdtempSync(join(tmpdir(), "gentle-pi-explicit-home-"));
|
|
1110
|
+
|
|
1111
|
+
try {
|
|
1112
|
+
delete process.env.GENTLE_PI_AGENT_HOME;
|
|
1113
|
+
process.env.PI_CODING_AGENT_DIR = temporaryPiAgentDir;
|
|
1114
|
+
|
|
1115
|
+
installSddAssets(PACKAGE_ROOT, true);
|
|
1116
|
+
|
|
1117
|
+
const installedPath = join(temporaryPiAgentDir, "agents", "gentle-ai-explore.md");
|
|
1118
|
+
assert.ok(existsSync(installedPath), "managed agents must install where Pi Subagents reads global definitions");
|
|
1119
|
+
assert.deepEqual(readAgentDefinition(installedPath).tools, MANAGED_EXEMPLAR_TOOLS);
|
|
1120
|
+
assert.ok(
|
|
1121
|
+
!existsSync(join(explicitGentleHome, "agents", "gentle-ai-explore.md")),
|
|
1122
|
+
"the explicit override fixture must still be untouched before it is selected",
|
|
1123
|
+
);
|
|
1124
|
+
|
|
1125
|
+
process.env.GENTLE_PI_AGENT_HOME = explicitGentleHome;
|
|
1126
|
+
installSddAssets(PACKAGE_ROOT, true);
|
|
1127
|
+
assert.ok(
|
|
1128
|
+
existsSync(join(explicitGentleHome, "agents", "gentle-ai-explore.md")),
|
|
1129
|
+
"GENTLE_PI_AGENT_HOME remains the explicit test/operator override",
|
|
1130
|
+
);
|
|
1131
|
+
} finally {
|
|
1132
|
+
if (previousAgentHome === undefined) delete process.env.GENTLE_PI_AGENT_HOME;
|
|
1133
|
+
else process.env.GENTLE_PI_AGENT_HOME = previousAgentHome;
|
|
1134
|
+
if (previousPiAgentDir === undefined) delete process.env.PI_CODING_AGENT_DIR;
|
|
1135
|
+
else process.env.PI_CODING_AGENT_DIR = previousPiAgentDir;
|
|
1136
|
+
rmSync(temporaryPiAgentDir, { recursive: true, force: true });
|
|
1137
|
+
rmSync(explicitGentleHome, { recursive: true, force: true });
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
test("global model routing uses PI_CODING_AGENT_DIR for package-installed agents", () => {
|
|
1142
|
+
const previousAgentHome = process.env.GENTLE_PI_AGENT_HOME;
|
|
1143
|
+
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
|
|
1144
|
+
const temporaryPiAgentDir = mkdtempSync(join(tmpdir(), "gentle-pi-model-agent-dir-"));
|
|
1145
|
+
const temporaryProject = mkdtempSync(join(tmpdir(), "gentle-pi-model-project-"));
|
|
1146
|
+
|
|
1147
|
+
try {
|
|
1148
|
+
delete process.env.GENTLE_PI_AGENT_HOME;
|
|
1149
|
+
process.env.PI_CODING_AGENT_DIR = temporaryPiAgentDir;
|
|
1150
|
+
installSddAssets(PACKAGE_ROOT, true);
|
|
1151
|
+
|
|
1152
|
+
const result = applyModelConfig(temporaryProject, {
|
|
1153
|
+
"gentle-ai-explore": { model: "provider/model", thinking: "high" },
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
assert.equal(result.updated, 2);
|
|
1157
|
+
const config = JSON.parse(readFileSync(join(temporaryPiAgentDir, "subagents.json"), "utf8"));
|
|
1158
|
+
assert.deepEqual(config.model_profiles["gentle-ai-explore"], {
|
|
1159
|
+
model: "provider/model",
|
|
1160
|
+
effort: "high",
|
|
1161
|
+
});
|
|
1162
|
+
} finally {
|
|
1163
|
+
if (previousAgentHome === undefined) delete process.env.GENTLE_PI_AGENT_HOME;
|
|
1164
|
+
else process.env.GENTLE_PI_AGENT_HOME = previousAgentHome;
|
|
1165
|
+
if (previousPiAgentDir === undefined) delete process.env.PI_CODING_AGENT_DIR;
|
|
1166
|
+
else process.env.PI_CODING_AGENT_DIR = previousPiAgentDir;
|
|
1167
|
+
rmSync(temporaryPiAgentDir, { recursive: true, force: true });
|
|
1168
|
+
rmSync(temporaryProject, { recursive: true, force: true });
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1058
1172
|
test("normal and forced installation copy generic agents with complete role contracts", () => {
|
|
1059
1173
|
const previousAgentHome = process.env.GENTLE_PI_AGENT_HOME;
|
|
1060
1174
|
const expectedTools = {
|
|
@@ -1146,8 +1260,8 @@ test("orchestrator routes generic roles without static RDD lens routing", () =>
|
|
|
1146
1260
|
}
|
|
1147
1261
|
|
|
1148
1262
|
const core = readFileSync(join(PACKAGE_ROOT, "assets", "orchestrator.md"), "utf8");
|
|
1149
|
-
assert.match(core, /
|
|
1150
|
-
assert.match(core, /this package
|
|
1263
|
+
assert.match(core, /injects the mirrored provider-bundle review execution contract/);
|
|
1264
|
+
assert.match(core, /this package invents no lifecycle instructions/);
|
|
1151
1265
|
});
|
|
1152
1266
|
|
|
1153
1267
|
test("pi-pretty wrapper uses real package path resolution for pnpm symlink installs", () => {
|
|
@@ -1163,9 +1277,9 @@ test("pi-pretty wrapper uses real package path resolution for pnpm symlink insta
|
|
|
1163
1277
|
assert.match(wrapper, /quietToolsEnabled/);
|
|
1164
1278
|
});
|
|
1165
1279
|
|
|
1166
|
-
test("v2.
|
|
1280
|
+
test("v2.4.0 release package and runtime stop before publication", () => {
|
|
1167
1281
|
const packageJson = readPackageJson();
|
|
1168
|
-
assert.equal(packageJson.version, "2.
|
|
1282
|
+
assert.equal(packageJson.version, "2.4.0", "the release manifest must remain explicitly pinned to v2.4.0");
|
|
1169
1283
|
assert.equal(
|
|
1170
1284
|
packageJson.scripts?.test,
|
|
1171
1285
|
"node --experimental-strip-types --test tests/*.test.ts && pnpm run check:provider-contract && pnpm run test:harness",
|
|
@@ -35,6 +35,16 @@ function fixtureEntries(): Map<string, Buffer> {
|
|
|
35
35
|
return new Map(FIXTURE_PATHS.map((path) => [path, readFileSync(join(FIXTURE_ROOT, path))]));
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// Fixture provenance: the real gentle-ai provider contract bundle 1.2.0
|
|
39
|
+
// release archive (contract semver 1.2.0), extracted. Adds `orchestration`
|
|
40
|
+
// to the manifest: gentle-pi#560 / gentle-ai#4056, #4057.
|
|
41
|
+
const FIXTURE_1_2_0_ROOT = join(import.meta.dirname, "fixtures", "provider-contract-bundle", "v1.2.0");
|
|
42
|
+
const FIXTURE_1_2_0_PATHS = [...FIXTURE_PATHS, "orchestration/pi.md"] as const;
|
|
43
|
+
|
|
44
|
+
function fixture120Entries(): Map<string, Buffer> {
|
|
45
|
+
return new Map(FIXTURE_1_2_0_PATHS.map((path) => [path, readFileSync(join(FIXTURE_1_2_0_ROOT, path))]));
|
|
46
|
+
}
|
|
47
|
+
|
|
38
48
|
function sha256Hex(bytes: Buffer): string {
|
|
39
49
|
return createHash("sha256").update(bytes).digest("hex");
|
|
40
50
|
}
|
|
@@ -177,11 +187,25 @@ test("generated baselines project roles, capabilities, and runtime registration
|
|
|
177
187
|
assert.equal(capabilities.transport_capability, PROVIDER_TRANSPORT_CAPABILITY);
|
|
178
188
|
assert.deepEqual(capabilities.mandatory_capabilities, [PROVIDER_TRANSPORT_CAPABILITY]);
|
|
179
189
|
assert.equal(capabilities.pi_registered, true);
|
|
190
|
+
assert.deepEqual(capabilities.orchestration, []);
|
|
180
191
|
// Byte determinism: regenerating from the same bundle is identical.
|
|
181
192
|
const again = generateProviderContractBaselines(verifyProviderContractBundleTree(FIXTURE_ROOT));
|
|
182
193
|
assert.deepEqual([...again.entries()], [...baselines.entries()]);
|
|
183
194
|
});
|
|
184
195
|
|
|
196
|
+
test("generated 1.2.0 baselines record the mirrored orchestration entry", () => {
|
|
197
|
+
const bundle = verifyProviderContractBundleTree(FIXTURE_1_2_0_ROOT);
|
|
198
|
+
const baselines = generateProviderContractBaselines(bundle);
|
|
199
|
+
const capabilities = JSON.parse(baselines.get("provider-capabilities.baseline.json") as string) as { orchestration: { runtime: string; path: string; sha256: string }[] };
|
|
200
|
+
assert.deepEqual(capabilities.orchestration, [
|
|
201
|
+
{
|
|
202
|
+
runtime: PI_RUNTIME_IDENTITY,
|
|
203
|
+
path: "orchestration/pi.md",
|
|
204
|
+
sha256: bundle.entrySha256.get("orchestration/pi.md"),
|
|
205
|
+
},
|
|
206
|
+
]);
|
|
207
|
+
});
|
|
208
|
+
|
|
185
209
|
// --- inventory and hash rejections ---------------------------------------------
|
|
186
210
|
|
|
187
211
|
test("rejects a ninth file in the inventory", () => {
|
|
@@ -315,6 +339,58 @@ test("rejects a schema whose $id does not match the manifest result schema id",
|
|
|
315
339
|
);
|
|
316
340
|
});
|
|
317
341
|
|
|
342
|
+
// --- orchestration (contract 1.2.0) -------------------------------------------------
|
|
343
|
+
|
|
344
|
+
test("verifies the 1.2.0 fixture tree and exposes the mirrored pi orchestration text", () => {
|
|
345
|
+
const bundle = verifyProviderContractBundleTree(FIXTURE_1_2_0_ROOT);
|
|
346
|
+
assert.equal(bundle.contractSemver, "1.2.0");
|
|
347
|
+
assert.deepEqual([bundle.major, bundle.minor, bundle.patch], [1, 2, 0]);
|
|
348
|
+
assert.equal(bundle.entries.size, 9);
|
|
349
|
+
assert.deepEqual([...bundle.orchestration.keys()], [PI_RUNTIME_IDENTITY]);
|
|
350
|
+
const piText = bundle.orchestration.get(PI_RUNTIME_IDENTITY);
|
|
351
|
+
assert.equal(piText, readFileSync(join(FIXTURE_1_2_0_ROOT, "orchestration/pi.md"), "utf8"));
|
|
352
|
+
assert.equal(bundle.entrySha256.get("orchestration/pi.md"), sha256Hex(readFileSync(join(FIXTURE_1_2_0_ROOT, "orchestration/pi.md"))));
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("rejects a 1.2.0 manifest without the required orchestration array", () => {
|
|
356
|
+
assertRejects(
|
|
357
|
+
withManifest(fixture120Entries(), (manifest) => {
|
|
358
|
+
delete manifest.orchestration;
|
|
359
|
+
}),
|
|
360
|
+
"manifest.orchestration is required from contract 1.2.0",
|
|
361
|
+
);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test("rejects a 1.2.0 orchestration entry whose runtime is not registered in manifest.runtimes", () => {
|
|
365
|
+
assertRejects(
|
|
366
|
+
withManifest(fixture120Entries(), (manifest) => {
|
|
367
|
+
const orchestration = manifest.orchestration as { runtime: string; file: { path: string } }[];
|
|
368
|
+
orchestration[0]!.runtime = "aider";
|
|
369
|
+
orchestration[0]!.file.path = "orchestration/aider.md";
|
|
370
|
+
}),
|
|
371
|
+
'manifest.orchestration[0].runtime "aider" is not registered in manifest.runtimes',
|
|
372
|
+
);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test("rejects a 1.2.0 orchestration entry whose file bytes do not match its manifest SHA-256", () => {
|
|
376
|
+
const entries = fixture120Entries();
|
|
377
|
+
const tampered = Buffer.from(entries.get("orchestration/pi.md") as Buffer);
|
|
378
|
+
tampered[tampered.length - 2] = 0x20;
|
|
379
|
+
entries.set("orchestration/pi.md", tampered);
|
|
380
|
+
assertRejects(entries, "does not match its manifest SHA-256");
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test("rejects an orchestration array on a 1.1.0 manifest", () => {
|
|
384
|
+
assertRejects(
|
|
385
|
+
withManifest(fixtureEntries(), (manifest) => {
|
|
386
|
+
manifest.orchestration = [
|
|
387
|
+
{ runtime: PI_RUNTIME_IDENTITY, file: { path: "orchestration/pi.md", sha256: "0".repeat(64) } },
|
|
388
|
+
];
|
|
389
|
+
}),
|
|
390
|
+
"manifest.orchestration is not part of contract 1.1.0",
|
|
391
|
+
);
|
|
392
|
+
});
|
|
393
|
+
|
|
318
394
|
// --- archive safety ------------------------------------------------------------------
|
|
319
395
|
|
|
320
396
|
test("rejects a tar entry that escapes the bundle root", () => {
|
|
@@ -27,6 +27,10 @@ const FIXTURE_PATHS = [
|
|
|
27
27
|
"vectors/targeted-validator.json",
|
|
28
28
|
] as const;
|
|
29
29
|
|
|
30
|
+
// Fixture provenance: the real gentle-ai provider contract bundle 1.2.0
|
|
31
|
+
// release archive, extracted. gentle-pi#560 / gentle-ai#4056, #4057.
|
|
32
|
+
const FIXTURE_1_2_0_ROOT = join(import.meta.dirname, "fixtures", "provider-contract-bundle", "v1.2.0");
|
|
33
|
+
|
|
30
34
|
interface LockRecord {
|
|
31
35
|
schema: string;
|
|
32
36
|
acquisition: string;
|
|
@@ -105,6 +109,21 @@ test("mirrors a verified local bundle tree and the offline drift check passes",
|
|
|
105
109
|
});
|
|
106
110
|
});
|
|
107
111
|
|
|
112
|
+
test("mirrors a verified local 1.2.0 bundle tree, including its orchestration entry, and the offline drift check passes", () => {
|
|
113
|
+
withTemporaryRoot((packageRoot) => {
|
|
114
|
+
mirrorProviderContractBundle(FIXTURE_1_2_0_ROOT, packageRoot);
|
|
115
|
+
const lockPath = join(packageRoot, PROVIDER_CONTRACT_MIRROR_ROOT, PROVIDER_CONTRACT_LOCK_FILE);
|
|
116
|
+
const lock = JSON.parse(readFileSync(lockPath, "utf8")) as LockRecord;
|
|
117
|
+
assert.equal(lock.contract_semver, "1.2.0");
|
|
118
|
+
assert.equal(Object.keys(lock.entries).length, 9);
|
|
119
|
+
assert.ok("orchestration/pi.md" in lock.entries, "the mirrored lock must record orchestration/pi.md");
|
|
120
|
+
const mirrored = join(packageRoot, PROVIDER_CONTRACT_MIRROR_ROOT, "v1.2.0", "bundle", "orchestration", "pi.md");
|
|
121
|
+
assert.ok(existsSync(mirrored), "orchestration/pi.md must be mirrored");
|
|
122
|
+
assert.deepEqual(readFileSync(mirrored), readFileSync(join(FIXTURE_1_2_0_ROOT, "orchestration", "pi.md")));
|
|
123
|
+
assert.deepEqual(checkProviderContractMirror(packageRoot), []);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
108
127
|
test("mirrors a verified local archive and records the archive digest in the lock", () => {
|
|
109
128
|
withTemporaryRoot((packageRoot) => {
|
|
110
129
|
const archivePath = join(packageRoot, "gentle-ai-review-provider-contract-1.1.0.tar.gz");
|