gentle-pi 2.1.2 → 2.2.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 +70 -12
- package/assets/agents/gentle-ai-worker.md +7 -3
- package/assets/agents/jd-fix-agent.md +1 -1
- package/assets/agents/jd-judge-a.md +3 -1
- package/assets/agents/jd-judge-b.md +3 -1
- package/assets/agents/review-readability.md +4 -1
- package/assets/agents/review-reliability.md +4 -1
- package/assets/agents/review-resilience.md +4 -1
- package/assets/agents/review-risk.md +4 -1
- package/assets/agents/sdd-apply.md +6 -1
- package/assets/agents/sdd-archive.md +6 -1
- package/assets/agents/sdd-design.md +6 -1
- package/assets/agents/sdd-explore.md +6 -2
- package/assets/agents/sdd-init.md +10 -2
- package/assets/agents/sdd-onboard.md +6 -1
- package/assets/agents/sdd-proposal.md +6 -1
- package/assets/agents/sdd-spec.md +6 -1
- package/assets/agents/sdd-status.md +6 -1
- package/assets/agents/sdd-sync.md +6 -1
- package/assets/agents/sdd-tasks.md +6 -1
- package/assets/agents/sdd-verify.md +6 -1
- package/assets/chains/4r-review.chain.md +2 -0
- package/assets/chains/sdd-full.chain.md +1 -1
- package/assets/chains/sdd-plan.chain.md +1 -1
- package/assets/chains/sdd-verify.chain.md +1 -1
- package/assets/orchestrator-delegation.md +246 -67
- package/assets/orchestrator.md +7 -14
- package/assets/sdd-orchestrator-workflow.md +154 -9
- package/assets/support/sdd-status-contract.md +19 -1
- package/contracts/review-integration/v1/fixtures/consent.fixture.json +3 -3
- package/contracts/review-integration/v1/fixtures/start-v2.fixture.json +19 -28
- package/contracts/review-integration/v1/fixtures/start.fixture.json +1 -10
- package/contracts/review-integration/v1/fixtures/status-v2.fixture.json +12 -21
- package/contracts/review-integration/v1/schemas/correction-plan-request.schema.json +49 -0
- package/contracts/review-integration/v1/schemas/operation.schema.json +76 -0
- package/contracts/review-integration/v1/schemas/repair.schema.json +39 -0
- package/contracts/review-integration/v1/schemas/status-v2.schema.json +4 -2
- package/contracts/review-integration/v1/schemas/status.schema.json +4 -2
- package/contracts/review-integration/v2/fixtures/consent.fixture.json +1 -1
- package/contracts/review-integration/v2/fixtures/start.fixture.json +1 -10
- package/contracts/review-integration/v2/fixtures/status.fixture.json +1 -10
- package/contracts/review-integration/v2/schemas/failure.schema.json +5 -1
- package/contracts/review-integration/v2/schemas/operation.schema.json +6 -1
- package/contracts/review-integration/v2/schemas/repair.schema.json +4 -2
- package/contracts/review-integration/v2/schemas/start.schema.json +5 -2
- package/contracts/review-integration/v2/schemas/status.schema.json +4 -2
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +30 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/README.md +12 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/manifest.json +65 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/lens.schema.json +16 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/refuter.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/targeted-validator.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/lens.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/refuter.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/targeted-validator.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-capabilities.baseline.json +15 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-roles.baseline.json +42 -0
- package/docs/native-authority-architecture.md +5 -5
- package/docs/review-integration.md +22 -2
- package/extensions/gentle-ai.ts +1595 -201
- package/extensions/sdd-init.ts +19 -6
- package/extensions/skill-registry.ts +10 -2
- package/extensions/startup-banner.ts +10 -4
- package/lib/gentle-ai-binary.ts +173 -2
- package/lib/git-commit-transaction.ts +77 -17
- package/lib/native-review-cli.ts +528 -65
- package/lib/provider-contract-bundle.ts +704 -0
- package/lib/review-candidate-view.ts +527 -18
- package/lib/review-compact-contract.ts +59 -248
- package/lib/review-host-relay.ts +436 -0
- package/lib/review-integration-v2.ts +537 -36
- package/lib/review-relay-contract.ts +16 -0
- package/lib/sdd-preflight.ts +53 -1
- package/package.json +5 -2
- package/runtime/gentle-ai-binary.mjs +173 -2
- package/runtime/git-commit-transaction.mjs +75 -15
- package/runtime/native-review-cli.mjs +524 -61
- package/runtime/review-integration-v2.mjs +536 -35
- package/runtime/review-relay-contract.mjs +17 -0
- package/scripts/build-git-commit-transaction-runner.mjs +1 -0
- package/scripts/check-provider-contract.mjs +138 -0
- package/scripts/gentle-ai-installer.mjs +23 -13
- package/scripts/maintainer/provider-relay-matrix.mjs +219 -0
- package/scripts/mirror-provider-contract.mjs +143 -0
- package/scripts/test-packed-runner.mjs +16 -2
- package/scripts/verify-package-files.mjs +110 -33
- package/skills/_shared/review-ledger-contract.md +4 -6
- package/skills/gentle-ai/SKILL.md +4 -4
- package/skills/issue-creation/SKILL.md +94 -168
- package/skills/judgment-day/SKILL.md +7 -1
- package/skills/judgment-day/references/prompts-and-formats.md +2 -0
- package/skills/rdd-defect-workflow/SKILL.md +54 -0
- package/tests/background-subagents.test.ts +771 -0
- package/tests/crosslane/cross-lane.mjs +1169 -0
- package/tests/delegated-key-learnings-contract.test.ts +238 -0
- package/tests/fixtures/devbinary/capabilities-v2.1.derived.json +331 -0
- package/tests/fixtures/devbinary/capabilities-v2.2.captured.json +340 -0
- package/tests/fixtures/devbinary/consent-v3.captured.json +37 -0
- package/tests/fixtures/devbinary/failure-v2-capture-evidence.captured.json +16 -0
- package/tests/fixtures/devbinary/result-artifact-v2-path.captured.json +12 -0
- package/tests/fixtures/devbinary/result-artifact-v2.captured.json +12 -0
- package/tests/fixtures/devbinary/start-v3-consent-declined.captured.json +19 -0
- package/tests/fixtures/devbinary/start-v3-consent-granted.captured.json +109 -0
- package/tests/fixtures/devbinary/status-v5-capture-result-submission.captured.json +184 -0
- package/tests/fixtures/devbinary/status-v5-repository-context.captured.json +138 -0
- package/tests/fixtures/devbinary/status-v5.captured.json +88 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/README.md +12 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/manifest.json +65 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/targeted-validator.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/targeted-validator.json +1 -0
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +195 -0
- package/tests/gentle-ai-dev-binary.test.ts +336 -0
- package/tests/gentle-ai-installer.test.ts +46 -46
- package/tests/git-commit-transaction.test.ts +229 -1
- package/tests/maintainer/provider-relay.maintest.ts +265 -0
- package/tests/native-review-capability-contract.test.ts +48 -2
- package/tests/native-review-cli.test.ts +56 -0
- package/tests/native-review-consent.test.ts +164 -3
- package/tests/native-review-parity-runtime.test.ts +37 -0
- package/tests/native-review-parity.test.ts +218 -15
- package/tests/native-sdd-attempt-authority.test.ts +235 -0
- package/tests/orchestrator-budget.test.ts +30 -5
- package/tests/package-manifest.test.ts +98 -72
- package/tests/provider-contract-bundle.test.ts +385 -0
- package/tests/provider-contract-mirror.test.ts +206 -0
- package/tests/provider-defect-handoff.test.ts +355 -0
- package/tests/review-actor-tool-deny.test.ts +12 -13
- package/tests/review-candidate-view.test.ts +489 -9
- package/tests/review-compact-contract.test.ts +52 -119
- package/tests/review-controller-native-recovery.test.ts +643 -47
- package/tests/review-controller-native-routing.test.ts +1667 -222
- package/tests/review-controller-workspace-root.test.ts +17 -2
- package/tests/review-corrected-finalize-binding.test.ts +175 -0
- package/tests/review-dispatch-hydration-gap.test.ts +197 -0
- package/tests/review-host-relay-routing.test.ts +317 -0
- package/tests/review-host-relay.test.ts +520 -0
- package/tests/review-integration-v2-forward.test.ts +631 -0
- package/tests/review-integration-v2.test.ts +114 -0
- package/tests/review-ledger-contract.test.ts +12 -28
- package/tests/review-recovered-lineage-routing.test.ts +246 -0
- package/tests/review-relay-transport-agent.test.ts +249 -0
- package/tests/runtime-harness.mjs +242 -14
- package/tests/sdd-agent-tools.test.ts +18 -33
- package/tests/skill-collision-prefixes.test.ts +1 -0
- package/tests/skill-registry.test.ts +50 -1
- package/tests/verify-package-files.test.ts +62 -0
- package/assets/agents/review-refuter.md +0 -40
- package/assets/agents/review-validator.md +0 -23
- package/lib/native-review-remediation.ts +0 -49
- package/lib/review-compact.ts +0 -947
- package/lib/review-refuter-adapter.ts +0 -129
- package/lib/review-runtime-contract.ts +0 -68
- package/prompts/gcl.md +0 -54
- package/prompts/gis.md +0 -25
- package/prompts/gpr.md +0 -41
- package/prompts/gwr.md +0 -31
- package/tests/fixtures/native-review-cli/v2.1.2/bind-sdd.json +0 -25
- package/tests/fixtures/native-review-cli/v2.1.2/finalize.json +0 -8
- package/tests/fixtures/native-review-cli/v2.1.2/sdd-status-engram.json +0 -139
- package/tests/fixtures/native-review-cli/v2.1.2/sdd-status.json +0 -200
- package/tests/fixtures/native-review-cli/v2.1.2/start.json +0 -12
- package/tests/fixtures/native-review-cli/v2.1.2/validate-allow.json +0 -24
- package/tests/fixtures/native-review-cli/v2.1.2/validate-deny-empty-context.json +0 -20
- package/tests/fixtures/native-review-cli/v2.1.2/validate-deny.json +0 -28
- package/tests/review-compact.test.ts +0 -243
- package/tests/review-refuter-adapter.test.ts +0 -89
|
@@ -245,6 +245,50 @@ test("capabilities enforce the exact mandatory feature set while accepting a sup
|
|
|
245
245
|
const extraOperation = clone(source);
|
|
246
246
|
(extraOperation.operations as string[]).push("review.future_operation");
|
|
247
247
|
assert.doesNotThrow(() => decode(extraOperation));
|
|
248
|
+
|
|
249
|
+
// mandatory features must stay an exact match: a known-but-optional feature
|
|
250
|
+
// name added to the mandatory list must still be rejected. This is the
|
|
251
|
+
// contract boundary (unknown_mandatory: "reject") and must not regress when
|
|
252
|
+
// gates/projections become additive-tolerant below.
|
|
253
|
+
const extraMandatoryUnknownAddition = clone(source);
|
|
254
|
+
((extraMandatoryUnknownAddition.features as JsonObject).mandatory as JsonObject[]).push({ name: "bounded_process_waits", supported: true, requires: [] });
|
|
255
|
+
assert.throws(() => decode(extraMandatoryUnknownAddition), /mandatory/);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("capabilities gates and projections accept an additive superset beyond the required floor", () => {
|
|
259
|
+
const source = fixture<JsonObject>("capabilities.fixture.json");
|
|
260
|
+
const decode = (value: unknown) => decodeReviewCapabilitiesV2(value, executableDigest);
|
|
261
|
+
|
|
262
|
+
const extraGate = clone(source);
|
|
263
|
+
(extraGate.gates as string[]).push("future-gate");
|
|
264
|
+
const decodedExtraGate = decode(extraGate) as { gates: ReadonlySet<string> };
|
|
265
|
+
assert.equal(decodedExtraGate.gates.has("future-gate"), false, "an unknown advertised gate must not leak into internal use");
|
|
266
|
+
for (const gate of ["post-apply", "pre-commit", "pre-push", "pre-pr", "release"]) {
|
|
267
|
+
assert.equal(decodedExtraGate.gates.has(gate), true, gate);
|
|
268
|
+
}
|
|
269
|
+
assert.equal(decodedExtraGate.gates.size, 5);
|
|
270
|
+
|
|
271
|
+
const extraProjection = clone(source);
|
|
272
|
+
(extraProjection.projections as string[]).push("future-projection");
|
|
273
|
+
const decodedExtraProjection = decode(extraProjection) as { projections: ReadonlySet<string> };
|
|
274
|
+
assert.equal(decodedExtraProjection.projections.has("future-projection"), false, "an unknown advertised projection must not leak into internal use");
|
|
275
|
+
for (const projection of ["staged", "workspace"]) {
|
|
276
|
+
assert.equal(decodedExtraProjection.projections.has(projection), true, projection);
|
|
277
|
+
}
|
|
278
|
+
assert.equal(decodedExtraProjection.projections.size, 2);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("capabilities gates and projections still enforce the required floor", () => {
|
|
282
|
+
const source = fixture<JsonObject>("capabilities.fixture.json");
|
|
283
|
+
const decode = (value: unknown) => decodeReviewCapabilitiesV2(value, executableDigest);
|
|
284
|
+
|
|
285
|
+
const missingGate = clone(source);
|
|
286
|
+
missingGate.gates = (missingGate.gates as string[]).filter((gate) => gate !== "release");
|
|
287
|
+
assert.throws(() => decode(missingGate), /gates/);
|
|
288
|
+
|
|
289
|
+
const missingProjection = clone(source);
|
|
290
|
+
missingProjection.projections = (missingProjection.projections as string[]).filter((projection) => projection !== "workspace");
|
|
291
|
+
assert.throws(() => decode(missingProjection), /projections/);
|
|
248
292
|
});
|
|
249
293
|
|
|
250
294
|
test("START independently binds base/candidate tree and the target-mode overlay pair", () => {
|
|
@@ -546,3 +590,73 @@ test("next_transition.execute.binding stays open, as its schema declares no prop
|
|
|
546
590
|
|
|
547
591
|
assert.doesNotThrow(() => decodeReviewNextTransitionV3(withContext));
|
|
548
592
|
});
|
|
593
|
+
|
|
594
|
+
// gentle-pi#311 P4-roles: the two Go-owned non-lens provider role capture
|
|
595
|
+
// operations render SELF-CONTAINED vectors (binding tokens + --agent=pi
|
|
596
|
+
// --execute=true). Their schemas are pinned, capture-validation carries the
|
|
597
|
+
// frozen validation request, and a submission descriptor on either one is a
|
|
598
|
+
// contract violation (it would hand the caller a way to author the verdict).
|
|
599
|
+
test("next_transition decodes the self-contained provider role capture vectors strictly", () => {
|
|
600
|
+
const tree = "b".repeat(40);
|
|
601
|
+
const validationRequest = {
|
|
602
|
+
schema: "gentle-ai.review-targeted-validation-request/v1",
|
|
603
|
+
request_hash: digest,
|
|
604
|
+
lineage_id: "review-fixture",
|
|
605
|
+
expected_revision: digest,
|
|
606
|
+
target_identity: digest,
|
|
607
|
+
fix_finding_ids: ["RISK-001"],
|
|
608
|
+
projection: "workspace",
|
|
609
|
+
correction_candidate_tree: tree,
|
|
610
|
+
correction_target_identity: digest,
|
|
611
|
+
correction_paths: ["lib/a.ts"],
|
|
612
|
+
correction_paths_digest: digest,
|
|
613
|
+
};
|
|
614
|
+
const roleInput = (name: string, captureOperation: string, schema: string, extra: Record<string, unknown> = {}) => ({
|
|
615
|
+
kind: "collect",
|
|
616
|
+
reason_code: "provider_refuter_required",
|
|
617
|
+
collect: { inputs: [{
|
|
618
|
+
name,
|
|
619
|
+
schema,
|
|
620
|
+
capture_operation: captureOperation,
|
|
621
|
+
arguments: [
|
|
622
|
+
{ name: "lineage", value: "review-fixture", token: "--lineage=review-fixture" },
|
|
623
|
+
{ name: "expected-revision", value: digest, token: `--expected-revision=${digest}` },
|
|
624
|
+
{ name: "target", value: digest, token: `--target=${digest}` },
|
|
625
|
+
{ name: "repository-context", value: `rctx1_${"c".repeat(64)}`, token: `--repository-context=rctx1_${"c".repeat(64)}` },
|
|
626
|
+
{ name: "agent", value: "pi", token: "--agent=pi" },
|
|
627
|
+
{ name: "execute", value: "true", token: "--execute=true" },
|
|
628
|
+
],
|
|
629
|
+
...extra,
|
|
630
|
+
}] },
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
const refuter = roleInput("provider_refuter", "review.capture-refuter", "https://gentle-ai.dev/schema/review/refuter/v1");
|
|
634
|
+
const decodedRefuter = decodeReviewNextTransitionV3(refuter);
|
|
635
|
+
assert.equal(decodedRefuter.collect?.inputs[0]?.captureOperation, "review.capture-refuter");
|
|
636
|
+
assert.equal(decodedRefuter.collect?.inputs[0]?.arguments.at(-1)?.token, "--execute=true");
|
|
637
|
+
|
|
638
|
+
assert.throws(
|
|
639
|
+
() => decodeReviewNextTransitionV3(roleInput("provider_refuter", "review.capture-refuter", "https://gentle-ai.dev/schema/review/reviewer/v1")),
|
|
640
|
+
/schema must be https:\/\/gentle-ai\.dev\/schema\/review\/refuter\/v1/,
|
|
641
|
+
);
|
|
642
|
+
assert.throws(
|
|
643
|
+
() => decodeReviewNextTransitionV3(roleInput("provider_refuter", "review.capture-refuter", "https://gentle-ai.dev/schema/review/refuter/v1", {
|
|
644
|
+
submission: { operation_token: "capture-refuter", argument_tokens: ["--input={{value}}"], values: [{ slot: "{{value}}", domain: "artifact-path", substitution_location: 0 }] },
|
|
645
|
+
})),
|
|
646
|
+
/submission is not allowed on the self-contained/,
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
const validator = roleInput("provider_targeted_validator", "review.capture-validation", "https://gentle-ai.dev/schema/review/validator/v1", { validation_request: validationRequest });
|
|
650
|
+
const decodedValidator = decodeReviewNextTransitionV3(validator);
|
|
651
|
+
assert.equal(decodedValidator.collect?.inputs[0]?.captureOperation, "review.capture-validation");
|
|
652
|
+
assert.equal(decodedValidator.collect?.inputs[0]?.validationRequest?.requestHash, digest);
|
|
653
|
+
|
|
654
|
+
assert.throws(
|
|
655
|
+
() => decodeReviewNextTransitionV3(roleInput("provider_targeted_validator", "review.capture-validation", "https://gentle-ai.dev/schema/review/validator/v1")),
|
|
656
|
+
/validation_request is required/,
|
|
657
|
+
);
|
|
658
|
+
assert.throws(
|
|
659
|
+
() => decodeReviewNextTransitionV3(roleInput("provider_targeted_validator", "review.capture-validation", "https://gentle-ai.dev/schema/review/refuter/v1", { validation_request: validationRequest })),
|
|
660
|
+
/schema must be https:\/\/gentle-ai\.dev\/schema\/review\/validator\/v1/,
|
|
661
|
+
);
|
|
662
|
+
});
|
|
@@ -14,8 +14,6 @@ const REVIEW_LENSES = [
|
|
|
14
14
|
"assets/agents/review-reliability.md",
|
|
15
15
|
] as const;
|
|
16
16
|
const JUDGES = ["assets/agents/jd-judge-a.md", "assets/agents/jd-judge-b.md"] as const;
|
|
17
|
-
const REFUTER = "assets/agents/review-refuter.md";
|
|
18
|
-
const VALIDATOR = "assets/agents/review-validator.md";
|
|
19
17
|
const FIX_AGENT = "assets/agents/jd-fix-agent.md";
|
|
20
18
|
const JD_SKILL = "skills/judgment-day/SKILL.md";
|
|
21
19
|
const JD_PROMPTS = "skills/judgment-day/references/prompts-and-formats.md";
|
|
@@ -108,7 +106,7 @@ test("canonical contract defines compact risk, causal admission, correction, CAS
|
|
|
108
106
|
/Judgment Day remains mutable on graph-v1/i,
|
|
109
107
|
/reloads authority and re-derives target\/publication evidence before allow/i,
|
|
110
108
|
/one one-shot authorization for the exact subsequent command/i,
|
|
111
|
-
/gentle-ai\.review-integration\/
|
|
109
|
+
/Native validation uses `gentle-ai\.review-integration\/v2`/i,
|
|
112
110
|
/durable hook\/native-validation transaction/i,
|
|
113
111
|
/Pi-owned `review-publication-gate` module isolates command projection and publication revalidation/i,
|
|
114
112
|
/local orchestrator and same-user process are trusted/i,
|
|
@@ -196,29 +194,15 @@ test("risk lens distinguishes trusted orchestration from concrete boundary bypas
|
|
|
196
194
|
assert.match(content, /untrusted repository content.*malformed inputs.*stale authority.*path drift.*external callers/i);
|
|
197
195
|
});
|
|
198
196
|
|
|
199
|
-
test("
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
/Do not create findings, alter frozen claims, request fixes, launch actors, persist authority, or repeat/,
|
|
209
|
-
]);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
test("targeted validator checks only original criteria and correction regression without expanding scope", () => {
|
|
213
|
-
const content = read(VALIDATOR);
|
|
214
|
-
assertMatches(VALIDATOR, content, [
|
|
215
|
-
/original-criteria proof/,
|
|
216
|
-
/correction-regression proof/,
|
|
217
|
-
/Validate the original criteria and correction regression only/,
|
|
218
|
-
/Never expand paths, IDs, untracked scope, acceptance criteria, or correction purpose/,
|
|
219
|
-
/empty `fix_caused_findings` array/,
|
|
220
|
-
/Do not request another fix or attempt, launch actors, persist authority, or repeat yourself/,
|
|
221
|
-
]);
|
|
197
|
+
test("the Pi-owned adversarial role agents are retired: roles execute through Go-owned pi processes", () => {
|
|
198
|
+
// gentle-pi#311 P5: the refuter and targeted validator are no longer
|
|
199
|
+
// Pi-authored actors. The provider renders self-contained
|
|
200
|
+
// review.capture-refuter / review.capture-validation vectors; executing
|
|
201
|
+
// them makes Go materialize the role prompt, spawn its own locked-down pi
|
|
202
|
+
// subprocess, and admit the raw verdict.
|
|
203
|
+
for (const retired of ["assets/agents/review-refuter.md", "assets/agents/review-validator.md"]) {
|
|
204
|
+
assert.throws(() => read(retired), `${retired} must be deleted`);
|
|
205
|
+
}
|
|
222
206
|
});
|
|
223
207
|
|
|
224
208
|
for (const path of JUDGES) {
|
|
@@ -286,8 +270,8 @@ test("orchestrator, skill, and README agree on compact facade and compatibility"
|
|
|
286
270
|
|
|
287
271
|
test("README documents the exact native pairing and authority-preserving rollback boundary", () => {
|
|
288
272
|
const content = read(README);
|
|
289
|
-
assert.match(content, /package-local Gentle AI v2\.
|
|
290
|
-
assert.match(content, /independently hashes it[\s\S]*negotiates `gentle-ai\.review-integration\/
|
|
273
|
+
assert.match(content, /package-local Gentle AI v2\.4\.0 executable/i);
|
|
274
|
+
assert.match(content, /independently hashes it[\s\S]*negotiates `gentle-ai\.review-integration\/v2`/i);
|
|
291
275
|
assert.match(content, /Capabilities are cached by that executable digest/i);
|
|
292
276
|
assert.match(content, /Every START, target status, FINALIZE, validate, and BIND-SDD request passes the same contract identifier/i);
|
|
293
277
|
assert.match(content, /rollback MUST preserve every native store and receipt/);
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import { __testing } from "../extensions/gentle-ai.ts";
|
|
8
|
+
import type { NativeReviewCli } from "../lib/native-review-cli.ts";
|
|
9
|
+
import { CandidateViewRegistry, injectReviewCandidateView } from "../lib/review-candidate-view.ts";
|
|
10
|
+
import type { ReviewCollectInputV3, ReviewStatusV3 } from "../lib/review-integration-v2.ts";
|
|
11
|
+
|
|
12
|
+
// Live-confirmed adapter defects (2026-08-16, gentle-ai 2.4.0-main, Engram
|
|
13
|
+
// #12461/#12466), both around a lineage recovered EXTERNALLY through native
|
|
14
|
+
// `gentle-ai review recover` (disposition scope_changed):
|
|
15
|
+
//
|
|
16
|
+
// A. The controller never saw the successor's START, so direct reviewer
|
|
17
|
+
// dispatch refused with `no current controller-owned candidate view
|
|
18
|
+
// lineage binding` even though the controller itself had just decoded
|
|
19
|
+
// the successor's authoritative STATUS.
|
|
20
|
+
// B. `gentle_review finalize` on a lineage still at reviewer_results_required
|
|
21
|
+
// misrouted into the correction evidence-first-ordering lane and failed
|
|
22
|
+
// instead of surfacing the provider-offered review.capture-result step.
|
|
23
|
+
|
|
24
|
+
const SHA = `sha256:${"1".repeat(64)}`;
|
|
25
|
+
|
|
26
|
+
function git(cwd: string, ...arguments_: string[]): string {
|
|
27
|
+
return execFileSync("git", arguments_, { cwd, encoding: "utf8" }).trim();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function repository(t: test.TestContext): string {
|
|
31
|
+
const cwd = mkdtempSync(join(tmpdir(), "gentle-pi-recovered-routing-"));
|
|
32
|
+
t.after(() => rmSync(cwd, { recursive: true, force: true }));
|
|
33
|
+
git(cwd, "init", "-b", "main");
|
|
34
|
+
writeFileSync(join(cwd, "tracked.txt"), "base\n");
|
|
35
|
+
git(cwd, "add", "tracked.txt");
|
|
36
|
+
git(cwd, "-c", "user.name=Recovered Test", "-c", "user.email=recovered@example.invalid", "commit", "-m", "base");
|
|
37
|
+
return cwd;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function reviewerResultCollectInput(lineageId: string, lens: string, order: number): ReviewCollectInputV3 {
|
|
41
|
+
return {
|
|
42
|
+
name: "reviewer_result",
|
|
43
|
+
schema: "https://gentle-ai.dev/schema/review/reviewer/v1",
|
|
44
|
+
captureOperation: "review.capture-result",
|
|
45
|
+
arguments: [
|
|
46
|
+
{ name: "lineage", value: lineageId, token: `--lineage=${lineageId}` },
|
|
47
|
+
{ name: "expected-revision", value: SHA, token: `--expected-revision=${SHA}` },
|
|
48
|
+
{ name: "target", value: SHA, token: `--target=${SHA}` },
|
|
49
|
+
{ name: "lens", value: lens, token: `--lens=${lens}` },
|
|
50
|
+
{ name: "order", value: String(order), token: `--order=${order}` },
|
|
51
|
+
{ name: "subject-hash", value: `sha256:${String(order).repeat(64)}`, token: `--subject-hash=sha256:${String(order).repeat(64)}` },
|
|
52
|
+
],
|
|
53
|
+
artifactSubject: {
|
|
54
|
+
schema: "gentle-ai.review-artifact-subject/v2",
|
|
55
|
+
subjectHash: `sha256:${String(order).repeat(64)}`,
|
|
56
|
+
lineageId,
|
|
57
|
+
authorityRevision: SHA,
|
|
58
|
+
targetIdentity: SHA,
|
|
59
|
+
baseTree: "3".repeat(40),
|
|
60
|
+
candidateTree: "4".repeat(40),
|
|
61
|
+
changedPathManifestSha256: SHA,
|
|
62
|
+
lens,
|
|
63
|
+
selectedOrder: order,
|
|
64
|
+
},
|
|
65
|
+
} as unknown as ReviewCollectInputV3;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface StatusFixtureShape {
|
|
69
|
+
lineageId: string;
|
|
70
|
+
baseTree: string;
|
|
71
|
+
currentCandidateTree: string;
|
|
72
|
+
paths: readonly string[];
|
|
73
|
+
inputs?: readonly ReviewCollectInputV3[];
|
|
74
|
+
executeFinalize?: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function recoveredStatus(shape: StatusFixtureShape): ReviewStatusV3 {
|
|
78
|
+
return {
|
|
79
|
+
contract: "gentle-ai.review-integration/v2",
|
|
80
|
+
applicability: "current_target",
|
|
81
|
+
authority: { version: "compact-v2", lineageId: shape.lineageId, state: "reviewer_results_required", generation: 2, revision: SHA },
|
|
82
|
+
receipt: { status: "expected_missing" },
|
|
83
|
+
action: "finalize",
|
|
84
|
+
replayability: "not_replayable",
|
|
85
|
+
targetIdentity: SHA,
|
|
86
|
+
projection: {
|
|
87
|
+
schema: "gentle-ai.review-integration.projection/v1",
|
|
88
|
+
kind: "current-changes",
|
|
89
|
+
projection: "workspace",
|
|
90
|
+
baseTree: shape.baseTree,
|
|
91
|
+
initialReviewTree: shape.currentCandidateTree,
|
|
92
|
+
currentCandidateTree: shape.currentCandidateTree,
|
|
93
|
+
pathsDigest: SHA,
|
|
94
|
+
paths: [...shape.paths],
|
|
95
|
+
intendedUntracked: [],
|
|
96
|
+
intendedUntrackedProof: SHA,
|
|
97
|
+
initialSnapshotIdentity: SHA,
|
|
98
|
+
currentSnapshotIdentity: SHA,
|
|
99
|
+
},
|
|
100
|
+
candidates: [],
|
|
101
|
+
...(shape.inputs !== undefined
|
|
102
|
+
? { nextTransition: { kind: "collect", reasonCode: "reviewer_results_required", collect: { inputs: [...shape.inputs] } } }
|
|
103
|
+
: {}),
|
|
104
|
+
...(shape.executeFinalize === true
|
|
105
|
+
? { nextTransition: { kind: "execute", reasonCode: "captured_results_ready", execute: { operation: "review.finalize", arguments: [], binding: { lineageId: shape.lineageId } } } }
|
|
106
|
+
: {}),
|
|
107
|
+
raw: { schema: "gentle-ai.review-integration.status/v5", action: "finalize", lineage_id: shape.lineageId },
|
|
108
|
+
} as unknown as ReviewStatusV3;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function statusOnlyNative(statuses: readonly ReviewStatusV3[]): { native: NativeReviewCli; finalizeCalls: () => number; statusCalls: () => number } {
|
|
112
|
+
const queue = [...statuses];
|
|
113
|
+
let finalizeCalls = 0;
|
|
114
|
+
let statusCalls = 0;
|
|
115
|
+
const native = {
|
|
116
|
+
targetStatus: async () => {
|
|
117
|
+
statusCalls += 1;
|
|
118
|
+
// The v5 lane legitimately re-queries STATUS (workspace-root
|
|
119
|
+
// rebind); keep serving the terminal fixture once drained.
|
|
120
|
+
const next = queue.length > 1 ? queue.shift() : queue[0];
|
|
121
|
+
if (next === undefined) throw new Error("status queue exhausted");
|
|
122
|
+
return next;
|
|
123
|
+
},
|
|
124
|
+
finalize: async () => {
|
|
125
|
+
finalizeCalls += 1;
|
|
126
|
+
throw new Error("native finalize must not be invoked while reviewer results are outstanding");
|
|
127
|
+
},
|
|
128
|
+
} as unknown as NativeReviewCli;
|
|
129
|
+
return { native, finalizeCalls: () => finalizeCalls, statusCalls: () => statusCalls };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function runController(
|
|
133
|
+
parameters: Record<string, unknown>,
|
|
134
|
+
cwd: string,
|
|
135
|
+
native: NativeReviewCli,
|
|
136
|
+
candidateViews: CandidateViewRegistry,
|
|
137
|
+
): Promise<Record<string, unknown>> {
|
|
138
|
+
return await __testing.executeReviewControllerOperation(
|
|
139
|
+
parameters,
|
|
140
|
+
cwd,
|
|
141
|
+
new Map(),
|
|
142
|
+
native,
|
|
143
|
+
undefined,
|
|
144
|
+
undefined,
|
|
145
|
+
undefined,
|
|
146
|
+
candidateViews,
|
|
147
|
+
) as Record<string, unknown>;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// --- DEFECT A: STATUS discovery hydrates the dispatch binding ---
|
|
151
|
+
|
|
152
|
+
test("STATUS discovery hydrates the dispatch binding for an externally recovered lineage", async (t) => {
|
|
153
|
+
const cwd = repository(t);
|
|
154
|
+
writeFileSync(join(cwd, "tracked.txt"), "successor candidate change\n");
|
|
155
|
+
// Derive the real frozen identity of the live workspace candidate the way
|
|
156
|
+
// native recover froze it: the descriptor in status/v5 names these trees.
|
|
157
|
+
const probe = new CandidateViewRegistry();
|
|
158
|
+
const probeView = probe.create({ contributorRoot: cwd });
|
|
159
|
+
const frozen = { baseTree: probeView.baseTree, currentCandidateTree: probeView.candidateTree, paths: probeView.paths };
|
|
160
|
+
probe.cleanup(probeView.token);
|
|
161
|
+
|
|
162
|
+
const successor = "review-recovered-successor";
|
|
163
|
+
const status = recoveredStatus({ lineageId: successor, ...frozen, inputs: [reviewerResultCollectInput(successor, "review-reliability", 0)] });
|
|
164
|
+
const { native } = statusOnlyNative([status]);
|
|
165
|
+
const registry = new CandidateViewRegistry();
|
|
166
|
+
|
|
167
|
+
// The controller does not know this lineage: dispatch refuses.
|
|
168
|
+
assert.throws(
|
|
169
|
+
() => injectReviewCandidateView({ agent: "review-reliability", task: "review", mode: "task" }, registry),
|
|
170
|
+
/no current controller-owned candidate view lineage binding/,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const envelope = await runController({ operation: "status", lineageId: successor }, cwd, native, registry);
|
|
174
|
+
assert.equal(envelope.operation, "status");
|
|
175
|
+
|
|
176
|
+
// After the controller itself decoded the successor's authoritative
|
|
177
|
+
// STATUS, the dispatch binding must be hydrated from that status.
|
|
178
|
+
assert.equal(registry.hasCurrentBinding(), true, "STATUS discovery must hydrate the controller-owned dispatch binding");
|
|
179
|
+
try {
|
|
180
|
+
const dispatch: Record<string, unknown> = { agent: "review-reliability", task: "review the recovered successor", mode: "task" };
|
|
181
|
+
assert.doesNotThrow(() => injectReviewCandidateView(dispatch, registry));
|
|
182
|
+
assert.match(String(dispatch.task), new RegExp(successor));
|
|
183
|
+
assert.equal(registry.resolveCurrentForLens("review-reliability").candidateTree, frozen.currentCandidateTree);
|
|
184
|
+
} finally {
|
|
185
|
+
registry.cleanup(registry.resolveCurrentForLens("review-reliability").token);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("STATUS discovery never hydrates without pending reviewer-result collection", async (t) => {
|
|
190
|
+
const cwd = repository(t);
|
|
191
|
+
writeFileSync(join(cwd, "tracked.txt"), "successor candidate change\n");
|
|
192
|
+
const probe = new CandidateViewRegistry();
|
|
193
|
+
const probeView = probe.create({ contributorRoot: cwd });
|
|
194
|
+
const frozen = { baseTree: probeView.baseTree, currentCandidateTree: probeView.candidateTree, paths: probeView.paths };
|
|
195
|
+
probe.cleanup(probeView.token);
|
|
196
|
+
|
|
197
|
+
const successor = "review-recovered-successor";
|
|
198
|
+
const { native } = statusOnlyNative([recoveredStatus({ lineageId: successor, ...frozen, executeFinalize: true })]);
|
|
199
|
+
const registry = new CandidateViewRegistry();
|
|
200
|
+
await runController({ operation: "status", lineageId: successor }, cwd, native, registry);
|
|
201
|
+
assert.equal(registry.hasCurrentBinding(), false, "an execute transition offers no reviewer dispatch and must not hydrate");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// --- DEFECT B: finalize routes strictly from the provider transition ---
|
|
205
|
+
|
|
206
|
+
test("finalize at reviewer_results_required surfaces capture-result and never enters evidence ordering", async (t) => {
|
|
207
|
+
const cwd = repository(t);
|
|
208
|
+
const lineageId = "recovered-lineage";
|
|
209
|
+
const status = () => recoveredStatus({
|
|
210
|
+
lineageId,
|
|
211
|
+
baseTree: "3".repeat(40),
|
|
212
|
+
currentCandidateTree: "4".repeat(40),
|
|
213
|
+
paths: ["tracked.txt"],
|
|
214
|
+
inputs: [reviewerResultCollectInput(lineageId, "review-reliability", 0)],
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Plain finalize with no negotiated collection answers.
|
|
218
|
+
const plainHarness = statusOnlyNative([status()]);
|
|
219
|
+
const plain = await runController({ operation: "finalize", lineageId, input: JSON.stringify({}) }, cwd, plainHarness.native, new CandidateViewRegistry());
|
|
220
|
+
assert.equal(plain.status, "blocked");
|
|
221
|
+
assert.equal(plain.outcome, "reviewer-results-required");
|
|
222
|
+
assert.match(String(plain.reason), /capture the reviewer result first/i);
|
|
223
|
+
assert.match(String(plain.reason), /review\.capture-result/);
|
|
224
|
+
assert.equal(plain.mutation_performed, false);
|
|
225
|
+
assert.equal(plain.mutation_outcome, "none");
|
|
226
|
+
assert.deepEqual(plain.pending_lenses, ["review-reliability"]);
|
|
227
|
+
assert.equal(plainHarness.finalizeCalls(), 0, "raw native finalize must never run while reviewer results are outstanding");
|
|
228
|
+
assert.doesNotMatch(JSON.stringify(plain), /evidence-first-ordering/);
|
|
229
|
+
|
|
230
|
+
// The live misroute shape: finalize carrying final evidence must not fall
|
|
231
|
+
// into the correction evidence-first-ordering lane either.
|
|
232
|
+
const evidenceHarness = statusOnlyNative([status()]);
|
|
233
|
+
const misrouted = await runController(
|
|
234
|
+
{ operation: "finalize", lineageId, input: JSON.stringify({ final_evidence: "all checks green", final_verification_passed: true }) },
|
|
235
|
+
cwd,
|
|
236
|
+
evidenceHarness.native,
|
|
237
|
+
new CandidateViewRegistry(),
|
|
238
|
+
);
|
|
239
|
+
assert.equal(misrouted.status, "blocked");
|
|
240
|
+
assert.equal(misrouted.outcome, "reviewer-results-required");
|
|
241
|
+
assert.match(String(misrouted.reason), /capture the reviewer result first/i);
|
|
242
|
+
assert.equal(misrouted.mutation_performed, false);
|
|
243
|
+
assert.equal(misrouted.mutation_outcome, "none");
|
|
244
|
+
assert.equal(evidenceHarness.finalizeCalls(), 0);
|
|
245
|
+
assert.doesNotMatch(JSON.stringify(misrouted), /evidence-first-ordering/);
|
|
246
|
+
});
|