gentle-pi 1.2.0 → 2.0.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 +40 -9
- package/assets/agents/review-readability.md +0 -1
- package/assets/agents/review-reliability.md +0 -1
- package/assets/agents/review-resilience.md +0 -1
- package/assets/agents/review-risk.md +0 -1
- package/contracts/review-integration/v1/fixtures/binding-revision-conflict.fixture.json +25 -0
- package/contracts/review-integration/v1/fixtures/capabilities-v1.2.fixture.json +263 -0
- package/contracts/review-integration/v1/fixtures/capabilities-v1.3.fixture.json +301 -0
- package/contracts/review-integration/v1/fixtures/capabilities-v1.4.fixture.json +312 -0
- package/contracts/review-integration/v1/fixtures/capabilities-v1.5.fixture.json +321 -0
- package/contracts/review-integration/v1/fixtures/consent.fixture.json +36 -0
- package/contracts/review-integration/v1/fixtures/final-verification-incident.fixture.json +1 -0
- package/contracts/review-integration/v1/fixtures/operation.fixture.json +4 -0
- package/contracts/review-integration/v1/fixtures/repair-preflight.fixture.json +54 -0
- package/contracts/review-integration/v1/fixtures/start-v2.fixture.json +97 -0
- package/contracts/review-integration/v1/fixtures/start.fixture.json +25 -1
- package/contracts/review-integration/v1/fixtures/status-ambiguous.fixture.json +4 -0
- package/contracts/review-integration/v1/fixtures/status-corrupted.fixture.json +4 -0
- package/contracts/review-integration/v1/fixtures/status-recover.fixture.json +4 -0
- package/contracts/review-integration/v1/fixtures/status-unrelated.fixture.json +4 -0
- package/contracts/review-integration/v1/fixtures/status-v2-ambiguous.fixture.json +110 -0
- package/contracts/review-integration/v1/fixtures/status-v2-corrupted.fixture.json +107 -0
- package/contracts/review-integration/v1/fixtures/status-v2-final-verification-retry.fixture.json +192 -0
- package/contracts/review-integration/v1/fixtures/status-v2-recover.fixture.json +130 -0
- package/contracts/review-integration/v1/fixtures/status-v2-repair.fixture.json +161 -0
- package/contracts/review-integration/v1/fixtures/status-v2-unrelated.fixture.json +103 -0
- package/contracts/review-integration/v1/fixtures/status-v2.fixture.json +193 -0
- package/contracts/review-integration/v1/fixtures/status.fixture.json +19 -5
- package/contracts/review-integration/v1/fixtures/verification-evidence.fixture.json +1 -0
- package/contracts/review-integration/v1/schemas/admitted-result.schema.json +35 -0
- package/contracts/review-integration/v1/schemas/artifact-subject.schema.json +33 -0
- package/contracts/review-integration/v1/schemas/authority-repair-assessment.schema.json +247 -0
- package/contracts/review-integration/v1/schemas/capabilities-v1.2.schema.json +249 -0
- package/contracts/review-integration/v1/schemas/capabilities-v1.3.schema.json +429 -0
- package/contracts/review-integration/v1/schemas/capabilities-v1.4.schema.json +432 -0
- package/contracts/review-integration/v1/schemas/capabilities-v1.5.schema.json +154 -0
- package/contracts/review-integration/v1/schemas/consent.schema.json +86 -0
- package/contracts/review-integration/v1/schemas/failure.schema.json +54 -8
- package/contracts/review-integration/v1/schemas/final-verification-incident.schema.json +51 -0
- package/contracts/review-integration/v1/schemas/operation.schema.json +204 -31
- package/contracts/review-integration/v1/schemas/repair.schema.json +251 -0
- package/contracts/review-integration/v1/schemas/result-artifact-v2.schema.json +38 -0
- package/contracts/review-integration/v1/schemas/start-v2.schema.json +216 -0
- package/contracts/review-integration/v1/schemas/start.schema.json +79 -3
- package/contracts/review-integration/v1/schemas/status-v2.schema.json +313 -0
- package/contracts/review-integration/v1/schemas/status.schema.json +37 -10
- package/contracts/review-integration/v1/schemas/targeted-validation-request.schema.json +46 -0
- package/contracts/review-integration/v1/schemas/verification-evidence.schema.json +64 -0
- package/contracts/review-integration/v2/fixtures/capabilities.fixture.json +111 -0
- package/contracts/review-integration/v2/fixtures/consent.fixture.json +36 -0
- package/contracts/review-integration/v2/fixtures/start.fixture.json +93 -0
- package/contracts/review-integration/v2/fixtures/status.fixture.json +113 -0
- package/contracts/review-integration/v2/schemas/admitted-result.schema.json +14 -0
- package/contracts/review-integration/v2/schemas/artifact-subject.schema.json +36 -0
- package/contracts/review-integration/v2/schemas/capabilities.schema.json +111 -0
- package/contracts/review-integration/v2/schemas/consent.schema.json +70 -0
- package/contracts/review-integration/v2/schemas/failure.schema.json +35 -0
- package/contracts/review-integration/v2/schemas/operation.schema.json +46 -0
- package/contracts/review-integration/v2/schemas/repair.schema.json +22 -0
- package/contracts/review-integration/v2/schemas/start.schema.json +106 -0
- package/contracts/review-integration/v2/schemas/status.schema.json +104 -0
- package/docs/native-authority-architecture.md +18 -3
- package/docs/review-integration.md +175 -27
- package/extensions/gentle-ai.ts +414 -19
- package/lib/gentle-ai-binary.ts +1 -1
- package/lib/native-review-cli.ts +679 -57
- package/lib/review-candidate-view.ts +131 -1
- package/lib/review-compact-contract.ts +12 -3
- package/lib/review-consent-latch.ts +51 -0
- package/lib/review-correction-lifecycle.ts +170 -0
- package/lib/review-integration-v2.ts +1563 -0
- package/package.json +15 -11
- package/runtime/gentle-ai-binary.mjs +1 -1
- package/runtime/native-review-cli.mjs +663 -41
- package/runtime/review-integration-v2.mjs +1564 -0
- package/scripts/build-git-commit-transaction-runner.mjs +1 -1
- package/scripts/gentle-ai-installer.mjs +16 -11
- package/scripts/install-gentle-ai.mjs +7 -3
- package/scripts/test-packed-runner.mjs +2 -2
- package/scripts/verify-package-files.mjs +216 -64
- package/skills/release/SKILL.md +38 -11
- package/tests/devbinary/native-review-parity.devtest.ts +348 -0
- package/tests/gentle-ai-binary.test.ts +8 -1
- package/tests/gentle-ai-installer.test.ts +24 -21
- package/tests/native-binary-gate.test.ts +145 -0
- package/tests/native-review-capability-contract.test.ts +87 -0
- package/tests/native-review-cli.test.ts +44 -1
- package/tests/native-review-parity-runtime.test.ts +70 -23
- package/tests/native-review-parity.test.ts +673 -0
- package/tests/native-risk-evidence-parity.test.ts +91 -0
- package/tests/package-manifest.test.ts +72 -7
- package/tests/review-actor-tool-deny.test.ts +20 -5
- package/tests/review-candidate-view.test.ts +331 -1
- package/tests/review-compact-contract.test.ts +5 -0
- package/tests/review-consent-latch.test.ts +67 -0
- package/tests/review-controller-native-recovery.test.ts +180 -5
- package/tests/review-controller-native-routing.test.ts +385 -12
- package/tests/review-controller-workspace-root.test.ts +26 -6
- package/tests/review-correction-lifecycle.test.ts +174 -0
- package/tests/review-integration-v2.test.ts +539 -0
- package/tests/runtime-harness.mjs +187 -0
- package/tests/support/native-binary-gate.ts +86 -0
- package/tests/verify-package-files.test.ts +119 -0
- package/lib/review-integration-v1.ts +0 -795
- package/runtime/review-integration-v1.mjs +0 -796
- package/tests/native-review-integration-v1.test.ts +0 -102
- package/tests/review-integration-v1.test.ts +0 -530
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import { readReviewConsentLatch, recordReviewConsentLatch, REVIEW_CONSENT_LATCH_SCHEMA } from "../lib/review-consent-latch.ts";
|
|
8
|
+
|
|
9
|
+
function git(cwd: string, ...args: string[]): string {
|
|
10
|
+
return execFileSync("git", args, { cwd, encoding: "utf8" }).trim();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function repository(t: test.TestContext): string {
|
|
14
|
+
const parent = mkdtempSync(join(tmpdir(), "gentle-pi-review-consent-latch-"));
|
|
15
|
+
const root = join(parent, "repo");
|
|
16
|
+
mkdirSync(root);
|
|
17
|
+
git(root, "init", "-b", "main");
|
|
18
|
+
git(root, "config", "user.email", "tests@example.com");
|
|
19
|
+
git(root, "config", "user.name", "Tests");
|
|
20
|
+
writeFileSync(join(root, "README.md"), "test\n");
|
|
21
|
+
git(root, "add", "README.md");
|
|
22
|
+
git(root, "commit", "-m", "initial");
|
|
23
|
+
t.after(() => rmSync(parent, { recursive: true, force: true }));
|
|
24
|
+
return root;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test("no latch is recorded by default", (t) => {
|
|
28
|
+
const cwd = repository(t);
|
|
29
|
+
assert.equal(readReviewConsentLatch(cwd), false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("recording the latch is one-way: it reads back true, forever, with exact canonical bytes at mode 0600", (t) => {
|
|
33
|
+
const cwd = repository(t);
|
|
34
|
+
recordReviewConsentLatch(cwd);
|
|
35
|
+
assert.equal(readReviewConsentLatch(cwd), true);
|
|
36
|
+
|
|
37
|
+
const commonDir = git(cwd, "rev-parse", "--path-format=absolute", "--git-common-dir");
|
|
38
|
+
const path = join(commonDir, "gentle-pi", "review-consent", "asked.json");
|
|
39
|
+
const bytes = readFileSync(path, "utf8");
|
|
40
|
+
assert.equal(bytes, `{"schema":"${REVIEW_CONSENT_LATCH_SCHEMA}"}\n`);
|
|
41
|
+
assert.equal(REVIEW_CONSENT_LATCH_SCHEMA, "gentle-pi.review-consent-asked/v1");
|
|
42
|
+
if (process.platform !== "win32") assert.equal(statSync(path).mode & 0o777, 0o600);
|
|
43
|
+
|
|
44
|
+
// Idempotent: recording again does not throw and the latch stays set.
|
|
45
|
+
recordReviewConsentLatch(cwd);
|
|
46
|
+
assert.equal(readReviewConsentLatch(cwd), true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("a linked worktree of the same clone shares one latch via the git common directory", (t) => {
|
|
50
|
+
const cwd = repository(t);
|
|
51
|
+
const worktreeParent = mkdtempSync(join(tmpdir(), "gentle-pi-review-consent-latch-worktree-"));
|
|
52
|
+
t.after(() => rmSync(worktreeParent, { recursive: true, force: true }));
|
|
53
|
+
const worktree = join(worktreeParent, "wt");
|
|
54
|
+
git(cwd, "worktree", "add", "-b", "feature", worktree);
|
|
55
|
+
|
|
56
|
+
recordReviewConsentLatch(cwd);
|
|
57
|
+
assert.equal(readReviewConsentLatch(worktree), true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("an unresolvable (non-Git) directory throws rather than silently reporting a latch", () => {
|
|
61
|
+
const outside = mkdtempSync(join(tmpdir(), "gentle-pi-review-consent-latch-not-git-"));
|
|
62
|
+
try {
|
|
63
|
+
assert.throws(() => readReviewConsentLatch(outside));
|
|
64
|
+
} finally {
|
|
65
|
+
rmSync(outside, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
@@ -9,10 +9,16 @@ import {
|
|
|
9
9
|
NATIVE_REVIEW_ERROR_CODE,
|
|
10
10
|
NativeReviewCliError,
|
|
11
11
|
NativeReviewCliV214 as NativeReviewCliV214Production,
|
|
12
|
+
NativeReviewCliV216,
|
|
13
|
+
clearNativeReviewCapabilitiesCacheForTesting,
|
|
12
14
|
nativeReviewLegacyAliasRepairAuthorization,
|
|
13
15
|
type ExecFileAdapter,
|
|
14
16
|
type NativeReviewCli,
|
|
15
17
|
} from "../lib/native-review-cli.ts";
|
|
18
|
+
import { GENTLE_AI_VERSION } from "../lib/gentle-ai-binary.ts";
|
|
19
|
+
|
|
20
|
+
const v2FixtureRoot = join(process.cwd(), "contracts", "review-integration", "v2", "fixtures");
|
|
21
|
+
const v2Fixture = <T = unknown>(name: string): T => JSON.parse(readFileSync(join(v2FixtureRoot, name), "utf8")) as T;
|
|
16
22
|
|
|
17
23
|
// Queued-adapter clients never execute a real process; a fixed absolute
|
|
18
24
|
// package-local path keeps these tests independent of an installed binary.
|
|
@@ -40,7 +46,7 @@ function queuedAdapter(results: QueuedResult[]): { adapter: ExecFileAdapter; cal
|
|
|
40
46
|
|
|
41
47
|
const VERSION_219 = { stdout: "gentle-ai 2.1.9\n" };
|
|
42
48
|
const VERSION_218 = { stdout: "gentle-ai 2.1.8\n" };
|
|
43
|
-
const
|
|
49
|
+
const VERSION_220 = { stdout: "gentle-ai 2.2.0\n" };
|
|
44
50
|
const RECLAIM_RECORD = { schema: "gentle-ai.review-reclaim-audit/v1", lineage: "stuck-lineage", actor: "maintainer", reason: "incomplete entry" };
|
|
45
51
|
const RECOVER_RECORD = { schema: "gentle-ai.review-recovery/v1", predecessor_lineage: "broken", successor_lineage: "successor" };
|
|
46
52
|
const RECONCILE_RECORD = { schema: "gentle-ai.review-reconcile-audit/v1", predecessor_lineage: "predecessor", successor_lineage: "successor", outcome: "quarantined" };
|
|
@@ -253,9 +259,9 @@ test("native v2.1.9 maintenance wrappers use exact argv and published authorizat
|
|
|
253
259
|
]);
|
|
254
260
|
});
|
|
255
261
|
|
|
256
|
-
test("native v2.
|
|
262
|
+
test("native v2.2.0 repair-legacy-alias uses the exact fixed binding and preserves idempotent audit records", async () => {
|
|
257
263
|
const { adapter, calls } = queuedAdapter([
|
|
258
|
-
|
|
264
|
+
VERSION_220,
|
|
259
265
|
{ stdout: JSON.stringify({ operation: "review/repair-legacy-alias", record: LEGACY_ALIAS_RECORD }) },
|
|
260
266
|
]);
|
|
261
267
|
const cli = new NativeReviewCliV214(adapter);
|
|
@@ -282,6 +288,45 @@ test("native v2.1.11 repair-legacy-alias uses the exact fixed binding and preser
|
|
|
282
288
|
]);
|
|
283
289
|
});
|
|
284
290
|
|
|
291
|
+
// Task 11.1 (migrate-review-integration-v2): `repairLegacyAlias` above stays
|
|
292
|
+
// unnegotiated and carries no --contract (asserted at :281-288). The net-new
|
|
293
|
+
// negotiated `repair()` is a DIFFERENT operation and must carry --contract on
|
|
294
|
+
// every invocation, including the capabilities preflight `negotiated()` runs
|
|
295
|
+
// first. A non-eligible preflight assessment lets this test stop after one
|
|
296
|
+
// repair call, without needing a second queued execute-mode response.
|
|
297
|
+
test("negotiated repair carries --contract on every invocation, unlike repair-legacy-alias", async (t) => {
|
|
298
|
+
t.after(() => clearNativeReviewCapabilitiesCacheForTesting());
|
|
299
|
+
const capabilities = v2Fixture<Record<string, unknown>>("capabilities.fixture.json");
|
|
300
|
+
const executableDigest = "dcc846103b16d365eaeeb9d7f289c23fc4f2897f23def1cb3fe7f05557b64705";
|
|
301
|
+
const capabilitiesBody = { ...capabilities, package: { ...(capabilities.package as Record<string, unknown>), version: GENTLE_AI_VERSION } };
|
|
302
|
+
const preflightResult = {
|
|
303
|
+
schema: "gentle-ai.review-integration.repair/v2",
|
|
304
|
+
contract: "gentle-ai.review-integration/v2",
|
|
305
|
+
operation: "review.repair",
|
|
306
|
+
mode: "preflight",
|
|
307
|
+
assessment: {
|
|
308
|
+
schema: "gentle-ai.review-authority-repair-assessment/v1",
|
|
309
|
+
status: "unsupported",
|
|
310
|
+
counts: { lineages: 0, compact_lineages: 0, legacy_lineages: 0, events: 0, bytes: 0, eligible_candidates: 0, unsupported_lineages: 0, conflicts: 0 },
|
|
311
|
+
supported_operations: ["review/complete-fix", "review/validate-fix"],
|
|
312
|
+
authorization_schema: "gentle-ai.review-repair-authorization/v1",
|
|
313
|
+
},
|
|
314
|
+
required_inputs: [],
|
|
315
|
+
};
|
|
316
|
+
const { adapter, calls } = queuedAdapter([
|
|
317
|
+
{ stdout: JSON.stringify(capabilitiesBody) },
|
|
318
|
+
{ stdout: JSON.stringify(preflightResult) },
|
|
319
|
+
]);
|
|
320
|
+
const cli = new NativeReviewCliV216(adapter, "/package/.gentle-ai/gentle-ai", 30_000, 1024 * 1024, async () => undefined, () => executableDigest);
|
|
321
|
+
const result = await cli.repair!({ cwd: "/repo", actor: "maintainer", reason: "quarantine approved historical alias", maintainerAuthorization: "irrelevant-for-non-eligible-preflight" });
|
|
322
|
+
assert.equal(result.mode, "preflight");
|
|
323
|
+
assert.equal(result.assessment.status, "unsupported");
|
|
324
|
+
assert.equal(calls.length, 2, "a non-eligible preflight must never issue an execute-mode call");
|
|
325
|
+
assert.deepEqual(calls[0]?.arguments, ["review", "capabilities", "--contract", "gentle-ai.review-integration/v2"]);
|
|
326
|
+
assert.deepEqual(calls[1]?.arguments, ["review", "repair", "--contract", "gentle-ai.review-integration/v2", "--cwd", "/repo", "--mode", "preflight"]);
|
|
327
|
+
for (const call of calls) assert.ok(call.arguments.includes("--contract"), "every negotiated repair() invocation must carry --contract");
|
|
328
|
+
});
|
|
329
|
+
|
|
285
330
|
test("native repair-legacy-alias fails closed for stale bindings, malformed output, cancellation, and partial failure", async () => {
|
|
286
331
|
const request = {
|
|
287
332
|
cwd: "/repo", repository: "/repo", lineage: "legacy-alias", expectedRevision: `sha256:${"c".repeat(64)}`,
|
|
@@ -294,7 +339,7 @@ test("native repair-legacy-alias fails closed for stale bindings, malformed outp
|
|
|
294
339
|
{ stdout: JSON.stringify({ operation: "review/repair-legacy-alias" }) },
|
|
295
340
|
{ stdout: JSON.stringify({ operation: "review/repair-legacy-alias", record: LEGACY_ALIAS_RECORD }), stderr: "interrupted", exitCode: 1 },
|
|
296
341
|
]) {
|
|
297
|
-
const queue = queuedAdapter([
|
|
342
|
+
const queue = queuedAdapter([VERSION_220, result]);
|
|
298
343
|
await assert.rejects(
|
|
299
344
|
() => new NativeReviewCliV214(queue.adapter).repairLegacyAlias!(request),
|
|
300
345
|
(error: unknown) => error instanceof NativeReviewCliError
|
|
@@ -784,3 +829,133 @@ test("REPAIR_LEGACY_ALIAS derives fixed inputs from fresh inventory and requires
|
|
|
784
829
|
assert.equal(injected.outcome, "native-input-invalid");
|
|
785
830
|
await assert.rejects(runControllerOperation({ operation: "dispose-result", input: "{}" }, native), /operation/);
|
|
786
831
|
});
|
|
832
|
+
|
|
833
|
+
// The capture-result gap, found by benchmarking Pi's client against the real
|
|
834
|
+
// binary: `finalize()` emitted `--result <file>` per lens, a flag gentle-ai
|
|
835
|
+
// retired because "a reviewer result supplied this way carries no
|
|
836
|
+
// provider-owned admission, so it cannot prove the lens inspected the frozen
|
|
837
|
+
// candidate". There was no capture-result surface at all, so Pi could only
|
|
838
|
+
// finalize a zero-lens low-risk candidate. The suite never caught it because
|
|
839
|
+
// it mocks the finalize response.
|
|
840
|
+
//
|
|
841
|
+
// Two inverse contracts meet here. `repair()` above MUST carry --contract.
|
|
842
|
+
// `capture-result` MUST NOT: it is an additive headless command, not a
|
|
843
|
+
// negotiated repository operation, and the provider's own tokens already
|
|
844
|
+
// carry the repository context -- it accepts that or --cwd, never both. So
|
|
845
|
+
// Pi passes the transition's tokens through verbatim and adds only --input.
|
|
846
|
+
test("captureResult passes the provider tokens through verbatim and carries no --contract", async (t) => {
|
|
847
|
+
t.after(() => clearNativeReviewCapabilitiesCacheForTesting());
|
|
848
|
+
const manifest = {
|
|
849
|
+
schema: "gentle-ai.review-result-artifact/v2",
|
|
850
|
+
capability: "review.native_result_artifact",
|
|
851
|
+
subject_hash: "sha256:" + "a".repeat(64),
|
|
852
|
+
admission_decision: "completed",
|
|
853
|
+
lens: "review-reliability",
|
|
854
|
+
reference: "rref1_" + "b".repeat(64),
|
|
855
|
+
};
|
|
856
|
+
const { adapter, calls } = queuedAdapter([{ stdout: JSON.stringify(manifest) }]);
|
|
857
|
+
const cli = new NativeReviewCliV216(adapter, "/package/.gentle-ai/gentle-ai", 30_000, 1024 * 1024, async () => undefined, () => "dcc846103b16d365eaeeb9d7f289c23fc4f2897f23def1cb3fe7f05557b64705");
|
|
858
|
+
|
|
859
|
+
const tokens = [
|
|
860
|
+
"--lineage=review-1d5aadacc600e167",
|
|
861
|
+
"--expected-revision=sha256:" + "c".repeat(64),
|
|
862
|
+
"--target=sha256:" + "d".repeat(64),
|
|
863
|
+
"--repository-context=rctx1_" + "e".repeat(64),
|
|
864
|
+
"--lens=review-reliability",
|
|
865
|
+
"--order=0",
|
|
866
|
+
"--subject-hash=" + manifest.subject_hash,
|
|
867
|
+
];
|
|
868
|
+
const captured = await cli.captureResult({ argumentTokens: tokens, resultDocument: JSON.stringify({ subject_hash: manifest.subject_hash, inspection: { status: "completed", paths: ["a.ts"] }, findings: [], evidence: ["reviewed the complete frozen candidate scope"] }) });
|
|
869
|
+
|
|
870
|
+
assert.equal(captured.subjectHash, manifest.subject_hash);
|
|
871
|
+
assert.equal(captured.admissionDecision, "completed");
|
|
872
|
+
|
|
873
|
+
// Exactly one invocation: capture-result is headless and never negotiates,
|
|
874
|
+
// so it must not drag a capabilities preflight along with it.
|
|
875
|
+
assert.equal(calls.length, 1);
|
|
876
|
+
const argv = calls[0]!.arguments;
|
|
877
|
+
assert.deepEqual(argv.slice(0, 2), ["review", "capture-result"]);
|
|
878
|
+
assert.equal(argv.includes("--contract"), false, "capture-result accepts no --contract");
|
|
879
|
+
assert.equal(argv.includes("--cwd"), false, "the provider tokens already carry the repository context");
|
|
880
|
+
// Tokens pass through in order, untouched, and --input is the only addition.
|
|
881
|
+
assert.deepEqual(argv.slice(2, 2 + tokens.length), tokens);
|
|
882
|
+
assert.equal(argv.at(-2), "--input");
|
|
883
|
+
assert.match(argv.at(-1) as string, /\S/);
|
|
884
|
+
assert.equal(argv.length, 2 + tokens.length + 2);
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
test("captureEvidence stages exact bytes, uses the closed outcome argv, and decodes the native record", async (t) => {
|
|
888
|
+
t.after(() => clearNativeReviewCapabilitiesCacheForTesting());
|
|
889
|
+
const capabilities = v2Fixture<Record<string, unknown>>("capabilities.fixture.json");
|
|
890
|
+
const capabilitiesBody = { ...capabilities, package: { ...(capabilities.package as Record<string, unknown>), version: GENTLE_AI_VERSION } };
|
|
891
|
+
const record = {
|
|
892
|
+
schema: "gentle-ai.review-verification-evidence/v2",
|
|
893
|
+
version: 2,
|
|
894
|
+
lineage_id: "review-evidence-lineage",
|
|
895
|
+
authority_revision: `sha256:${"a".repeat(64)}`,
|
|
896
|
+
target_identity: `sha256:${"b".repeat(64)}`,
|
|
897
|
+
candidate_tree: "c".repeat(40),
|
|
898
|
+
paths_digest: `sha256:${"d".repeat(64)}`,
|
|
899
|
+
paths: ["app.ts"],
|
|
900
|
+
ledger_ids: [],
|
|
901
|
+
raw_payload_sha256: `sha256:${"e".repeat(64)}`,
|
|
902
|
+
raw_payload_bytes: 24,
|
|
903
|
+
outcome: "verification_failed",
|
|
904
|
+
record_digest: `sha256:${"f".repeat(64)}`,
|
|
905
|
+
};
|
|
906
|
+
let staged = "";
|
|
907
|
+
const calls: Array<{ arguments: readonly string[] }> = [];
|
|
908
|
+
const cli = new NativeReviewCliV216(async (request) => {
|
|
909
|
+
calls.push({ arguments: request.arguments });
|
|
910
|
+
if (request.arguments[1] === "capabilities") return { stdout: JSON.stringify(capabilitiesBody), stderr: "", exitCode: 0, signal: null, timedOut: false, outputLimitExceeded: false };
|
|
911
|
+
const inputIndex = request.arguments.indexOf("--input");
|
|
912
|
+
assert.ok(inputIndex >= 0);
|
|
913
|
+
staged = readFileSync(request.arguments[inputIndex + 1]!, "utf8");
|
|
914
|
+
return { stdout: JSON.stringify(record), stderr: "", exitCode: 0, signal: null, timedOut: false, outputLimitExceeded: false };
|
|
915
|
+
}, "/package/.gentle-ai/gentle-ai", 30_000, 1024 * 1024, async () => undefined, () => "dcc846103b16d365eaeeb9d7f289c23fc4f2897f23def1cb3fe7f05557b64705");
|
|
916
|
+
const evidence = "focused verification failed\n";
|
|
917
|
+
const captured = await cli.captureEvidence({
|
|
918
|
+
cwd: "/repo",
|
|
919
|
+
lineageId: record.lineage_id,
|
|
920
|
+
targetIdentity: record.target_identity,
|
|
921
|
+
expectedRevision: record.authority_revision,
|
|
922
|
+
outcome: "verification_failed",
|
|
923
|
+
evidenceDocument: evidence,
|
|
924
|
+
});
|
|
925
|
+
assert.equal(staged, evidence);
|
|
926
|
+
assert.equal(captured.recordDigest, record.record_digest);
|
|
927
|
+
assert.equal(captured.outcome, "verification_failed");
|
|
928
|
+
const argv = calls[1]!.arguments;
|
|
929
|
+
assert.deepEqual(argv.slice(0, 2), ["review", "capture-evidence"]);
|
|
930
|
+
assert.equal(argv.includes("--contract"), false);
|
|
931
|
+
assert.equal(argv[argv.indexOf("--outcome") + 1], "verification_failed");
|
|
932
|
+
await assert.rejects(
|
|
933
|
+
cli.captureEvidence({ cwd: "/repo", lineageId: record.lineage_id, targetIdentity: record.target_identity, expectedRevision: record.authority_revision, outcome: "failed" as never, evidenceDocument: evidence }),
|
|
934
|
+
/outcome must be passed, verification_failed, or procedural_tooling_failed/,
|
|
935
|
+
);
|
|
936
|
+
assert.equal(calls.length, 2, "outside-domain outcomes must fail before another native launch");
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
test("negotiated finalize never emits the retired --result flag", async (t) => {
|
|
940
|
+
t.after(() => clearNativeReviewCapabilitiesCacheForTesting());
|
|
941
|
+
const capabilities = v2Fixture<Record<string, unknown>>("capabilities.fixture.json");
|
|
942
|
+
const capabilitiesBody = { ...capabilities, package: { ...(capabilities.package as Record<string, unknown>), version: GENTLE_AI_VERSION } };
|
|
943
|
+
const finalizeBody = {
|
|
944
|
+
schema: "gentle-ai.review-integration.operation/v2",
|
|
945
|
+
contract: "gentle-ai.review-integration/v2",
|
|
946
|
+
operation: "review.finalize",
|
|
947
|
+
result: { operation: "review/finalize", lineage_id: "review-1d5aadacc600e167", state: "approved", action: "validate delivery", store_revision: "sha256:" + "f".repeat(64) },
|
|
948
|
+
};
|
|
949
|
+
const { adapter, calls } = queuedAdapter([
|
|
950
|
+
{ stdout: JSON.stringify(capabilitiesBody) },
|
|
951
|
+
{ stdout: JSON.stringify(finalizeBody) },
|
|
952
|
+
]);
|
|
953
|
+
const cli = new NativeReviewCliV216(adapter, "/package/.gentle-ai/gentle-ai", 30_000, 1024 * 1024, async () => undefined, () => "dcc846103b16d365eaeeb9d7f289c23fc4f2897f23def1cb3fe7f05557b64705");
|
|
954
|
+
|
|
955
|
+
await cli.finalize({ cwd: "/repo", lineageId: "review-1d5aadacc600e167", capturedResults: true });
|
|
956
|
+
|
|
957
|
+
const argv = calls[1]!.arguments;
|
|
958
|
+
assert.equal(argv.includes("--result"), false, "--result is retired; results reach authority through capture-result");
|
|
959
|
+
assert.ok(argv.some((token) => token === "--captured-results" || token.startsWith("--captured-results=")), "finalize must tell the provider to discover the captured results");
|
|
960
|
+
assert.ok(argv.includes("--contract"), "finalize IS negotiated, unlike capture-result");
|
|
961
|
+
});
|