gentle-pi 3.2.0 → 3.3.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/assets/orchestrator-delegation.md +13 -8
- package/assets/orchestrator.md +2 -2
- package/docs/gentle-shell.md +40 -17
- package/docs/readme-reference.md +41 -7
- package/docs/review-integration.md +25 -11
- package/extensions/gentle-agents.ts +85 -17
- package/extensions/gentle-ai.ts +179 -12
- package/extensions/gentle-shell.ts +408 -38
- package/extensions/gentle-todo.ts +19 -1
- package/lib/agents-view.ts +41 -14
- package/lib/agents-widget.ts +84 -13
- package/lib/command-palette-catalog.ts +1 -0
- package/lib/double-esc-cancel-policy.ts +138 -0
- package/lib/inprocess-reviewer.ts +260 -0
- package/lib/model-routing-authority.ts +1 -1
- package/lib/native-review-cli.ts +23 -0
- package/lib/odd-runtime-delegation-gate.ts +88 -0
- package/lib/review-host-relay.ts +262 -94
- package/lib/review-integration-v2.ts +110 -26
- package/lib/shell-bar.ts +158 -29
- package/lib/shell-card.ts +19 -9
- package/lib/shell-changes-view.ts +43 -5
- package/lib/shell-changes.ts +92 -5
- package/lib/shell-hover.ts +39 -0
- package/lib/shell-prompt.ts +10 -1
- package/lib/shell-sidebar-layout.ts +111 -15
- package/lib/shell-sidebar.ts +16 -0
- package/lib/shell-todo.ts +7 -1
- package/lib/shell-usage-view.ts +98 -10
- package/lib/shell-usage.ts +226 -10
- package/package.json +2 -1
- package/runtime/native-review-cli.mjs +23 -0
- package/runtime/review-integration-v2.mjs +110 -26
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/maintainer/provider-relay-matrix.mjs +118 -47
- package/scripts/mirror-odd-routing.mjs +242 -0
- package/scripts/verify-package-files.mjs +3 -3
- package/tests/agents-grouping.test.ts +75 -18
- package/tests/agents-view.test.ts +28 -18
- package/tests/agents-widget.test.ts +100 -12
- package/tests/command-palette.test.ts +1 -0
- package/tests/devbinary/pi-host-relay.devtest.ts +176 -138
- package/tests/double-esc-cancel-policy.test.ts +194 -0
- package/tests/gentle-agents.test.ts +528 -5
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-installer.test.ts +47 -47
- package/tests/gentle-ai.test.ts +69 -5
- package/tests/gentle-shell.test.ts +903 -25
- package/tests/gentle-todo.test.ts +17 -4
- package/tests/inprocess-reviewer.test.ts +368 -0
- package/tests/maintainer/provider-relay.maintest.ts +101 -143
- package/tests/native-review-capability-contract.test.ts +32 -1
- package/tests/odd-routing-canonical-ratchet.test.ts +293 -0
- package/tests/odd-routing-contract.test.ts +57 -0
- package/tests/odd-runtime-delegation-gate.test.ts +212 -0
- package/tests/orchestrator-rdd-ownership.test.ts +3 -3
- package/tests/package-manifest.test.ts +6 -6
- package/tests/review-controller-native-routing.test.ts +60 -1
- package/tests/review-host-relay-routing.test.ts +77 -0
- package/tests/review-host-relay.test.ts +285 -239
- package/tests/review-integration-v2-forward.test.ts +61 -0
- package/tests/review-integration-v2.test.ts +116 -1
- package/tests/review-relay-transport-agent.test.ts +83 -0
- package/tests/runtime-harness.mjs +11 -0
- package/tests/session-changes-shell.test.ts +27 -0
- package/tests/session-worktree-registry.test.ts +41 -0
- package/tests/shell-bar.test.ts +224 -6
- package/tests/shell-card.test.ts +5 -3
- package/tests/shell-changes-view.test.ts +47 -0
- package/tests/shell-changes.test.ts +177 -0
- package/tests/shell-hover.test.ts +19 -0
- package/tests/shell-prompt.test.ts +20 -0
- package/tests/shell-sidebar-fullscreen.test.ts +59 -0
- package/tests/shell-sidebar-layout.test.ts +243 -5
- package/tests/shell-sidebar.test.ts +25 -1
- package/tests/shell-todo.test.ts +36 -0
- package/tests/shell-usage-view.test.ts +123 -3
- package/tests/shell-usage.test.ts +254 -6
- package/lib/opaque-pi-reviewer-adapter.ts +0 -284
- package/tests/opaque-pi-reviewer-adapter.test.ts +0 -266
|
@@ -56,11 +56,12 @@ const VALIDATOR_TOKENS = Object.freeze([`--lineage=${ROLE_LINEAGE}`, `--expected
|
|
|
56
56
|
const VALIDATION_REQUEST = Object.freeze({ schema: "gentle-ai.review-targeted-validation-request/v1", requestHash: ROLE_REQUEST_HASH });
|
|
57
57
|
const ROLE_ARTIFACT = Object.freeze({ schema: "gentle-ai.review-provider-role-capture/v1", lineage_id: ROLE_LINEAGE, target_identity: ROLE_TARGET, role: "refuter", captured: true });
|
|
58
58
|
|
|
59
|
+
const REVIEWER_SELECTION = "test-provider/test-model";
|
|
59
60
|
function descriptor(overrides = {}) {
|
|
60
61
|
return {
|
|
61
62
|
schema: DESCRIPTOR_SCHEMA,
|
|
62
63
|
gentleAiExecutable: PLACEHOLDER_BINARY,
|
|
63
|
-
|
|
64
|
+
reviewerSelection: REVIEWER_SELECTION,
|
|
64
65
|
cases: [{ name: "baseline-negative-control", kind: "relay-unavailable", captureArgumentTokens: [...CAPTURE_TOKENS], submission: structuredClone(SUBMISSION) }],
|
|
65
66
|
...overrides,
|
|
66
67
|
};
|
|
@@ -121,7 +122,7 @@ function roleDescriptor(kind: "provider-role-refuter" | "provider-role-validator
|
|
|
121
122
|
const tokens = kind === "provider-role-refuter" ? REFUTER_TOKENS : VALIDATOR_TOKENS;
|
|
122
123
|
const entry: Record<string, unknown> = { name: `${kind}-case`, kind, argumentTokens: [...tokens] };
|
|
123
124
|
if (kind === "provider-role-validator") entry.validationRequest = structuredClone(VALIDATION_REQUEST);
|
|
124
|
-
return { schema: DESCRIPTOR_SCHEMA, gentleAiExecutable: PLACEHOLDER_BINARY,
|
|
125
|
+
return { schema: DESCRIPTOR_SCHEMA, gentleAiExecutable: PLACEHOLDER_BINARY, reviewerSelection: REVIEWER_SELECTION, cases: [entry], ...overrides };
|
|
125
126
|
}
|
|
126
127
|
const ROLE_STUB_BINARY = sandboxPath("gentle-ai-role-stub");
|
|
127
128
|
writeFileSync(ROLE_STUB_BINARY, "stub");
|
|
@@ -355,7 +356,6 @@ function capableStubHarness(t: test.TestContext) {
|
|
|
355
356
|
t.after(() => rmSync(directory, { recursive: true, force: true }));
|
|
356
357
|
const materializeLog = join(directory, "materialized");
|
|
357
358
|
const submitLog = join(directory, "submitted");
|
|
358
|
-
const piLog = join(directory, "pi-launched");
|
|
359
359
|
const gentleAi = join(directory, "gentle-ai");
|
|
360
360
|
// Deliberately CAPABLE: it honours --materialize=true and would accept a
|
|
361
361
|
// submission, exactly like a binary a maintainer mis-declared as incapable.
|
|
@@ -375,152 +375,114 @@ process.exit(0);
|
|
|
375
375
|
`,
|
|
376
376
|
);
|
|
377
377
|
chmodSync(gentleAi, 0o755);
|
|
378
|
-
|
|
379
|
-
writeFileSync(
|
|
380
|
-
pi,
|
|
381
|
-
`#!/usr/bin/env node
|
|
382
|
-
const fs = require("node:fs");
|
|
383
|
-
fs.writeFileSync(${JSON.stringify(piLog)}, "launched");
|
|
384
|
-
const chunks = [];
|
|
385
|
-
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
386
|
-
process.stdin.on("end", () => { process.stdout.write("pi-result-bytes"); process.exit(0); });
|
|
387
|
-
`,
|
|
388
|
-
);
|
|
389
|
-
chmodSync(pi, 0o755);
|
|
390
|
-
return { gentleAi, pi, materializeLog, submitLog, piLog };
|
|
378
|
+
return { gentleAi, materializeLog, submitLog };
|
|
391
379
|
}
|
|
392
|
-
|
|
380
|
+
// gentle-pi#311 P4: lens captures no longer spawn a pi child, so the
|
|
381
|
+
// mis-declared-capable negative control no longer needs an "unlaunchable pi"
|
|
382
|
+
// fixture -- it must instead fail closed at the reviewer-registry stage
|
|
383
|
+
// (`unresolvableReviewerRegistry`, provider-relay-matrix.mjs), before any
|
|
384
|
+
// completion or submission.
|
|
385
|
+
test("a relay-unavailable case against a CAPABLE binary fails closed at the reviewer stage: zero completion, zero submission", async (t) => {
|
|
393
386
|
const stub = capableStubHarness(t);
|
|
394
|
-
const [verdict] = await runMatrix(validateDescriptor({ ...descriptor(), gentleAiExecutable: stub.gentleAi
|
|
387
|
+
const [verdict] = await runMatrix(validateDescriptor({ ...descriptor(), gentleAiExecutable: stub.gentleAi }));
|
|
395
388
|
// The declared binary really is capable, so the negative control cannot
|
|
396
389
|
// pass — but it must fail WITHOUT mutating anything.
|
|
397
390
|
assert.equal(verdict!.verdict, "fail");
|
|
398
|
-
assert.match(verdict!.reason, /kind=
|
|
399
|
-
// The load-bearing
|
|
400
|
-
// submission never fired.
|
|
401
|
-
assert.equal(existsSync(stub.piLog), false, "pi must never launch for a relay-unavailable case");
|
|
391
|
+
assert.match(verdict!.reason, /kind=reviewer-model-not-found stage=pi mutationOutcome=none/);
|
|
392
|
+
// The load-bearing assertion: the real submission never fired.
|
|
402
393
|
assert.equal(existsSync(stub.submitLog), false, "a relay-unavailable case must never reach submit");
|
|
403
394
|
// Materialize still happens: it is the honest capability probe, and it is
|
|
404
395
|
// read-only (mutationOutcome stays "none" before submit).
|
|
405
396
|
assert.equal(existsSync(stub.materializeLog), true);
|
|
406
397
|
});
|
|
407
398
|
// ---------------------------------------------------------------------------
|
|
408
|
-
//
|
|
409
|
-
//
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
//
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
// not silently claimed here.
|
|
399
|
+
// gentle-pi#311 P4: the two "resolve-once" tests this section held (a POSIX
|
|
400
|
+
// subprocess proof and a platform-neutral boundary proof) verified that a
|
|
401
|
+
// bare `pi` PATH declaration was resolved exactly once and never re-resolved
|
|
402
|
+
// between precheck and launch (issue #324). Lens captures now run in-process
|
|
403
|
+
// through an injected reviewer registry — there is no executable to resolve
|
|
404
|
+
// or re-resolve, so that TOCTOU class no longer exists for this transport and
|
|
405
|
+
// the coverage was removed rather than kept as dead assertions. The armed
|
|
406
|
+
// positive-lens journey's new wiring (the in-process reviewer registry built
|
|
407
|
+
// from `descriptor.reviewerSelection`) is covered below instead.
|
|
418
408
|
// ---------------------------------------------------------------------------
|
|
419
|
-
test("
|
|
420
|
-
const
|
|
421
|
-
chmodSync(
|
|
422
|
-
t.after(() => rmSync(
|
|
423
|
-
const
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
//
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
409
|
+
test("armed positive-lens: runMatrix completes end-to-end through the in-process reviewer registry against a stub gentle-ai binary", async (t) => {
|
|
410
|
+
const directory = mkdtempSync(join(tmpdir(), "gentle-pi-maintainer-armed-positive-"));
|
|
411
|
+
chmodSync(directory, 0o700);
|
|
412
|
+
t.after(() => rmSync(directory, { recursive: true, force: true }));
|
|
413
|
+
const materializeLog = join(directory, "materialized");
|
|
414
|
+
const submitLog = join(directory, "submitted");
|
|
415
|
+
const gentleAi = join(directory, "gentle-ai");
|
|
416
|
+
// A capable gentle-ai: materialize returns a frozen prompt carrying the
|
|
417
|
+
// real GENTLE_AI_REVIEW_BINDING and GENTLE_AI_REVIEW_CONTEXT lines
|
|
418
|
+
// (exactly what Go embeds in production) so the armed in-process reviewer
|
|
419
|
+
// can echo the subject_hash and the changed-path manifest the submission
|
|
420
|
+
// expects. Submit does NOT accept unconditionally: it reads the --input
|
|
421
|
+
// payload and refuses any result missing a field Go admission requires
|
|
422
|
+
// (subject_hash, inspection.status, inspection.paths, findings,
|
|
423
|
+
// evidence), so a faux reviewer that drifts from the reviewer contract
|
|
424
|
+
// fails here instead of only against the real binary.
|
|
425
|
+
const subjectHash = `sha256:${"a".repeat(64)}`;
|
|
426
|
+
const manifestPaths = ["lib/review-host-relay.ts", "docs/review-integration.md"];
|
|
427
|
+
const bindingLine = `GENTLE_AI_REVIEW_BINDING {"subject_hash":"${subjectHash}"}`;
|
|
428
|
+
const contextLine = `GENTLE_AI_REVIEW_CONTEXT ${JSON.stringify({ changed_path_manifest: manifestPaths.map((path) => ({ path, status: "modified" })) })}`;
|
|
429
|
+
writeFileSync(
|
|
430
|
+
gentleAi,
|
|
431
|
+
`#!/usr/bin/env node
|
|
432
|
+
const fs = require("node:fs");
|
|
433
|
+
const argv = process.argv.slice(2);
|
|
434
|
+
if (argv.includes("--materialize=true")) {
|
|
435
|
+
fs.writeFileSync(${JSON.stringify(materializeLog)}, JSON.stringify(argv));
|
|
436
|
+
process.stdout.write(${JSON.stringify(`${bindingLine}\n${contextLine}\nreview this diff\n`)});
|
|
437
|
+
process.exit(0);
|
|
438
|
+
}
|
|
439
|
+
const inputToken = argv.find((token) => token.startsWith("--input="));
|
|
440
|
+
const payload = JSON.parse(fs.readFileSync(inputToken.slice("--input=".length), "utf8"));
|
|
441
|
+
fs.writeFileSync(${JSON.stringify(submitLog)}, JSON.stringify({ argv, payload }));
|
|
442
|
+
const missing = [];
|
|
443
|
+
if (payload.subject_hash !== ${JSON.stringify(subjectHash)}) missing.push("subject_hash");
|
|
444
|
+
if (payload.inspection?.status !== "completed") missing.push("inspection.status");
|
|
445
|
+
if (!Array.isArray(payload.inspection?.paths) || payload.inspection.paths.length === 0) missing.push("inspection.paths");
|
|
446
|
+
if (!Array.isArray(payload.findings)) missing.push("findings");
|
|
447
|
+
if (!Array.isArray(payload.evidence)) missing.push("evidence");
|
|
448
|
+
if (missing.length > 0) {
|
|
449
|
+
process.stderr.write("reviewer result is missing required fields: " + missing.join(", "));
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
process.stdout.write(JSON.stringify({ schema: "gentle-ai.review-result-artifact/v2", admission_decision: "completed" }));
|
|
453
|
+
process.exit(0);
|
|
454
|
+
`,
|
|
455
|
+
);
|
|
444
456
|
chmodSync(gentleAi, 0o755);
|
|
445
|
-
const savedPath = process.env.PATH;
|
|
446
|
-
process.env.PATH = [dirA, dirB, savedPath].join(delimiter);
|
|
447
|
-
t.after(() => { process.env.PATH = savedPath; });
|
|
448
457
|
const [verdict] = await runMatrix(validateDescriptor({
|
|
449
458
|
...descriptor(),
|
|
450
459
|
gentleAiExecutable: gentleAi,
|
|
451
|
-
|
|
452
|
-
cases: [{ name: "resolve-once", kind: "positive-lens", captureArgumentTokens: [...CAPTURE_TOKENS], submission: structuredClone(SUBMISSION) }],
|
|
460
|
+
cases: [{ name: "armed-positive-lens", kind: "positive-lens", captureArgumentTokens: [...CAPTURE_TOKENS], submission: structuredClone(SUBMISSION) }],
|
|
453
461
|
}), { armPositive: true });
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
assert.
|
|
457
|
-
assert.
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
// ---------------------------------------------------------------------------
|
|
471
|
-
test("platform-neutral: runMatrix passes the first resolved concrete Pi path into the relay boundary (no second resolution)", async (t) => {
|
|
472
|
-
// Keep the fixture on the same Windows volume as process.cwd() so the
|
|
473
|
-
// first PATH component genuinely exercises relative-path normalization.
|
|
474
|
-
const root = mkdtempSync(join(process.cwd(), ".gentle-pi-maintainer-portable-"));
|
|
475
|
-
chmodSync(root, 0o700);
|
|
476
|
-
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
477
|
-
const dirA = join(root, "path-a");
|
|
478
|
-
const dirB = join(root, "path-b");
|
|
479
|
-
mkdirSync(dirA, { recursive: true });
|
|
480
|
-
mkdirSync(dirB, { recursive: true });
|
|
481
|
-
chmodSync(dirA, 0o700);
|
|
482
|
-
chmodSync(dirB, 0o700);
|
|
483
|
-
const firstPi = join(dirA, "pi");
|
|
484
|
-
const secondPi = join(dirB, "pi");
|
|
485
|
-
// Ordinary files (no shebang/chmod needed): the injected relay never
|
|
486
|
-
// executes them. Only the precheck's existsSync/statSync touches them.
|
|
487
|
-
writeFileSync(firstPi, "first");
|
|
488
|
-
writeFileSync(secondPi, "second");
|
|
489
|
-
const gentleAi = join(root, "gentle-ai");
|
|
490
|
-
writeFileSync(gentleAi, "gentle-ai");
|
|
491
|
-
// First PATH component is RELATIVE to process.cwd(): a relative component
|
|
492
|
-
// must still normalize to the same absolute firstPi the precheck resolved.
|
|
493
|
-
const dirARelative = relative(process.cwd(), dirA);
|
|
494
|
-
assert.equal(isAbsolute(dirARelative), false, "fixture must keep the first PATH component relative");
|
|
495
|
-
const savedPath = process.env.PATH;
|
|
496
|
-
process.env.PATH = [dirARelative, dirB, savedPath].join(delimiter);
|
|
497
|
-
t.after(() => { process.env.PATH = savedPath; });
|
|
498
|
-
let receivedPiExecutable: string | undefined;
|
|
462
|
+
assert.equal(verdict!.verdict, "pass", JSON.stringify(verdict));
|
|
463
|
+
assert.ok(verdict!.promptByteLength! > 0);
|
|
464
|
+
assert.ok(verdict!.resultByteLength! > 0);
|
|
465
|
+
assert.equal(existsSync(materializeLog), true);
|
|
466
|
+
assert.equal(existsSync(submitLog), true, "an armed, successful positive-lens case must reach submit");
|
|
467
|
+
// The submitted payload must carry every field Go admission requires and
|
|
468
|
+
// inspect exactly the frozen manifest, so the faux reviewer can never
|
|
469
|
+
// drift from the reviewer contract unnoticed.
|
|
470
|
+
const submitted = JSON.parse(readFileSync(submitLog, "utf8")) as { payload: Record<string, unknown> };
|
|
471
|
+
assert.equal(submitted.payload["subject_hash"], subjectHash);
|
|
472
|
+
assert.deepEqual(submitted.payload["inspection"], { status: "completed", paths: manifestPaths });
|
|
473
|
+
assert.deepEqual(submitted.payload["findings"], []);
|
|
474
|
+
assert.ok(Array.isArray(submitted.payload["evidence"]) && (submitted.payload["evidence"] as unknown[]).length > 0);
|
|
475
|
+
});
|
|
476
|
+
test("armed positive-lens: a malformed reviewerSelection blocks with a clear reason, never a synthesized model", async (t) => {
|
|
477
|
+
const stub = capableStubHarness(t);
|
|
499
478
|
const [verdict] = await runMatrix(validateDescriptor({
|
|
500
|
-
...descriptor(),
|
|
501
|
-
gentleAiExecutable: gentleAi,
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
receivedPiExecutable = request.piExecutable;
|
|
508
|
-
// Simulate materialization removing the first candidate; the
|
|
509
|
-
// path was captured at precheck and must not be re-resolved.
|
|
510
|
-
rmSync(firstPi, { force: true });
|
|
511
|
-
return { promptByteLength: 1, resultByteLength: 1, submission: "{}" };
|
|
512
|
-
},
|
|
513
|
-
});
|
|
514
|
-
// The relay received the EXACT first concrete resolved path — equality to
|
|
515
|
-
// the absolute fixture proves normalization held under a relative PATH.
|
|
516
|
-
assert.equal(receivedPiExecutable, firstPi);
|
|
517
|
-
assert.notEqual(receivedPiExecutable, secondPi);
|
|
518
|
-
assert.notEqual(receivedPiExecutable, "pi");
|
|
519
|
-
assert.equal(verdict!.verdict, "pass");
|
|
520
|
-
// The first candidate was removed during the relay call; the second is
|
|
521
|
-
// untouched, proving no fallthrough resolution occurred.
|
|
522
|
-
assert.equal(existsSync(firstPi), false);
|
|
523
|
-
assert.equal(existsSync(secondPi), true);
|
|
479
|
+
...descriptor({ reviewerSelection: "not-a-provider-slash-id" }),
|
|
480
|
+
gentleAiExecutable: stub.gentleAi,
|
|
481
|
+
cases: [{ name: "malformed-reviewer-selection", kind: "positive-lens", captureArgumentTokens: [...CAPTURE_TOKENS], submission: structuredClone(SUBMISSION) }],
|
|
482
|
+
}), { armPositive: true });
|
|
483
|
+
assert.equal(verdict!.verdict, "fail");
|
|
484
|
+
assert.match(verdict!.reason, /reviewerSelection/);
|
|
485
|
+
assert.equal(existsSync(stub.submitLog), false, "a malformed reviewer selection must never reach submit");
|
|
524
486
|
});
|
|
525
487
|
|
|
526
488
|
// ---------------------------------------------------------------------------
|
|
@@ -549,7 +511,7 @@ test("negative control: the real relay returns a typed relay-unavailable error b
|
|
|
549
511
|
// pi launch; RELAY_UNAVAILABLE at materialize proves Pi never launched.
|
|
550
512
|
let caught: unknown;
|
|
551
513
|
try {
|
|
552
|
-
await runReviewHostRelaySlot({ captureArgumentTokens: [...CAPTURE_TOKENS], submission: structuredClone(SUBMISSION), gentleAiExecutable: baselineBinary
|
|
514
|
+
await runReviewHostRelaySlot({ captureArgumentTokens: [...CAPTURE_TOKENS], submission: structuredClone(SUBMISSION), gentleAiExecutable: baselineBinary! });
|
|
553
515
|
} catch (error) {
|
|
554
516
|
caught = error;
|
|
555
517
|
}
|
|
@@ -568,16 +530,18 @@ test("negative control: an armed positive-lens against the baseline blocks becau
|
|
|
568
530
|
});
|
|
569
531
|
// ---------------------------------------------------------------------------
|
|
570
532
|
// Honest positive arming/skip — the organic journey needs a real capable
|
|
571
|
-
// binary
|
|
572
|
-
//
|
|
573
|
-
//
|
|
533
|
+
// binary and a real review session (lifecycle machinery beyond this work
|
|
534
|
+
// unit); the reviewer leg itself runs through the in-process registry built
|
|
535
|
+
// from `descriptor.reviewerSelection` (gentle-pi#311 P4), never a real pi
|
|
536
|
+
// child. The runner blocks the positive leg before materialize unless armed.
|
|
537
|
+
// These tests run WITHOUT the arm (the verifier arms it).
|
|
574
538
|
// ---------------------------------------------------------------------------
|
|
575
539
|
const capableArmed = typeof capableBinary === "string" && capableBinary.length > 0 && capableBinary.startsWith("/") && existsSync(capableBinary);
|
|
576
540
|
const capableReason = () => `${CAPABLE_ENV} is unset or not an existing absolute path; supply a capable gentle-ai binary (local build of origin/main with the pi host-relay surface), or set ${REQUIRE_ENV}=1 to fail instead of skip.`;
|
|
577
541
|
if (!capableArmed && armed) throw new Error(capableReason());
|
|
578
542
|
if (!capableArmed) console.log(`tests/maintainer/provider-relay.maintest.ts: ${capableReason()}`);
|
|
579
|
-
function positiveDescriptor(gentleAi = capableBinary
|
|
580
|
-
return validateDescriptor({ ...descriptor(), gentleAiExecutable: gentleAi!,
|
|
543
|
+
function positiveDescriptor(gentleAi = capableBinary) {
|
|
544
|
+
return validateDescriptor({ ...descriptor(), gentleAiExecutable: gentleAi!, cases: [{ name: "positive-lens-relay", kind: "positive-lens", captureArgumentTokens: [...CAPTURE_TOKENS], submission: structuredClone(SUBMISSION) }] });
|
|
581
545
|
}
|
|
582
546
|
test("positive arming/skip: an unarmed positive leg blocks loudly and never fakes green", { skip: !capableArmed }, async () => {
|
|
583
547
|
const [verdict] = await runMatrix(positiveDescriptor());
|
|
@@ -588,12 +552,6 @@ test("positive arming/skip: an unarmed positive leg blocks loudly and never fake
|
|
|
588
552
|
assert.notEqual(verdict!.verdict, "pass");
|
|
589
553
|
assert.notEqual(verdict!.verdict, "fail");
|
|
590
554
|
});
|
|
591
|
-
test("positive arming/skip: a missing pi blocks at the pi pre-arm before materialize", { skip: !capableArmed }, async () => {
|
|
592
|
-
const [verdict] = await runMatrix(positiveDescriptor(capableBinary, sandboxPath("not-a-real-pi")));
|
|
593
|
-
assert.equal(verdict!.verdict, "blocked");
|
|
594
|
-
assert.match(verdict!.reason, /piExecutable/);
|
|
595
|
-
assert.equal(verdict!.command, POSITIVE_JOURNEY_COMMAND);
|
|
596
|
-
});
|
|
597
555
|
test("positive arming/skip: the exact next evidence command is surfaced for the separate verifier", { skip: !capableArmed }, async () => {
|
|
598
556
|
const [verdict] = await runMatrix(positiveDescriptor());
|
|
599
557
|
assert.match(verdict!.command!, /node --experimental-strip-types scripts\/maintainer\/provider-relay-matrix.mjs --descriptor/);
|
|
@@ -288,11 +288,42 @@ test("3.1.0 repeats 3.0.1 because the negotiated lane Pi consumes is unchanged",
|
|
|
288
288
|
assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["3.0.1"] as Record<string, boolean>);
|
|
289
289
|
});
|
|
290
290
|
|
|
291
|
+
test("3.2.1 repeats 3.1.0 because the negotiated lane Pi consumes is unchanged", () => {
|
|
292
|
+
// v3.2.1 changed the ODD orchestrator contract only (gentle-ai #4714
|
|
293
|
+
// follow-up). Diffing contracts/review-integration/v2 and
|
|
294
|
+
// contracts/review-provider-contract between the v3.1.0 and v3.2.1 tags
|
|
295
|
+
// in the gentle-ai source tree showed zero byte changes (provider contract
|
|
296
|
+
// stays 1.2.0), so this row repeats 3.1.0. riskEvidence and hint remain
|
|
297
|
+
// dark because neither is proven to reach Pi's negotiated START path.
|
|
298
|
+
const contract = NATIVE_CLI_CONTRACTS["3.2.1"] as Record<string, boolean>;
|
|
299
|
+
assert.equal(contract.riskEvidence, false);
|
|
300
|
+
assert.equal(contract.hint, false);
|
|
301
|
+
assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["3.1.0"] as Record<string, boolean>);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("3.4.0 repeats 3.2.1 because the negotiated lane Pi consumes is unchanged", () => {
|
|
305
|
+
// v3.4.0 (gentle-pi never pinned the intervening v3.3.0 tag, so it gets no
|
|
306
|
+
// row) added capabilities/v2.6, status/v8-v9, and the `review assess`
|
|
307
|
+
// review_due/review_due_reason/next_transition fields. Diffing
|
|
308
|
+
// contracts/review-integration/v2 and contracts/review-provider-contract
|
|
309
|
+
// between the v3.2.1 and v3.4.0 tags in the gentle-ai source tree showed
|
|
310
|
+
// the provider contract stays byte-identical at 1.2.0, and every
|
|
311
|
+
// review-integration/v2 change is an additive superset that
|
|
312
|
+
// decodeReviewStatusV3 and the capabilities negotiator already accept
|
|
313
|
+
// without touching the closed START/STATUS fields this row negotiates, so
|
|
314
|
+
// this row repeats 3.2.1. riskEvidence and hint remain dark because
|
|
315
|
+
// neither is proven to reach Pi's negotiated START path.
|
|
316
|
+
const contract = NATIVE_CLI_CONTRACTS["3.4.0"] as Record<string, boolean>;
|
|
317
|
+
assert.equal(contract.riskEvidence, false);
|
|
318
|
+
assert.equal(contract.hint, false);
|
|
319
|
+
assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["3.2.1"] as Record<string, boolean>);
|
|
320
|
+
});
|
|
321
|
+
|
|
291
322
|
test("no shipped version key was added beyond the pin bump", () => {
|
|
292
323
|
// Rows are promises to consumers, so a new key only ever appears in a
|
|
293
324
|
// dedicated commit alongside a pin bump, never as a side effect. v2.2.4 and
|
|
294
325
|
// v2.3.0 shipped upstream while Pi stayed on 2.2.3 and were never pinned,
|
|
295
326
|
// so they get no row: a row asserts ground truth measured against a binary
|
|
296
327
|
// Pi actually ran, and the table only has to be ascending, not gapless.
|
|
297
|
-
assert.deepEqual(Object.keys(NATIVE_CLI_CONTRACTS), [...DARK_VERSIONS, "2.2.0", "2.2.1", "2.2.2", "2.2.3", "2.4.0", "2.5.0-rc.3", "2.5.0", "2.6.0", "2.7.0", "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.1", "3.0.0", "3.0.1", "3.1.0"]);
|
|
328
|
+
assert.deepEqual(Object.keys(NATIVE_CLI_CONTRACTS), [...DARK_VERSIONS, "2.2.0", "2.2.1", "2.2.2", "2.2.3", "2.4.0", "2.5.0-rc.3", "2.5.0", "2.6.0", "2.7.0", "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.1", "3.0.0", "3.0.1", "3.1.0", "3.2.1", "3.4.0"]);
|
|
298
329
|
});
|