gentle-pi 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -80
- 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-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 +54 -248
- package/assets/orchestrator-memory.md +2 -0
- package/assets/orchestrator.md +22 -38
- 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/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 +1778 -3673
- package/extensions/quiet-tools.ts +515 -32
- package/extensions/sdd-init.ts +4 -8
- package/lib/gentle-ai-renderer.ts +70 -0
- package/lib/model-routing-authority.ts +133 -0
- package/lib/native-review-cli.ts +372 -940
- package/lib/opaque-pi-reviewer-adapter.ts +284 -0
- package/lib/review-candidate-view.ts +341 -132
- package/lib/review-host-relay.ts +210 -68
- package/lib/review-integration-v2.ts +839 -307
- package/lib/review-last-event-controller.ts +35 -0
- package/lib/sdd-preflight.ts +177 -66
- 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 +322 -890
- package/runtime/review-integration-v2.mjs +789 -257
- 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 +12 -13
- package/skills/_shared/review-ledger-contract.md +8 -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/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/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 +820 -131
- package/tests/gentle-theme.test.ts +133 -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 +28 -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 +37 -35
- package/tests/provider-defect-handoff.test.ts +15 -118
- package/tests/quiet-tool-rendering.test.ts +1055 -28
- 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-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 +117 -82
- package/tests/review-host-relay.test.ts +242 -8
- package/tests/review-integration-v2-forward.test.ts +576 -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 +361 -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 +230 -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
|
@@ -6,13 +6,12 @@ import { join } from "node:path";
|
|
|
6
6
|
import test from "node:test";
|
|
7
7
|
import { __testing } from "../extensions/gentle-ai.ts";
|
|
8
8
|
import type { NativeReviewCli } from "../lib/native-review-cli.ts";
|
|
9
|
-
import { REVIEW_HOST_RELAY_FAILURE, REVIEW_HOST_RELAY_SUBMISSION_MISSING_MESSAGE, REVIEW_HOST_RELAY_UNAVAILABLE_MESSAGE, ReviewHostRelayError, type ReviewHostRelayRequest } from "../lib/review-host-relay.ts";
|
|
9
|
+
import { REVIEW_HOST_RELAY_FAILURE, REVIEW_HOST_RELAY_PI_TIMEOUT_ENV, REVIEW_HOST_RELAY_PI_TIMEOUT_MAX_MS, REVIEW_HOST_RELAY_SUBMISSION_MISSING_MESSAGE, REVIEW_HOST_RELAY_UNAVAILABLE_MESSAGE, ReviewHostRelayError, type ReviewHostRelayRequest } from "../lib/review-host-relay.ts";
|
|
10
10
|
import type { ReviewCaptureSubmissionV1, ReviewCollectInputV3, ReviewStatusV3 } from "../lib/review-integration-v2.ts";
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
// routes pi-slot capture inputs through the host relay ONLY when the
|
|
12
|
+
// One-slot capture routing: the host relay runs only when the selected
|
|
14
13
|
// provider-returned collect input carries the --materialize token. Every
|
|
15
|
-
// other
|
|
14
|
+
// other capture form stays untouched.
|
|
16
15
|
|
|
17
16
|
const SHA = `sha256:${"1".repeat(64)}`;
|
|
18
17
|
const TREE = "2".repeat(40);
|
|
@@ -69,8 +68,7 @@ function finalizeStatus(lineageId: string, inputs?: readonly ReviewCollectInputV
|
|
|
69
68
|
contract: "gentle-ai.review-integration/v2",
|
|
70
69
|
applicability: "current_target",
|
|
71
70
|
authority: { version: "compact-v2", lineageId, state: "reviewing", generation: 1, revision: SHA },
|
|
72
|
-
|
|
73
|
-
action: "finalize",
|
|
71
|
+
action: "stop",
|
|
74
72
|
replayability: "unknown",
|
|
75
73
|
targetIdentity: SHA,
|
|
76
74
|
projection: {
|
|
@@ -89,14 +87,13 @@ function finalizeStatus(lineageId: string, inputs?: readonly ReviewCollectInputV
|
|
|
89
87
|
},
|
|
90
88
|
candidates: [],
|
|
91
89
|
...(inputs === undefined ? {} : { nextTransition: { kind: "collect", reasonCode: "reviewer_results_required", collect: { inputs: [...inputs] } } }),
|
|
92
|
-
raw: { schema: "gentle-ai.review-integration.status/v3", action: "
|
|
90
|
+
raw: { schema: "gentle-ai.review-integration.status/v3", action: "stop", lineage_id: lineageId },
|
|
93
91
|
} as unknown as ReviewStatusV3;
|
|
94
92
|
}
|
|
95
93
|
|
|
96
94
|
interface RoutingHarness {
|
|
97
95
|
statusQueue: ReviewStatusV3[];
|
|
98
96
|
statusCalls: Array<{ cwd: string; lineageId?: string }>;
|
|
99
|
-
finalizeCalls: number;
|
|
100
97
|
native: NativeReviewCli;
|
|
101
98
|
}
|
|
102
99
|
|
|
@@ -104,19 +101,9 @@ function nativeHarness(statuses: readonly ReviewStatusV3[]): RoutingHarness {
|
|
|
104
101
|
const harness: RoutingHarness = {
|
|
105
102
|
statusQueue: [...statuses],
|
|
106
103
|
statusCalls: [],
|
|
107
|
-
finalizeCalls: 0,
|
|
108
104
|
native: undefined as unknown as NativeReviewCli,
|
|
109
105
|
};
|
|
110
106
|
harness.native = {
|
|
111
|
-
start: async () => { throw new Error("unexpected start"); },
|
|
112
|
-
finalize: async () => {
|
|
113
|
-
harness.finalizeCalls += 1;
|
|
114
|
-
return { lineageId: "relay-lineage", state: "approved", action: "approved", storeRevision: "r1" };
|
|
115
|
-
},
|
|
116
|
-
validate: async () => { throw new Error("unexpected validate"); },
|
|
117
|
-
bindSdd: async () => { throw new Error("unexpected bindSdd"); },
|
|
118
|
-
sddStatus: async () => ({ ready: false, artifactStore: "none", artifacts: {} as never, nextRecommended: "" }),
|
|
119
|
-
reviewStatus: async () => { throw new Error("unexpected reviewStatus"); },
|
|
120
107
|
targetStatus: async (request) => {
|
|
121
108
|
harness.statusCalls.push({ cwd: request.cwd, ...(request.lineageId === undefined ? {} : { lineageId: request.lineageId }) });
|
|
122
109
|
const next = harness.statusQueue.shift();
|
|
@@ -127,63 +114,41 @@ function nativeHarness(statuses: readonly ReviewStatusV3[]): RoutingHarness {
|
|
|
127
114
|
return harness;
|
|
128
115
|
}
|
|
129
116
|
|
|
130
|
-
function
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// finalize now forecasts its reviewer model runs and spends nothing until the
|
|
136
|
-
// caller acknowledges them (see review-relay-transport-agent.test.ts), so these
|
|
137
|
-
// routing cases authorize the run up front and keep asserting where the slots
|
|
138
|
-
// go. A case that must observe the forecast passes the acknowledgement off.
|
|
139
|
-
async function runFinalize(cwd: string, harness: RoutingHarness, lineageId: string, input: Record<string, unknown> = { reviewer_run_acknowledged: true }): Promise<Record<string, unknown>> {
|
|
140
|
-
return await __testing.executeReviewControllerOperation(
|
|
141
|
-
finalizeParameters(lineageId, input),
|
|
117
|
+
async function runCapture(cwd: string, harness: RoutingHarness, lineageId: string, input: Record<string, unknown> = { reviewerRunAcknowledged: true }): Promise<Record<string, unknown>> {
|
|
118
|
+
const selected = harness.statusQueue[0]?.nextTransition?.collect?.inputs[0];
|
|
119
|
+
if (selected === undefined) throw new Error("capture test requires one current collect input");
|
|
120
|
+
return await __testing.executeReviewCaptureOperation(
|
|
121
|
+
{ lineageId, collectBinding: JSON.stringify(selected), ...input },
|
|
142
122
|
cwd,
|
|
143
|
-
new Map(),
|
|
144
123
|
harness.native,
|
|
145
124
|
) as Record<string, unknown>;
|
|
146
125
|
}
|
|
147
126
|
|
|
148
|
-
test("materialize
|
|
127
|
+
test("one materialize binding routes exactly one provider slot through the host relay", async (t) => {
|
|
149
128
|
t.after(() => __testing.setReviewHostRelayRunnerForTesting());
|
|
150
129
|
const cwd = repository(t);
|
|
151
130
|
const lineageId = "relay-lineage";
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
relayCollectInput(lineageId, "review-reliability", 1),
|
|
155
|
-
];
|
|
156
|
-
const harness = nativeHarness([finalizeStatus(lineageId, inputs), finalizeStatus(lineageId)]);
|
|
131
|
+
const input = relayCollectInput(lineageId, "review-risk", 0);
|
|
132
|
+
const harness = nativeHarness([finalizeStatus(lineageId, [input])]);
|
|
157
133
|
const relayed: ReviewHostRelayRequest[] = [];
|
|
158
134
|
__testing.setReviewHostRelayRunnerForTesting(async (request: ReviewHostRelayRequest) => {
|
|
159
135
|
relayed.push(request);
|
|
160
136
|
return { promptByteLength: 64, resultByteLength: 32, submission: '{"admission_decision":"completed"}' };
|
|
161
137
|
});
|
|
162
138
|
|
|
163
|
-
const result = await
|
|
139
|
+
const result = await runCapture(cwd, harness, lineageId);
|
|
164
140
|
|
|
165
|
-
assert.equal(relayed.length,
|
|
166
|
-
assert.deepEqual(relayed[0]!.captureArgumentTokens,
|
|
167
|
-
// The provider-owned completing form reaches the relay verbatim.
|
|
141
|
+
assert.equal(relayed.length, 1);
|
|
142
|
+
assert.deepEqual(relayed[0]!.captureArgumentTokens, input.arguments.map((argument) => argument.token));
|
|
168
143
|
assert.deepEqual(relayed[0]!.submission, providerSubmission(lineageId, "review-risk", 0));
|
|
169
|
-
assert.
|
|
170
|
-
assert.
|
|
171
|
-
|
|
172
|
-
assert.equal(harness.finalizeCalls, 0, "the relay never invokes native finalize itself");
|
|
173
|
-
assert.equal(harness.statusCalls.length, 2, "STATUS is re-queried after all slots are captured");
|
|
174
|
-
assert.deepEqual(harness.statusCalls[1], { cwd, lineageId });
|
|
175
|
-
|
|
176
|
-
const hostRelay = result.host_relay as { transport: string; captured_slots: Array<Record<string, unknown>> };
|
|
177
|
-
assert.equal(hostRelay.transport, "pi_host_relay");
|
|
178
|
-
assert.equal(hostRelay.captured_slots.length, 2);
|
|
179
|
-
assert.deepEqual(hostRelay.captured_slots.map((slot) => slot.lens), ["review-risk", "review-reliability"]);
|
|
180
|
-
assert.equal(result.status, "in-progress");
|
|
144
|
+
assert.equal(harness.statusCalls.length, 1, "a nonterminal capture does not auto-follow STATUS");
|
|
145
|
+
assert.equal(result.status, "captured");
|
|
146
|
+
assert.equal((result.host_relay as { transport: string }).transport, "pi_host_relay");
|
|
181
147
|
});
|
|
182
148
|
|
|
183
|
-
test("Pi-authored review documents are rejected at the
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
// a Pi-authored reviewer document can never race a host-mediated slot.
|
|
149
|
+
test("Pi-authored review documents are rejected at the capture input boundary", async (t) => {
|
|
150
|
+
// The narrow capture schema rejects a caller-authored reviewer document
|
|
151
|
+
// before any relay launch or native call.
|
|
187
152
|
t.after(() => __testing.setReviewHostRelayRunnerForTesting());
|
|
188
153
|
const cwd = repository(t);
|
|
189
154
|
const lineageId = "relay-lineage";
|
|
@@ -195,11 +160,10 @@ test("Pi-authored review documents are rejected at the FINALIZE input boundary",
|
|
|
195
160
|
});
|
|
196
161
|
|
|
197
162
|
await assert.rejects(
|
|
198
|
-
() =>
|
|
199
|
-
/
|
|
163
|
+
() => runCapture(cwd, harness, lineageId, { review_result: { lens_results: [{ findings: [], evidence: ["reviewed"] }] } }),
|
|
164
|
+
/does not accept review_result/,
|
|
200
165
|
);
|
|
201
166
|
assert.equal(relayCalls, 0);
|
|
202
|
-
assert.equal(harness.finalizeCalls, 0);
|
|
203
167
|
});
|
|
204
168
|
|
|
205
169
|
test("an old binary reports the relay as unavailable without touching existing behavior", async (t) => {
|
|
@@ -211,14 +175,13 @@ test("an old binary reports the relay as unavailable without touching existing b
|
|
|
211
175
|
throw new ReviewHostRelayError(REVIEW_HOST_RELAY_FAILURE.RELAY_UNAVAILABLE, "materialize", REVIEW_HOST_RELAY_UNAVAILABLE_MESSAGE, { exitCode: 2, stderr: "flag provided but not defined: -materialize" });
|
|
212
176
|
});
|
|
213
177
|
|
|
214
|
-
const result = await
|
|
178
|
+
const result = await runCapture(cwd, harness, lineageId);
|
|
215
179
|
|
|
216
180
|
assert.equal(result.status, "blocked");
|
|
217
181
|
assert.equal(result.outcome, "pi-host-relay-unavailable");
|
|
218
182
|
assert.equal(result.reason, REVIEW_HOST_RELAY_UNAVAILABLE_MESSAGE);
|
|
219
183
|
assert.equal(result.mutation_performed, false);
|
|
220
184
|
assert.equal(result.mutation_outcome, "none");
|
|
221
|
-
assert.equal(harness.finalizeCalls, 0);
|
|
222
185
|
});
|
|
223
186
|
|
|
224
187
|
test("a handshake refusal surfaces the provider refusal verbatim through the controller envelope", async (t) => {
|
|
@@ -231,43 +194,116 @@ test("a handshake refusal surfaces the provider refusal verbatim through the con
|
|
|
231
194
|
throw new ReviewHostRelayError(REVIEW_HOST_RELAY_FAILURE.HANDSHAKE_REFUSED, "materialize", refusal, { exitCode: 1, stderr: refusal });
|
|
232
195
|
});
|
|
233
196
|
|
|
234
|
-
const result = await
|
|
197
|
+
const result = await runCapture(cwd, harness, lineageId);
|
|
235
198
|
|
|
236
199
|
assert.equal(result.outcome, "pi-host-relay-handshake-refused");
|
|
237
200
|
assert.equal(result.reason, refusal);
|
|
238
201
|
assert.equal(result.refusal, refusal);
|
|
239
202
|
});
|
|
240
203
|
|
|
241
|
-
test("a transport failure
|
|
204
|
+
test("a transport failure stops the selected capture without auto-follow", async (t) => {
|
|
242
205
|
t.after(() => __testing.setReviewHostRelayRunnerForTesting());
|
|
243
206
|
const cwd = repository(t);
|
|
244
207
|
const lineageId = "relay-lineage";
|
|
245
|
-
const
|
|
246
|
-
relayCollectInput(lineageId, "review-risk", 0),
|
|
247
|
-
relayCollectInput(lineageId, "review-reliability", 1),
|
|
248
|
-
];
|
|
249
|
-
const harness = nativeHarness([finalizeStatus(lineageId, inputs)]);
|
|
250
|
-
let calls = 0;
|
|
208
|
+
const harness = nativeHarness([finalizeStatus(lineageId, [relayCollectInput(lineageId, "review-risk", 0)])]);
|
|
251
209
|
__testing.setReviewHostRelayRunnerForTesting(async () => {
|
|
252
|
-
calls += 1;
|
|
253
|
-
if (calls === 1) return { promptByteLength: 8, resultByteLength: 8, submission: '{"admission_decision":"completed"}' };
|
|
254
210
|
throw new ReviewHostRelayError(REVIEW_HOST_RELAY_FAILURE.PI_FAILED, "pi", "pi subprocess failed", { exitCode: 4 });
|
|
255
211
|
});
|
|
256
212
|
|
|
257
|
-
const result = await
|
|
213
|
+
const result = await runCapture(cwd, harness, lineageId);
|
|
258
214
|
|
|
259
|
-
assert.equal(calls, 2);
|
|
260
215
|
assert.equal(result.status, "blocked");
|
|
261
216
|
assert.equal(result.outcome, "pi-host-relay-transport-failure");
|
|
262
217
|
assert.deepEqual(result.failure, { kind: "pi-failed", stage: "pi", exit_code: 4, timed_out: false });
|
|
263
|
-
assert.
|
|
264
|
-
assert.equal(result.mutation_performed, true);
|
|
265
|
-
assert.match(String(result.next_action), /Re-query negotiated STATUS/);
|
|
266
|
-
assert.match(String(result.next_action), /exact same bound slot/);
|
|
267
|
-
assert.equal(harness.finalizeCalls, 0);
|
|
218
|
+
assert.match(String(result.next_action), /fresh STATUS/);
|
|
268
219
|
assert.equal(harness.statusCalls.length, 1, "no automatic relaunch after transport failure");
|
|
269
220
|
});
|
|
270
221
|
|
|
222
|
+
// gentle-pi#522 / #524: a submission Go refused at admission is a proven
|
|
223
|
+
// non-mutation. The model must see the refusal text, mutation_outcome none,
|
|
224
|
+
// and a continuation for the reoffered slot, never an unknown outcome that the
|
|
225
|
+
// contract forbids replaying.
|
|
226
|
+
test("an admission refusal reaches the model as a proven non-mutation carrying the refusal and a continuation", async (t) => {
|
|
227
|
+
t.after(() => __testing.setReviewHostRelayRunnerForTesting());
|
|
228
|
+
const cwd = repository(t);
|
|
229
|
+
const lineageId = "relay-lineage";
|
|
230
|
+
const harness = nativeHarness([finalizeStatus(lineageId, [relayCollectInput(lineageId, "review-risk", 0)])]);
|
|
231
|
+
const refusal = "Error: reviewer artifact admission binding_mismatch: reviewer result echoed a different artifact subject: the rejected admission did not consume the lens slot, so re-run the lens and invoke gentle-ai review capture-result again on the same lineage with a result that echoes the binding's top-level subject_hash [invalid_request]\n";
|
|
232
|
+
__testing.setReviewHostRelayRunnerForTesting(async () => {
|
|
233
|
+
throw new ReviewHostRelayError(REVIEW_HOST_RELAY_FAILURE.SUBMISSION_REFUSED, "submit", refusal.trim(), { exitCode: 1, stderr: refusal, elapsedMs: 40, timeoutMs: 120_000, mutationOutcome: "none" });
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const result = await runCapture(cwd, harness, lineageId);
|
|
237
|
+
|
|
238
|
+
assert.equal(result.status, "blocked");
|
|
239
|
+
assert.equal(result.outcome, "pi-host-relay-transport-failure");
|
|
240
|
+
assert.deepEqual(result.failure, { kind: "submission-refused", stage: "submit", exit_code: 1, timed_out: false, elapsed_ms: 40, timeout_ms: 120_000, stderr: refusal });
|
|
241
|
+
assert.equal(result.reason, refusal.trim());
|
|
242
|
+
assert.equal(result.mutation_performed, false);
|
|
243
|
+
assert.equal(result.mutation_outcome, "none");
|
|
244
|
+
assert.match(String(result.next_action), /did not consume the lens slot/);
|
|
245
|
+
assert.match(String(result.next_action), /fresh STATUS/);
|
|
246
|
+
assert.equal(harness.statusCalls.length, 1, "a proven non-mutation needs no STATUS reconciliation and no relaunch");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("a submission whose outcome is genuinely indeterminate still reconciles through STATUS and carries its evidence", async (t) => {
|
|
250
|
+
t.after(() => __testing.setReviewHostRelayRunnerForTesting());
|
|
251
|
+
const cwd = repository(t);
|
|
252
|
+
const lineageId = "relay-lineage";
|
|
253
|
+
const pending = finalizeStatus(lineageId, [relayCollectInput(lineageId, "review-risk", 0)]);
|
|
254
|
+
const harness = nativeHarness([pending, pending]);
|
|
255
|
+
__testing.setReviewHostRelayRunnerForTesting(async () => {
|
|
256
|
+
throw new ReviewHostRelayError(REVIEW_HOST_RELAY_FAILURE.SUBMISSION_REFUSED, "submit", "gentle-ai capture submission exceeded its 120000ms bound after 120004ms", { exitCode: null, stderr: "", timedOut: true, elapsedMs: 120_004, timeoutMs: 120_000 });
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const result = await runCapture(cwd, harness, lineageId);
|
|
260
|
+
|
|
261
|
+
assert.equal(result.status, "reconciled");
|
|
262
|
+
assert.equal(result.outcome, "native-capture-outcome-unknown");
|
|
263
|
+
assert.deepEqual(result.failure, { kind: "submission-refused", stage: "submit", exit_code: null, timed_out: true, elapsed_ms: 120_004, timeout_ms: 120_000 });
|
|
264
|
+
assert.match(String(result.reason), /exceeded its 120000ms bound/);
|
|
265
|
+
assert.equal(harness.statusCalls.length, 2, "an indeterminate submission reconciles exactly once through STATUS");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("a relay timeout reports its one-slot measurements and no auto-follow", async (t) => {
|
|
269
|
+
t.after(() => __testing.setReviewHostRelayRunnerForTesting());
|
|
270
|
+
const cwd = repository(t);
|
|
271
|
+
const lineageId = "relay-lineage";
|
|
272
|
+
const harness = nativeHarness([finalizeStatus(lineageId, [relayCollectInput(lineageId, "review-risk", 0)])]);
|
|
273
|
+
__testing.setReviewHostRelayRunnerForTesting(async () => {
|
|
274
|
+
throw new ReviewHostRelayError(
|
|
275
|
+
REVIEW_HOST_RELAY_FAILURE.PI_TIMED_OUT,
|
|
276
|
+
"pi",
|
|
277
|
+
"pi reviewer subprocess exceeded the relay bound",
|
|
278
|
+
{ exitCode: null, timedOut: true, elapsedMs: 2_256_004, timeoutMs: 2_256_000 },
|
|
279
|
+
);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const result = await runCapture(cwd, harness, lineageId);
|
|
283
|
+
assert.equal(result.status, "blocked");
|
|
284
|
+
assert.equal(result.outcome, "pi-host-relay-timeout");
|
|
285
|
+
assert.deepEqual(result.failure, { kind: "pi-timed-out", stage: "pi", exit_code: null, timed_out: true, elapsed_ms: 2_256_004, timeout_ms: 2_256_000 });
|
|
286
|
+
assert.match(String(result.next_action), new RegExp(`${REVIEW_HOST_RELAY_PI_TIMEOUT_ENV}=<milliseconds>`));
|
|
287
|
+
assert.match(String(result.next_action), new RegExp(String(REVIEW_HOST_RELAY_PI_TIMEOUT_MAX_MS)));
|
|
288
|
+
assert.equal(harness.statusCalls.length, 1);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("a non-timeout transport failure keeps its generic continuation and now carries its measurements", async (t) => {
|
|
292
|
+
t.after(() => __testing.setReviewHostRelayRunnerForTesting());
|
|
293
|
+
const cwd = repository(t);
|
|
294
|
+
const lineageId = "relay-lineage";
|
|
295
|
+
const harness = nativeHarness([finalizeStatus(lineageId, [relayCollectInput(lineageId, "review-reliability", 0)])]);
|
|
296
|
+
__testing.setReviewHostRelayRunnerForTesting(async () => {
|
|
297
|
+
throw new ReviewHostRelayError(REVIEW_HOST_RELAY_FAILURE.PI_FAILED, "pi", "pi subprocess failed", { exitCode: 4, elapsedMs: 1_200, timeoutMs: 900_000 });
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const result = await runCapture(cwd, harness, lineageId);
|
|
301
|
+
|
|
302
|
+
assert.equal(result.outcome, "pi-host-relay-transport-failure");
|
|
303
|
+
assert.deepEqual(result.failure, { kind: "pi-failed", stage: "pi", exit_code: 4, timed_out: false, elapsed_ms: 1_200, timeout_ms: 900_000 });
|
|
304
|
+
assert.match(String(result.next_action), /fresh STATUS/);
|
|
305
|
+
});
|
|
306
|
+
|
|
271
307
|
test("materialize tokens without a provider submission fail closed as a typed contract mismatch", async (t) => {
|
|
272
308
|
t.after(() => __testing.setReviewHostRelayRunnerForTesting());
|
|
273
309
|
const cwd = repository(t);
|
|
@@ -279,7 +315,7 @@ test("materialize tokens without a provider submission fail closed as a typed co
|
|
|
279
315
|
return { promptByteLength: 1, resultByteLength: 1, submission: "{}" };
|
|
280
316
|
});
|
|
281
317
|
|
|
282
|
-
const result = await
|
|
318
|
+
const result = await runCapture(cwd, harness, lineageId);
|
|
283
319
|
|
|
284
320
|
assert.equal(relayCalls, 0, "the completing form is never synthesized, so nothing launches");
|
|
285
321
|
assert.equal(result.status, "blocked");
|
|
@@ -288,7 +324,6 @@ test("materialize tokens without a provider submission fail closed as a typed co
|
|
|
288
324
|
assert.equal(result.reason, REVIEW_HOST_RELAY_SUBMISSION_MISSING_MESSAGE);
|
|
289
325
|
assert.equal(result.mutation_performed, false);
|
|
290
326
|
assert.equal(result.mutation_outcome, "none");
|
|
291
|
-
assert.equal(harness.finalizeCalls, 0);
|
|
292
327
|
assert.equal(harness.statusCalls.length, 1, "no relaunch and no post-capture STATUS after the contract mismatch");
|
|
293
328
|
});
|
|
294
329
|
|
|
@@ -309,7 +344,7 @@ test("collect inputs without the provider-issued materialize token never reach t
|
|
|
309
344
|
// The existing lane may fail on the synthetic projection; only the relay
|
|
310
345
|
// boundary is under test here: it must never be consulted.
|
|
311
346
|
try {
|
|
312
|
-
await
|
|
347
|
+
await runCapture(cwd, harness, lineageId);
|
|
313
348
|
} catch {
|
|
314
349
|
// Existing-lane behavior for this synthetic fixture is out of scope.
|
|
315
350
|
}
|