gentle-pi 2.2.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 +76 -79
- package/assets/agents/review-readability.md +0 -2
- package/assets/agents/review-reliability.md +0 -2
- package/assets/agents/review-resilience.md +0 -2
- package/assets/agents/review-risk.md +0 -2
- package/assets/agents/sdd-apply.md +5 -3
- package/assets/agents/sdd-explore.md +1 -0
- package/assets/agents/sdd-proposal.md +2 -0
- package/assets/agents/sdd-research.md +54 -0
- package/assets/agents/sdd-status.md +4 -4
- package/assets/agents/sdd-tasks.md +2 -3
- package/assets/agents/sdd-verify.md +21 -1
- package/assets/chains/sdd-full.chain.md +1 -1
- package/assets/chains/sdd-verify.chain.md +1 -1
- package/assets/orchestrator-delegation.md +56 -249
- package/assets/orchestrator-memory.md +2 -0
- package/assets/orchestrator.md +20 -40
- package/assets/sdd-orchestrator-workflow.md +30 -37
- package/assets/support/sdd-status-contract.md +7 -7
- package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
- package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
- package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
- 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/native-authority-architecture.md +9 -11
- package/docs/review-integration.md +27 -373
- package/extensions/ask-user-choice.ts +151 -0
- package/extensions/gentle-ai.ts +2349 -3785
- package/extensions/quiet-tools.ts +515 -32
- package/extensions/sdd-init.ts +4 -8
- package/lib/agent-home.ts +8 -0
- package/lib/gentle-ai-renderer.ts +70 -0
- package/lib/model-routing-authority.ts +133 -0
- package/lib/native-review-cli.ts +387 -940
- package/lib/opaque-pi-reviewer-adapter.ts +284 -0
- package/lib/provider-contract-bundle.ts +88 -6
- package/lib/review-candidate-view.ts +341 -132
- package/lib/review-host-relay.ts +324 -96
- package/lib/review-integration-v2.ts +868 -307
- package/lib/review-last-event-controller.ts +35 -0
- package/lib/sdd-preflight.ts +179 -68
- package/lib/sdd-status.ts +66 -111
- package/lib/terminal-theme.ts +1 -1
- package/package.json +83 -82
- package/runtime/gentle-ai-binary.mjs +1 -1
- package/runtime/native-review-cli.mjs +337 -890
- package/runtime/review-integration-v2.mjs +816 -255
- package/runtime/review-relay-contract.mjs +1 -1
- package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +4 -5
- package/scripts/gentle-ai-installer.mjs +75 -22
- package/scripts/maintainer/provider-relay-matrix.mjs +195 -11
- package/scripts/test-packed-runner.mjs +4 -7
- package/scripts/verify-package-files.mjs +23 -23
- package/skills/_shared/review-ledger-contract.md +14 -14
- package/skills/chained-pr/SKILL.md +3 -0
- package/skills/cognitive-doc-design/SKILL.md +1 -1
- package/skills/comment-writer/SKILL.md +1 -1
- package/skills/gentle-ai/SKILL.md +7 -74
- package/skills/issue-creation/SKILL.md +53 -93
- package/skills/judgment-day/SKILL.md +5 -7
- package/skills/rdd-defect-workflow/SKILL.md +3 -3
- package/skills/release/SKILL.md +3 -3
- package/skills/skill-registry/SKILL.md +1 -1
- package/skills/work-unit-commits/SKILL.md +3 -1
- package/tests/artifact-language.test.ts +24 -11
- package/tests/ask-user-choice.test.ts +264 -0
- package/tests/codegraph-tools.test.ts +3 -3
- package/tests/crosslane/cross-lane.mjs +15 -1168
- package/tests/delegated-key-learnings-contract.test.ts +8 -6
- package/tests/devbinary/native-review-parity.devtest.ts +167 -250
- package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
- package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
- package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
- package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
- package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
- package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
- package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
- package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
- package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
- package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
- package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
- package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
- 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 +83 -13
- package/tests/gentle-ai-dev-binary.test.ts +51 -228
- package/tests/gentle-ai-installer.test.ts +137 -47
- package/tests/gentle-ai.test.ts +821 -131
- package/tests/gentle-theme.test.ts +133 -0
- package/tests/issue-creation-skill.test.ts +103 -0
- package/tests/maintainer/provider-relay.maintest.ts +340 -4
- package/tests/model-routing-authority.test.ts +257 -0
- package/tests/native-review-capability-contract.test.ts +45 -1
- package/tests/native-review-cli.test.ts +586 -962
- package/tests/native-review-consent.test.ts +150 -66
- package/tests/native-review-parity-runtime.test.ts +80 -359
- package/tests/native-review-parity.test.ts +674 -925
- package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
- package/tests/orchestrator-budget.test.ts +132 -88
- package/tests/orchestrator-rdd-ownership.test.ts +103 -0
- package/tests/package-manifest.test.ts +152 -36
- package/tests/provider-contract-bundle.test.ts +76 -0
- package/tests/provider-contract-mirror.test.ts +19 -0
- package/tests/provider-defect-handoff.test.ts +15 -118
- package/tests/quiet-tool-rendering.test.ts +1055 -28
- package/tests/review-agent-end-preflight.test.ts +408 -0
- package/tests/review-authority-recovery-docs.test.ts +1 -2
- package/tests/review-candidate-view.test.ts +409 -3
- package/tests/review-compact-contract.test.ts +4 -30
- package/tests/review-contract-prompt.test.ts +139 -0
- package/tests/review-controller-lock-status.test.ts +2 -2
- package/tests/review-controller-native-recovery.test.ts +363 -1450
- package/tests/review-controller-native-routing.test.ts +1251 -5642
- package/tests/review-controller-retired-ops.test.ts +1 -1
- package/tests/review-controller-workspace-root.test.ts +222 -71
- package/tests/review-controller.test.ts +26 -816
- package/tests/review-corrected-finalize-binding.test.ts +112 -153
- package/tests/review-dispatch-hydration-gap.test.ts +1 -53
- package/tests/review-gate.test.ts +0 -45
- package/tests/review-host-relay-restart-parity.test.ts +360 -0
- package/tests/review-host-relay-routing.test.ts +328 -91
- package/tests/review-host-relay.test.ts +436 -14
- package/tests/review-integration-v2-forward.test.ts +623 -553
- package/tests/review-integration-v2.test.ts +239 -165
- package/tests/review-last-event-closure.test.ts +408 -0
- package/tests/review-ledger-contract.test.ts +97 -35
- package/tests/review-recovered-lineage-routing.test.ts +0 -47
- package/tests/review-relay-transport-agent.test.ts +119 -46
- package/tests/review-snapshot.test.ts +3 -2
- package/tests/runtime-harness.mjs +362 -196
- package/tests/sdd-agent-tools.test.ts +36 -0
- package/tests/sdd-preflight.test.ts +81 -15
- package/tests/sdd-status.test.ts +109 -110
- package/tests/skill-collision-prefixes.test.ts +5 -8
- package/tests/writer-edit-surface-scope.test.ts +366 -0
- package/themes/Gentleman-Cute.json +94 -0
- package/themes/Gentleman-Sexy.json +92 -0
- package/lib/git-commit-transaction.ts +0 -861
- package/runtime/git-commit-transaction.mjs +0 -862
- package/scripts/run-git-commit-transaction.mjs +0 -35
- package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
- package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
- package/tests/git-commit-transaction.test.ts +0 -530
- /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
|
@@ -1,175 +1,134 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import {
|
|
3
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
5
3
|
import { join } from "node:path";
|
|
6
4
|
import test from "node:test";
|
|
7
5
|
import { __testing } from "../extensions/gentle-ai.ts";
|
|
8
6
|
import type { NativeReviewCli } from "../lib/native-review-cli.ts";
|
|
9
|
-
import {
|
|
10
|
-
import type { ReviewStatusV3 } from "../lib/review-integration-v2.ts";
|
|
7
|
+
import { assertReviewLastEventClosureBinding, decodeReviewLastEventClosureV1, type ReviewStatusV3 } from "../lib/review-integration-v2.ts";
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
// lineage detected a reliability defect, corrected it inside budget, captured
|
|
14
|
-
// evidence and the native targeted validator. Negotiated STATUS then returned
|
|
15
|
-
// `captured_provider_targeted_validation_ready` with an execute
|
|
16
|
-
// `review.finalize` transition, and running that exact transition failed twice
|
|
17
|
-
// with `candidate-target-projection-drift`, mutation none, so no receipt was
|
|
18
|
-
// ever minted.
|
|
19
|
-
//
|
|
20
|
-
// Measured on a faithful live reproduction (medium candidate, real binary,
|
|
21
|
-
// START through the controller so the registry holds the START-time view):
|
|
22
|
-
//
|
|
23
|
-
// START candidate tree: 4c24487f850050af0a0944d37d4dbb15287ab3da
|
|
24
|
-
// corrected candidate tree: 777f48fdc4e6ed9abd325d6b4c49e6d06036c906
|
|
25
|
-
// FINALIZE (no documents) -> candidate-target-projection-drift, mutation none
|
|
26
|
-
// registry branch taken: ["resolveForFinalize"]
|
|
27
|
-
//
|
|
28
|
-
// Root cause: the corrected tree is tolerated only while
|
|
29
|
-
// `correctionCompletion` (validation AND final_evidence) or `validationAttempt`
|
|
30
|
-
// (final_evidence without a forecast) hold. A FINALIZE that merely follows the
|
|
31
|
-
// provider's own execute transition carries no documents, so both are false,
|
|
32
|
-
// the START-time view is resolved, and it is compared against the corrected
|
|
33
|
-
// candidate the provider itself authorized.
|
|
9
|
+
const CAPTURED_FIXTURES = join(process.cwd(), "tests", "fixtures", "devbinary");
|
|
34
10
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const CORRECTED = "export function lastComponent(input) {\n const parts = input.split(\"/\");\n return parts[parts.length - 1];\n}\n";
|
|
38
|
-
|
|
39
|
-
function git(cwd: string, ...arguments_: string[]): string {
|
|
40
|
-
return execFileSync("git", arguments_, { cwd, encoding: "utf8" }).trim();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function repository(t: test.TestContext): string {
|
|
44
|
-
const cwd = mkdtempSync(join(tmpdir(), "gentle-pi-corrected-finalize-"));
|
|
45
|
-
t.after(() => rmSync(cwd, { recursive: true, force: true }));
|
|
46
|
-
git(cwd, "init", "-b", "main");
|
|
47
|
-
git(cwd, "config", "user.name", "Corrected Test");
|
|
48
|
-
git(cwd, "config", "user.email", "corrected@example.invalid");
|
|
49
|
-
mkdirSync(join(cwd, "src"), { recursive: true });
|
|
50
|
-
writeFileSync(join(cwd, "src", "parse.js"), "export function parsePath(input) {\n return input.split(\"/\");\n}\n");
|
|
51
|
-
git(cwd, "add", "-A");
|
|
52
|
-
git(cwd, "commit", "-m", "base");
|
|
53
|
-
writeFileSync(join(cwd, "src", "parse.js"), DEFECT);
|
|
54
|
-
return cwd;
|
|
11
|
+
function captured(name: string): Record<string, unknown> {
|
|
12
|
+
return JSON.parse(readFileSync(join(CAPTURED_FIXTURES, name), "utf8")) as Record<string, unknown>;
|
|
55
13
|
}
|
|
56
14
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
15
|
+
test("captured correction-required result preserves the provider status continuation", () => {
|
|
16
|
+
const fixture = captured("last-event-capture-result-correction-required.captured.json");
|
|
17
|
+
const closure = decodeReviewLastEventClosureV1(fixture);
|
|
18
|
+
assert.equal(closure.operation, "review/capture-result");
|
|
19
|
+
assert.equal(closure.state, "correction_required");
|
|
20
|
+
assert.match(closure.storeRevision, /^sha256:[a-f0-9]{64}$/);
|
|
21
|
+
assert.equal(closure.statusContinuation?.operation, "review.status");
|
|
22
|
+
assert.deepEqual(
|
|
23
|
+
closure.statusContinuation?.arguments.map((argument) => argument.token),
|
|
24
|
+
[
|
|
25
|
+
"--cwd=/tmp/repository",
|
|
26
|
+
"--contract=gentle-ai.review-integration/v2",
|
|
27
|
+
"--next-transition=true",
|
|
28
|
+
"--lineage=review-afeed0d5a9aa24a1",
|
|
29
|
+
"--agent=pi",
|
|
30
|
+
],
|
|
31
|
+
);
|
|
32
|
+
assert.deepEqual(closure.statusContinuation?.raw, fixture.status_continuation);
|
|
33
|
+
});
|
|
67
34
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
replayability: "not_replayable",
|
|
76
|
-
frozen: { tier: "medium", originalChangedLines: 4, correctionBudget: 2 },
|
|
77
|
-
targetIdentity: SHA,
|
|
78
|
-
projection: {
|
|
79
|
-
schema: "gentle-ai.review-integration.projection/v1",
|
|
80
|
-
kind: "current-changes",
|
|
81
|
-
projection: "workspace",
|
|
82
|
-
baseTree: identity.baseTree,
|
|
83
|
-
initialReviewTree: identity.candidateTree,
|
|
84
|
-
currentCandidateTree: identity.candidateTree,
|
|
85
|
-
pathsDigest: SHA,
|
|
86
|
-
paths: [...identity.paths],
|
|
87
|
-
intendedUntracked: [],
|
|
88
|
-
intendedUntrackedProof: SHA,
|
|
89
|
-
initialSnapshotIdentity: SHA,
|
|
90
|
-
currentSnapshotIdentity: SHA,
|
|
35
|
+
test("correction status continuation stays bound to its enclosing closure and provider target", () => {
|
|
36
|
+
const mismatch = `sha256:${"b".repeat(64)}`;
|
|
37
|
+
const cases = [
|
|
38
|
+
{
|
|
39
|
+
name: "missing binding lineage",
|
|
40
|
+
mutate: (binding: Record<string, unknown>) => { delete binding.lineage_id; },
|
|
41
|
+
error: /lineage does not match its enclosing closure/,
|
|
91
42
|
},
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
reasonCode: "captured_provider_targeted_validation_ready",
|
|
97
|
-
execute: {
|
|
98
|
-
operation: "review.finalize",
|
|
99
|
-
arguments: [
|
|
100
|
-
{ name: "lineage", value: lineageId, token: `--lineage=${lineageId}` },
|
|
101
|
-
{ name: "captured-evidence", value: "true", token: "--captured-evidence=true" },
|
|
102
|
-
],
|
|
103
|
-
binding: { lineageId },
|
|
104
|
-
},
|
|
43
|
+
{
|
|
44
|
+
name: "different binding lineage",
|
|
45
|
+
mutate: (binding: Record<string, unknown>) => { binding.lineage_id = "review-different"; },
|
|
46
|
+
error: /lineage does not match its enclosing closure/,
|
|
105
47
|
},
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function approvingNative(status: ReviewStatusV3): { native: NativeReviewCli; transitions: number } {
|
|
111
|
-
const state = { transitions: 0 };
|
|
112
|
-
const native = {
|
|
113
|
-
targetStatus: async () => status,
|
|
114
|
-
finalizeTransition: async () => {
|
|
115
|
-
state.transitions += 1;
|
|
116
|
-
return { lineageId: status.authority!.lineageId, state: "approved", action: "approved", storeRevision: "r2" };
|
|
48
|
+
{
|
|
49
|
+
name: "missing binding revision",
|
|
50
|
+
mutate: (binding: Record<string, unknown>) => { delete binding.revision; },
|
|
51
|
+
error: /revision does not match its enclosing closure/,
|
|
117
52
|
},
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
test("FINALIZE follows the provider transition after a correction instead of drifting on the START view", async (t) => {
|
|
132
|
-
const cwd = repository(t);
|
|
133
|
-
const lineageId = "review-corrected-lineage";
|
|
134
|
-
const { registry, startTree } = sessionWithStartBinding(cwd, lineageId);
|
|
135
|
-
t.after(() => { try { registry.cleanup(registry.resolveForFinalize(lineageId).token); } catch { /* already cleaned */ } });
|
|
136
|
-
|
|
137
|
-
// The bounded correction lands: the candidate identity legitimately moves.
|
|
138
|
-
writeFileSync(join(cwd, "src", "parse.js"), CORRECTED);
|
|
139
|
-
const corrected = liveIdentity(cwd);
|
|
140
|
-
assert.notEqual(corrected.candidateTree, startTree, "the correction must move the candidate tree");
|
|
53
|
+
{
|
|
54
|
+
name: "different binding revision",
|
|
55
|
+
mutate: (binding: Record<string, unknown>) => { binding.revision = mismatch; },
|
|
56
|
+
error: /revision does not match its enclosing closure/,
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
for (const scenario of cases) {
|
|
60
|
+
const fixture = captured("last-event-capture-result-correction-required.captured.json");
|
|
61
|
+
const continuation = fixture.status_continuation as Record<string, unknown>;
|
|
62
|
+
scenario.mutate(continuation.binding as Record<string, unknown>);
|
|
63
|
+
assert.throws(() => decodeReviewLastEventClosureV1(fixture), scenario.error, scenario.name);
|
|
64
|
+
}
|
|
141
65
|
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
66
|
+
const argumentMismatch = captured("last-event-capture-result-correction-required.captured.json");
|
|
67
|
+
const continuation = argumentMismatch.status_continuation as Record<string, unknown>;
|
|
68
|
+
const argumentsList = continuation.arguments as Array<Record<string, unknown>>;
|
|
69
|
+
const lineageArgument = argumentsList.find((argument) => argument.name === "lineage")!;
|
|
70
|
+
lineageArgument.value = "review-different";
|
|
71
|
+
lineageArgument.token = "--lineage=review-different";
|
|
72
|
+
assert.throws(() => decodeReviewLastEventClosureV1(argumentMismatch), /lineage argument does not match its enclosing closure/);
|
|
148
73
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
74
|
+
const closure = decodeReviewLastEventClosureV1(captured("last-event-capture-result-correction-required.captured.json"));
|
|
75
|
+
assert.throws(
|
|
76
|
+
() => assertReviewLastEventClosureBinding(closure, { lineageId: closure.lineageId, targetIdentity: mismatch }),
|
|
77
|
+
/status continuation target does not match its provider binding/,
|
|
153
78
|
);
|
|
154
|
-
assert.equal(harness.transitions, 1, "the provider's own finalize transition must run");
|
|
155
|
-
assert.equal((result.result as { state?: string } | undefined)?.state, "approved");
|
|
156
79
|
});
|
|
157
80
|
|
|
158
|
-
test("
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
81
|
+
test("correction-required result and refuter closures require one status continuation", () => {
|
|
82
|
+
for (const name of [
|
|
83
|
+
"last-event-capture-result-correction-required.captured.json",
|
|
84
|
+
"last-event-capture-refuter-correction-required.captured.json",
|
|
85
|
+
]) {
|
|
86
|
+
const fixture = captured(name);
|
|
87
|
+
delete fixture.status_continuation;
|
|
88
|
+
assert.throws(() => decodeReviewLastEventClosureV1(fixture), /requires status_continuation/, name);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
163
91
|
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
cwd, new Map(), harness.native, undefined, undefined, undefined, registry,
|
|
170
|
-
) as Record<string, unknown>;
|
|
92
|
+
test("closures outside result or refuter correction-required forbid a status continuation", () => {
|
|
93
|
+
const approved = captured("last-event-capture-result-approved.captured.json");
|
|
94
|
+
approved.status_continuation = captured("last-event-capture-result-correction-required.captured.json").status_continuation;
|
|
95
|
+
assert.throws(() => decodeReviewLastEventClosureV1(approved), /status_continuation is only valid/, "approved result closure");
|
|
96
|
+
});
|
|
171
97
|
|
|
172
|
-
|
|
98
|
+
test("current capture fails closed when fresh STATUS no longer offers the corrected candidate binding", async () => {
|
|
99
|
+
const lineageId = "corrected-candidate";
|
|
100
|
+
const sha = `sha256:${"a".repeat(64)}`;
|
|
101
|
+
const binding = {
|
|
102
|
+
name: "provider_targeted_validator",
|
|
103
|
+
schema: "https://gentle-ai.dev/schema/review/targeted-validator/v1",
|
|
104
|
+
captureOperation: "review.capture-validation",
|
|
105
|
+
arguments: [
|
|
106
|
+
{ name: "lineage", value: lineageId, token: `--lineage=${lineageId}` },
|
|
107
|
+
{ name: "target", value: sha, token: `--target=${sha}` },
|
|
108
|
+
{ name: "agent", value: "pi", token: "--agent=pi" },
|
|
109
|
+
{ name: "execute", value: "true", token: "--execute=true" },
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
let captureCalls = 0;
|
|
113
|
+
const native = {
|
|
114
|
+
targetStatus: async () => ({
|
|
115
|
+
contract: "gentle-ai.review-integration/v2",
|
|
116
|
+
applicability: "current_target",
|
|
117
|
+
authority: { version: "compact-v2", lineageId, state: "correction_required", generation: 1, revision: sha },
|
|
118
|
+
receipt: { status: "expected_missing" },
|
|
119
|
+
action: "stop",
|
|
120
|
+
replayability: "not_replayable",
|
|
121
|
+
targetIdentity: sha,
|
|
122
|
+
projection: { schema: "gentle-ai.review-candidate-projection/v1", kind: "current-changes", projection: "workspace", baseTree: "b".repeat(40), initialReviewTree: "b".repeat(40), currentCandidateTree: "c".repeat(40), pathsDigest: sha, paths: ["corrected.ts"], intendedUntracked: [], intendedUntrackedProof: sha, initialSnapshotIdentity: sha, currentSnapshotIdentity: sha },
|
|
123
|
+
repair: { schema: "gentle-ai.review-authority-repair-assessment/v1", status: "unsupported", counts: { lineages: 0, compactLineages: 0, legacyLineages: 0, events: 0, bytes: 0, eligibleCandidates: 0, unsupportedLineages: 0, conflicts: 0 }, supportedOperations: [], authorizationSchema: "gentle-ai.review-repair-authorization/v1" },
|
|
124
|
+
candidates: [],
|
|
125
|
+
nextTransition: { kind: "stop", reasonCode: "corrected_candidate_unavailable" },
|
|
126
|
+
raw: { schema: "gentle-ai.review-integration.status/v5" },
|
|
127
|
+
} as unknown as ReviewStatusV3),
|
|
128
|
+
captureProviderRole: async () => { captureCalls += 1; throw new Error("must not capture"); },
|
|
129
|
+
} as unknown as NativeReviewCli;
|
|
130
|
+
const result = await __testing.executeReviewCaptureOperation({ lineageId, collectBinding: JSON.stringify(binding) }, process.cwd(), native);
|
|
131
|
+
assert.equal(result.outcome, "capture-binding-rejected");
|
|
173
132
|
assert.equal(result.mutation_outcome, "none");
|
|
174
|
-
assert.equal(
|
|
133
|
+
assert.equal(captureCalls, 0);
|
|
175
134
|
});
|
|
@@ -124,62 +124,10 @@ function statusNative(status: ReviewStatusV3): NativeReviewCli {
|
|
|
124
124
|
|
|
125
125
|
async function runController(parameters: Record<string, unknown>, cwd: string, native: NativeReviewCli, candidateViews: CandidateViewRegistry): Promise<Record<string, unknown>> {
|
|
126
126
|
return await __testing.executeReviewControllerOperation(
|
|
127
|
-
parameters, cwd,
|
|
127
|
+
parameters, cwd, native, undefined, candidateViews,
|
|
128
128
|
) as Record<string, unknown>;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
test("FINALIZE hydrates the dispatch binding for an externally recovered successor in a dirty linked worktree", async (t) => {
|
|
132
|
-
const cwd = linkedDirtyWorktree(t);
|
|
133
|
-
const lineageId = "recovered-dirty-successor";
|
|
134
|
-
const registry = new CandidateViewRegistry();
|
|
135
|
-
const native = statusNative(successorStatus(lineageId, liveProjection(cwd)));
|
|
136
|
-
|
|
137
|
-
// The maintainer's flow: FINALIZE first (correctly blocked since #340),
|
|
138
|
-
// then a reviewer dispatch. FINALIZE never went through the STATUS
|
|
139
|
-
// controller operation, so nothing had hydrated the registry.
|
|
140
|
-
const finalize = await runController({ operation: "finalize", lineageId, input: JSON.stringify({}) }, cwd, native, registry);
|
|
141
|
-
assert.equal(finalize.outcome, "reviewer-results-required");
|
|
142
|
-
assert.equal(registry.hasCurrentBinding(), true, "FINALIZE must hydrate the dispatch binding from the status it decoded");
|
|
143
|
-
assert.deepEqual(finalize.dispatch_binding, { hydrated: true, lineage_id: lineageId, lenses: ["review-reliability"] });
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
const dispatch: Record<string, unknown> = { agent: "review-reliability", task: "review the recovered successor", mode: "task" };
|
|
147
|
-
assert.doesNotThrow(() => injectReviewCandidateView(dispatch, registry));
|
|
148
|
-
assert.match(String(dispatch.task), new RegExp(lineageId));
|
|
149
|
-
} finally {
|
|
150
|
-
registry.cleanup(registry.resolveCurrentForLens("review-reliability").token);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test("a swallowed hydration failure becomes an observable typed dispatch refusal", async (t) => {
|
|
155
|
-
const cwd = linkedDirtyWorktree(t);
|
|
156
|
-
const lineageId = "drifted-successor";
|
|
157
|
-
const registry = new CandidateViewRegistry();
|
|
158
|
-
// Realistic drift: the frozen candidate names the pristine HEAD tree while
|
|
159
|
-
// the live worktree carries the uncommitted modification. Hydration
|
|
160
|
-
// legitimately fails, and that failure must stop being invisible.
|
|
161
|
-
const live = liveProjection(cwd);
|
|
162
|
-
const drifted = { ...live, currentCandidateTree: live.baseTree };
|
|
163
|
-
const finalize = await runController({ operation: "finalize", lineageId, input: JSON.stringify({}) }, cwd, statusNative(successorStatus(lineageId, drifted)), registry);
|
|
164
|
-
|
|
165
|
-
assert.equal(finalize.outcome, "reviewer-results-required");
|
|
166
|
-
assert.equal(registry.hasCurrentBinding(), false);
|
|
167
|
-
const binding = finalize.dispatch_binding as { hydrated: boolean; lineage_id: string; reason: string; message: string };
|
|
168
|
-
assert.equal(binding.hydrated, false, "a failed hydration must be reported, never swallowed");
|
|
169
|
-
assert.equal(binding.lineage_id, lineageId);
|
|
170
|
-
assert.ok(typeof binding.reason === "string" && binding.reason.length > 0, "the failure carries a typed reason");
|
|
171
|
-
assert.match(binding.message, /projection|live candidate/i);
|
|
172
|
-
|
|
173
|
-
// The dispatch refusal must now admit that hydration was attempted.
|
|
174
|
-
assert.throws(
|
|
175
|
-
() => injectReviewCandidateView({ agent: "review-reliability", task: "review", mode: "task" }, registry),
|
|
176
|
-
(error: unknown) => error instanceof CandidateViewError
|
|
177
|
-
&& error.reason === "current-binding-hydration-failed"
|
|
178
|
-
&& /hydrat/i.test(error.message)
|
|
179
|
-
&& error.message.includes(lineageId),
|
|
180
|
-
);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
131
|
test("STATUS keeps hydrating and stays read-only when hydration fails", async (t) => {
|
|
184
132
|
const cwd = linkedDirtyWorktree(t);
|
|
185
133
|
const lineageId = "status-drifted-successor";
|
|
@@ -4,7 +4,6 @@ import { chmodSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:f
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import test from "node:test";
|
|
7
|
-
import { __testing } from "../extensions/gentle-ai.ts";
|
|
8
7
|
import {
|
|
9
8
|
EXTERNAL_RELEASE_EVIDENCE,
|
|
10
9
|
GATE_RESULT,
|
|
@@ -175,14 +174,6 @@ function temporaryAuthority(t: test.TestContext): GateRepository & {
|
|
|
175
174
|
return { ...repository, store, receipt: receiptFor(state), authoritativeReceipt: store.createAuthoritativeReceipt("approved-lineage") };
|
|
176
175
|
}
|
|
177
176
|
|
|
178
|
-
test("lifecycle command classification identifies gates but never runs review routing", () => {
|
|
179
|
-
assert.equal(__testing.classifyReviewEvent("git commit -m fix"), "pre-commit");
|
|
180
|
-
assert.equal(__testing.classifyReviewEvent("git -C /repo push origin main"), "pre-push");
|
|
181
|
-
assert.equal(__testing.classifyReviewEvent("gh pr create --draft"), "pre-pr");
|
|
182
|
-
assert.equal(__testing.classifyReviewEvent("gh release create v1.2.3"), "pre-release");
|
|
183
|
-
assert.equal(__testing.classifyReviewEvent("git status"), null);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
177
|
test("unbranded receipts are rejected before lifecycle gate evaluation", (t) => {
|
|
187
178
|
const { repository, finalTree, store, receipt } = temporaryAuthority(t);
|
|
188
179
|
execFileSync("git", ["read-tree", finalTree], { cwd: repository });
|
|
@@ -589,42 +580,6 @@ test("scope child claim and parent gate journal publish atomically across faults
|
|
|
589
580
|
assert.equal(store.read(receipt.body.lineage_id).child_claims?.length, 1);
|
|
590
581
|
});
|
|
591
582
|
|
|
592
|
-
test("receipt gate cannot bypass independent dangerous-command safety", async () => {
|
|
593
|
-
let safetyCalls = 0;
|
|
594
|
-
let gateCalls = 0;
|
|
595
|
-
const safetyBlock = { block: true, reason: "dangerous command denied" };
|
|
596
|
-
const result = await __testing.enforceReviewGateAndCommandSafety(
|
|
597
|
-
"git push origin main",
|
|
598
|
-
() => {
|
|
599
|
-
gateCalls += 1;
|
|
600
|
-
return undefined;
|
|
601
|
-
},
|
|
602
|
-
async () => {
|
|
603
|
-
safetyCalls += 1;
|
|
604
|
-
return safetyBlock;
|
|
605
|
-
},
|
|
606
|
-
);
|
|
607
|
-
assert.deepEqual(result, safetyBlock);
|
|
608
|
-
assert.equal(safetyCalls, 1);
|
|
609
|
-
assert.equal(gateCalls, 0);
|
|
610
|
-
|
|
611
|
-
const gateBlock = { block: true, reason: "exact receipt required" };
|
|
612
|
-
const blocked = await __testing.enforceReviewGateAndCommandSafety(
|
|
613
|
-
"git push origin main",
|
|
614
|
-
() => {
|
|
615
|
-
gateCalls += 1;
|
|
616
|
-
return gateBlock;
|
|
617
|
-
},
|
|
618
|
-
async () => {
|
|
619
|
-
safetyCalls += 1;
|
|
620
|
-
return undefined;
|
|
621
|
-
},
|
|
622
|
-
);
|
|
623
|
-
assert.deepEqual(blocked, gateBlock);
|
|
624
|
-
assert.equal(safetyCalls, 2);
|
|
625
|
-
assert.equal(gateCalls, 1);
|
|
626
|
-
});
|
|
627
|
-
|
|
628
583
|
function releaseTarget(repository: GateRepository): GateTargetV1 {
|
|
629
584
|
return {
|
|
630
585
|
kind: GATE_TARGET_KIND.RELEASE,
|