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
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import { __testing } from "../extensions/gentle-ai.ts";
|
|
6
|
+
import { reconcileUnknownReviewLastEventCapture } from "../lib/review-last-event-controller.ts";
|
|
7
|
+
import * as nativeReviewCliModule from "../lib/native-review-cli.ts";
|
|
8
|
+
import type { NativeReviewCli } from "../lib/native-review-cli.ts";
|
|
9
|
+
import {
|
|
10
|
+
decodeReviewLastEventClosureV1,
|
|
11
|
+
decodeReviewStartV3,
|
|
12
|
+
type ReviewCollectInputV3,
|
|
13
|
+
type ReviewStatusV3,
|
|
14
|
+
} from "../lib/review-integration-v2.ts";
|
|
15
|
+
|
|
16
|
+
const SHA = `sha256:${"a".repeat(64)}`;
|
|
17
|
+
const TREE = "b".repeat(40);
|
|
18
|
+
const CAPTURED_FIXTURES = join(process.cwd(), "tests", "fixtures", "devbinary");
|
|
19
|
+
|
|
20
|
+
function captured(name: string): unknown {
|
|
21
|
+
return JSON.parse(readFileSync(join(CAPTURED_FIXTURES, name), "utf8"));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function closure(operation: string, lineageId: string): Record<string, unknown> {
|
|
25
|
+
return {
|
|
26
|
+
schema: "gentle-ai.review-last-event-closure/v1",
|
|
27
|
+
operation,
|
|
28
|
+
lineage_id: lineageId,
|
|
29
|
+
state: operation === "review.capture-correction-plan" ? "correction_required" : "approved",
|
|
30
|
+
...(operation === "review.capture-correction-plan"
|
|
31
|
+
? { target_identity: SHA, request_hash: SHA, correction_lines: 2 }
|
|
32
|
+
: { action: "native last event closed the review" }),
|
|
33
|
+
store_revision: SHA,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function bindingArguments(lineageId: string, suffix = "0"): ReviewCollectInputV3["arguments"] {
|
|
38
|
+
return [
|
|
39
|
+
{ name: "lineage", value: lineageId, token: `--lineage=${lineageId}` },
|
|
40
|
+
{ name: "expected-revision", value: SHA, token: `--expected-revision=${SHA}` },
|
|
41
|
+
{ name: "target", value: SHA, token: `--target=${SHA}` },
|
|
42
|
+
{ name: "repository-context", value: `rctx1_${"c".repeat(64)}`, token: `--repository-context=rctx1_${"c".repeat(64)}` },
|
|
43
|
+
{ name: "lens", value: `review-risk-${suffix}`, token: `--lens=review-risk-${suffix}` },
|
|
44
|
+
{ name: "order", value: suffix, token: `--order=${suffix}` },
|
|
45
|
+
{ name: "subject-hash", value: SHA, token: `--subject-hash=${SHA}` },
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function materializeInput(lineageId: string, suffix = "0"): ReviewCollectInputV3 {
|
|
50
|
+
const arguments_ = [
|
|
51
|
+
...bindingArguments(lineageId, suffix),
|
|
52
|
+
{ name: "agent", value: "pi", token: "--agent=pi" },
|
|
53
|
+
{ name: "materialize", value: "true", token: "--materialize=true" },
|
|
54
|
+
];
|
|
55
|
+
return {
|
|
56
|
+
name: "reviewer_result",
|
|
57
|
+
schema: "https://gentle-ai.dev/schema/review/reviewer/v1",
|
|
58
|
+
captureOperation: "review.capture-result",
|
|
59
|
+
arguments: arguments_,
|
|
60
|
+
artifactSubject: {
|
|
61
|
+
schema: "gentle-ai.review-artifact-subject/v2",
|
|
62
|
+
subjectHash: SHA,
|
|
63
|
+
lineageId,
|
|
64
|
+
authorityRevision: SHA,
|
|
65
|
+
targetIdentity: SHA,
|
|
66
|
+
baseTree: TREE,
|
|
67
|
+
candidateTree: TREE,
|
|
68
|
+
changedPathManifestSha256: SHA,
|
|
69
|
+
lens: `review-risk-${suffix}`,
|
|
70
|
+
selectedOrder: Number(suffix),
|
|
71
|
+
},
|
|
72
|
+
submission: {
|
|
73
|
+
operationToken: "capture-result",
|
|
74
|
+
argumentTokens: [...bindingArguments(lineageId, suffix).map((argument) => argument.token!), "--input={{value}}"],
|
|
75
|
+
values: [{ slot: "reviewer_result", domain: "artifact_path_or_stdin", substitutionLocation: 7 }],
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function correctionPlanInput(lineageId: string): ReviewCollectInputV3 {
|
|
81
|
+
return {
|
|
82
|
+
name: "correction_plan",
|
|
83
|
+
schema: "gentle-ai.review-correction-plan/v1",
|
|
84
|
+
captureOperation: "review.capture-correction-plan",
|
|
85
|
+
arguments: bindingArguments(lineageId),
|
|
86
|
+
submission: {
|
|
87
|
+
operationToken: "capture-correction-plan",
|
|
88
|
+
argumentTokens: [...bindingArguments(lineageId).map((argument) => argument.token!), "--correction-lines={{value}}"],
|
|
89
|
+
values: [{ slot: "correction_lines", domain: "positive_integer", substitutionLocation: 7, minimum: 2, maximum: 3 }],
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function roleInput(lineageId: string, operation: "review.capture-refuter" | "review.capture-validation"): ReviewCollectInputV3 {
|
|
95
|
+
return {
|
|
96
|
+
name: operation === "review.capture-refuter" ? "provider_refuter" : "targeted_validator",
|
|
97
|
+
schema: "gentle-ai.review-provider-role/v1",
|
|
98
|
+
captureOperation: operation,
|
|
99
|
+
arguments: [
|
|
100
|
+
...bindingArguments(lineageId),
|
|
101
|
+
{ name: "agent", value: "pi", token: "--agent=pi" },
|
|
102
|
+
{ name: "execute", value: "true", token: "--execute=true" },
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function status(lineageId: string, inputs: readonly ReviewCollectInputV3[]): ReviewStatusV3 {
|
|
108
|
+
return {
|
|
109
|
+
contract: "gentle-ai.review-integration/v2",
|
|
110
|
+
applicability: "current_target",
|
|
111
|
+
authority: { version: "compact-v2", lineageId, state: "reviewing", generation: 1, revision: SHA },
|
|
112
|
+
action: "stop",
|
|
113
|
+
replayability: "not_replayable",
|
|
114
|
+
targetIdentity: SHA,
|
|
115
|
+
projection: {
|
|
116
|
+
schema: "gentle-ai.review-candidate-projection/v1",
|
|
117
|
+
kind: "current-changes",
|
|
118
|
+
projection: "workspace",
|
|
119
|
+
baseTree: TREE,
|
|
120
|
+
initialReviewTree: TREE,
|
|
121
|
+
currentCandidateTree: TREE,
|
|
122
|
+
pathsDigest: SHA,
|
|
123
|
+
paths: ["app.ts"],
|
|
124
|
+
intendedUntracked: [],
|
|
125
|
+
intendedUntrackedProof: SHA,
|
|
126
|
+
initialSnapshotIdentity: SHA,
|
|
127
|
+
currentSnapshotIdentity: SHA,
|
|
128
|
+
},
|
|
129
|
+
candidates: [],
|
|
130
|
+
nextTransition: { kind: "collect", reasonCode: "capture_required", collect: { inputs: [...inputs] } },
|
|
131
|
+
raw: { schema: "gentle-ai.review-integration.status/v5" },
|
|
132
|
+
} as unknown as ReviewStatusV3;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function capture(lineageId: string, input: ReviewCollectInputV3, native: NativeReviewCli, extras: Record<string, unknown> = {}): Promise<Record<string, unknown>> {
|
|
136
|
+
return __testing.executeReviewCaptureOperation({ lineageId, collectBinding: JSON.stringify(input), ...extras }, process.cwd(), native);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
test("strictly decodes the real zero-lens closed START and every last-event closure", () => {
|
|
140
|
+
const start = decodeReviewStartV3(captured("start-v3-zero-lens-closed.captured.json"));
|
|
141
|
+
assert.equal(start.action, "closed");
|
|
142
|
+
assert.equal(start.state, "approved");
|
|
143
|
+
assert.equal(start.lensesRequired, false);
|
|
144
|
+
assert.deepEqual(start.selectedLenses, []);
|
|
145
|
+
for (const [name, operation, state] of [
|
|
146
|
+
["last-event-capture-result-approved.captured.json", "review/capture-result", "approved"],
|
|
147
|
+
["last-event-capture-result-correction-required.captured.json", "review/capture-result", "correction_required"],
|
|
148
|
+
["last-event-capture-correction-plan.captured.json", "review.capture-correction-plan", "correction_required"],
|
|
149
|
+
["last-event-capture-refuter-correction-required.captured.json", "review.capture-refuter", "correction_required"],
|
|
150
|
+
["last-event-capture-refuter-approved.captured.json", "review.capture-refuter", "approved"],
|
|
151
|
+
["last-event-capture-validation-approved.captured.json", "review/capture-validation", "approved"],
|
|
152
|
+
] as const) {
|
|
153
|
+
const decoded = decodeReviewLastEventClosureV1(captured(name));
|
|
154
|
+
assert.equal(decoded.operation, operation, name);
|
|
155
|
+
assert.equal(decoded.state, state, name);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("retired legacy client no longer exposes a FINALIZE route", () => {
|
|
160
|
+
assert.equal("NativeReviewCliV214" in nativeReviewCliModule, false);
|
|
161
|
+
assert.equal("NativeReviewCliV213" in nativeReviewCliModule, false);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("one exact offered materialize binding executes only its selected slot and never follows STATUS", async (t) => {
|
|
165
|
+
t.after(() => __testing.setReviewHostRelayRunnerForTesting());
|
|
166
|
+
const lineageId = "one-slot-lineage";
|
|
167
|
+
const selected = materializeInput(lineageId, "0");
|
|
168
|
+
const ignored = materializeInput(lineageId, "1");
|
|
169
|
+
let statusCalls = 0;
|
|
170
|
+
const native = {
|
|
171
|
+
targetStatus: async () => {
|
|
172
|
+
statusCalls += 1;
|
|
173
|
+
return status(lineageId, [selected, ignored]);
|
|
174
|
+
},
|
|
175
|
+
} as unknown as NativeReviewCli;
|
|
176
|
+
const relayed: unknown[] = [];
|
|
177
|
+
__testing.setReviewHostRelayRunnerForTesting(async (request) => {
|
|
178
|
+
relayed.push(request);
|
|
179
|
+
return { promptByteLength: 12, resultByteLength: 8, submission: "{\"admission_decision\":\"completed\"}" };
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const result = await capture(lineageId, selected, native, { reviewerRunAcknowledged: true });
|
|
183
|
+
assert.equal(statusCalls, 1);
|
|
184
|
+
assert.equal(relayed.length, 1);
|
|
185
|
+
assert.equal(result.status, "captured");
|
|
186
|
+
assert.equal(result.outcome, "native-reviewer-result-captured");
|
|
187
|
+
assert.deepEqual((relayed[0] as { captureArgumentTokens: readonly string[] }).captureArgumentTokens, selected.arguments.map((argument) => argument.token));
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("malformed, stale, duplicate, and incomplete collect bindings reject before capture mutation", async (t) => {
|
|
191
|
+
t.after(() => __testing.setReviewHostRelayRunnerForTesting());
|
|
192
|
+
const lineageId = "rejected-binding";
|
|
193
|
+
const input = materializeInput(lineageId);
|
|
194
|
+
let statusCalls = 0;
|
|
195
|
+
let launches = 0;
|
|
196
|
+
const native = { targetStatus: async () => { statusCalls += 1; return status(lineageId, [input]); } } as unknown as NativeReviewCli;
|
|
197
|
+
__testing.setReviewHostRelayRunnerForTesting(async () => { launches += 1; return { promptByteLength: 1, resultByteLength: 1, submission: "{}" }; });
|
|
198
|
+
|
|
199
|
+
await assert.rejects(() => __testing.executeReviewCaptureOperation({ lineageId, collectBinding: "{" }, process.cwd(), native), /collectBinding is not valid JSON/);
|
|
200
|
+
const stale = await __testing.executeReviewCaptureOperation({ lineageId, collectBinding: JSON.stringify(materializeInput("other")) }, process.cwd(), native);
|
|
201
|
+
const duplicate = await __testing.executeReviewCaptureOperation({ lineageId, collectBinding: JSON.stringify(input) }, process.cwd(), {
|
|
202
|
+
targetStatus: async () => { statusCalls += 1; return status(lineageId, [input, input]); },
|
|
203
|
+
} as unknown as NativeReviewCli);
|
|
204
|
+
assert.equal(stale.outcome, "capture-binding-rejected");
|
|
205
|
+
assert.equal(duplicate.outcome, "capture-binding-rejected");
|
|
206
|
+
|
|
207
|
+
const missingLineageToken: ReviewCollectInputV3 = { ...input, arguments: input.arguments.filter((argument) => argument.name !== "lineage") };
|
|
208
|
+
const missingTargetToken: ReviewCollectInputV3 = { ...input, arguments: input.arguments.filter((argument) => argument.name !== "target") };
|
|
209
|
+
const mismatchedLineageToken: ReviewCollectInputV3 = {
|
|
210
|
+
...input,
|
|
211
|
+
arguments: input.arguments.map((argument) => argument.name === "lineage" ? { ...argument, value: "other-lineage" } : argument),
|
|
212
|
+
};
|
|
213
|
+
const mismatchedTargetToken: ReviewCollectInputV3 = {
|
|
214
|
+
...input,
|
|
215
|
+
arguments: input.arguments.map((argument) => argument.name === "target" ? { ...argument, value: `sha256:${"d".repeat(64)}` } : argument),
|
|
216
|
+
};
|
|
217
|
+
const missingAuthorityLineage = { ...status(lineageId, [input]), authority: undefined } as ReviewStatusV3;
|
|
218
|
+
const emptyAuthorityLineage = {
|
|
219
|
+
...status(lineageId, [input]),
|
|
220
|
+
authority: { ...status(lineageId, [input]).authority!, lineageId: "" },
|
|
221
|
+
} as unknown as ReviewStatusV3;
|
|
222
|
+
const missingStatusTarget = { ...status(lineageId, [missingTargetToken]), targetIdentity: undefined } as unknown as ReviewStatusV3;
|
|
223
|
+
const emptyStatusTarget = { ...status(lineageId, [input]), targetIdentity: "" } as unknown as ReviewStatusV3;
|
|
224
|
+
for (const [offeredInput, currentStatus] of [
|
|
225
|
+
[missingLineageToken, status(lineageId, [missingLineageToken])],
|
|
226
|
+
[missingTargetToken, status(lineageId, [missingTargetToken])],
|
|
227
|
+
[mismatchedLineageToken, status(lineageId, [mismatchedLineageToken])],
|
|
228
|
+
[mismatchedTargetToken, status(lineageId, [mismatchedTargetToken])],
|
|
229
|
+
[input, missingAuthorityLineage],
|
|
230
|
+
[input, emptyAuthorityLineage],
|
|
231
|
+
[missingTargetToken, missingStatusTarget],
|
|
232
|
+
[input, emptyStatusTarget],
|
|
233
|
+
] as const) {
|
|
234
|
+
const rejected = await __testing.executeReviewCaptureOperation(
|
|
235
|
+
{ lineageId, collectBinding: JSON.stringify(offeredInput), reviewerRunAcknowledged: true },
|
|
236
|
+
process.cwd(),
|
|
237
|
+
{ targetStatus: async () => currentStatus } as unknown as NativeReviewCli,
|
|
238
|
+
);
|
|
239
|
+
assert.equal(rejected.outcome, "capture-binding-rejected");
|
|
240
|
+
}
|
|
241
|
+
assert.equal(launches, 0);
|
|
242
|
+
assert.equal(statusCalls, 2);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("forecast spends zero and acknowledgement runs exactly one materialize reviewer", async (t) => {
|
|
246
|
+
t.after(() => __testing.setReviewHostRelayRunnerForTesting());
|
|
247
|
+
const lineageId = "forecast-lineage";
|
|
248
|
+
const input = materializeInput(lineageId);
|
|
249
|
+
let statusCalls = 0;
|
|
250
|
+
let launches = 0;
|
|
251
|
+
const native = { targetStatus: async () => { statusCalls += 1; return status(lineageId, [input]); } } as unknown as NativeReviewCli;
|
|
252
|
+
__testing.setReviewHostRelayRunnerForTesting(async () => { launches += 1; return { promptByteLength: 1, resultByteLength: 1, submission: "{}" }; });
|
|
253
|
+
|
|
254
|
+
const forecast = await capture(lineageId, input, native);
|
|
255
|
+
const acknowledged = await capture(lineageId, input, native, { reviewerRunAcknowledged: true });
|
|
256
|
+
assert.equal(forecast.outcome, "reviewer-model-run-forecast");
|
|
257
|
+
assert.equal((forecast.cost_forecast as { model_runs: number }).model_runs, 1);
|
|
258
|
+
assert.equal(acknowledged.status, "captured");
|
|
259
|
+
assert.equal(launches, 1);
|
|
260
|
+
assert.equal(statusCalls, 2);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("correction-plan preserves exact provider tokens and bounds without a follow-up lifecycle call", async () => {
|
|
264
|
+
const lineageId = "correction-plan";
|
|
265
|
+
const input = correctionPlanInput(lineageId);
|
|
266
|
+
let statusCalls = 0;
|
|
267
|
+
const requests: Array<{ argumentTokens: readonly string[]; correctionLines: number }> = [];
|
|
268
|
+
const native = {
|
|
269
|
+
targetStatus: async () => { statusCalls += 1; return status(lineageId, [input]); },
|
|
270
|
+
captureCorrectionPlan: async (request: { argumentTokens: readonly string[]; correctionLines: number }) => {
|
|
271
|
+
requests.push(request);
|
|
272
|
+
return decodeReviewLastEventClosureV1(closure("review.capture-correction-plan", lineageId));
|
|
273
|
+
},
|
|
274
|
+
} as unknown as NativeReviewCli;
|
|
275
|
+
const missing = await capture(lineageId, input, native);
|
|
276
|
+
const outOfBounds = await capture(lineageId, input, native, { correctionLines: 4 });
|
|
277
|
+
const completed = await capture(lineageId, input, native, { correctionLines: 2 });
|
|
278
|
+
assert.equal(missing.outcome, "correction-lines-required");
|
|
279
|
+
assert.equal(outOfBounds.outcome, "capture-binding-rejected");
|
|
280
|
+
assert.equal(completed.status, "closed");
|
|
281
|
+
assert.equal(requests.length, 1);
|
|
282
|
+
assert.deepEqual(requests[0]!.argumentTokens, input.submission!.argumentTokens);
|
|
283
|
+
assert.equal(requests[0]!.correctionLines, 2);
|
|
284
|
+
assert.equal(statusCalls, 3);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("refuter and validation each execute one self-contained provider vector", async () => {
|
|
288
|
+
for (const operation of ["review.capture-refuter", "review.capture-validation"] as const) {
|
|
289
|
+
const lineageId = `role-${operation}`;
|
|
290
|
+
const input = roleInput(lineageId, operation);
|
|
291
|
+
const requests: Array<{ captureOperation: string; argumentTokens: readonly string[] }> = [];
|
|
292
|
+
const native = {
|
|
293
|
+
targetStatus: async () => status(lineageId, [input]),
|
|
294
|
+
captureProviderRole: async (request: { captureOperation: string; argumentTokens: readonly string[] }) => {
|
|
295
|
+
requests.push(request);
|
|
296
|
+
return { schema: "gentle-ai.review-provider-role-capture/v1", lineageId, targetIdentity: SHA, role: operation, captured: true };
|
|
297
|
+
},
|
|
298
|
+
} as unknown as NativeReviewCli;
|
|
299
|
+
const result = await capture(lineageId, input, native);
|
|
300
|
+
assert.equal(result.status, "captured");
|
|
301
|
+
assert.equal(requests.length, 1);
|
|
302
|
+
assert.equal(requests[0]!.captureOperation, operation);
|
|
303
|
+
assert.deepEqual(requests[0]!.argumentTokens, input.arguments.map((argument) => argument.token!));
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("terminal capture closes directly, nonterminal capture does not auto-follow, and unknown mutation reconciles once", async (t) => {
|
|
308
|
+
t.after(() => __testing.setReviewHostRelayRunnerForTesting());
|
|
309
|
+
const lineageId = "closure-behavior";
|
|
310
|
+
const input = materializeInput(lineageId);
|
|
311
|
+
let statusCalls = 0;
|
|
312
|
+
const native = { targetStatus: async () => { statusCalls += 1; return status(lineageId, [input]); } } as unknown as NativeReviewCli;
|
|
313
|
+
__testing.setReviewHostRelayRunnerForTesting(async () => ({ promptByteLength: 1, resultByteLength: 1, submission: JSON.stringify(closure("review/capture-result", lineageId)) }));
|
|
314
|
+
const terminal = await capture(lineageId, input, native, { reviewerRunAcknowledged: true });
|
|
315
|
+
assert.equal(terminal.status, "closed");
|
|
316
|
+
assert.equal(statusCalls, 1, "terminal closure performs no post-success STATUS");
|
|
317
|
+
|
|
318
|
+
statusCalls = 0;
|
|
319
|
+
let captureCalls = 0;
|
|
320
|
+
const correction = correctionPlanInput(lineageId);
|
|
321
|
+
const ambiguousNative = {
|
|
322
|
+
targetStatus: async () => { statusCalls += 1; return status(lineageId, [correction]); },
|
|
323
|
+
captureCorrectionPlan: async () => {
|
|
324
|
+
captureCalls += 1;
|
|
325
|
+
throw Object.assign(new Error("response lost"), { mutationOutcome: "unknown", nextAction: "review.status" });
|
|
326
|
+
},
|
|
327
|
+
} as unknown as NativeReviewCli;
|
|
328
|
+
const reconciled = await capture(lineageId, correction, ambiguousNative, { correctionLines: 2 });
|
|
329
|
+
assert.equal(reconciled.status, "reconciled");
|
|
330
|
+
assert.equal(captureCalls, 1);
|
|
331
|
+
assert.equal(statusCalls, 2, "only initial STATUS plus one ambiguity reconciliation");
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test("unknown-capture reconciliation validates the target-scoped lineage without replay", async () => {
|
|
335
|
+
const calls: Array<Record<string, unknown>> = [];
|
|
336
|
+
const native = {
|
|
337
|
+
targetStatus: async (request: Record<string, unknown>) => {
|
|
338
|
+
calls.push(request);
|
|
339
|
+
assert.equal(request.lineageId, "reconcile-lineage");
|
|
340
|
+
return { targetIdentity: SHA, authority: { lineageId: "reconcile-lineage" } } as ReviewStatusV3;
|
|
341
|
+
},
|
|
342
|
+
} as unknown as NativeReviewCli;
|
|
343
|
+
const result = await reconcileUnknownReviewLastEventCapture(native, "/repo", { lineageId: "reconcile-lineage", targetIdentity: SHA });
|
|
344
|
+
await reconcileUnknownReviewLastEventCapture(native, "/repo", { lineageId: "reconcile-lineage", targetIdentity: SHA }, { baseRef: "refs/heads/main", committedOnly: true });
|
|
345
|
+
assert.deepEqual(calls, [{ cwd: "/repo", lineageId: "reconcile-lineage" }, { cwd: "/repo", lineageId: "reconcile-lineage", baseRef: "refs/heads/main", committedOnly: true }]);
|
|
346
|
+
assert.equal(result.targetIdentity, SHA);
|
|
347
|
+
let launches = 0;
|
|
348
|
+
const strictNative = new nativeReviewCliModule.NativeReviewCliV216(async () => { launches += 1; throw new Error("must not launch"); }, "/package/.gentle-ai/gentle-ai");
|
|
349
|
+
for (const selector of [{ committedOnly: true }, { baseRef: "refs/heads/main" }, { baseRef: "refs/heads/main", committedOnly: false }] as unknown as readonly Parameters<typeof reconcileUnknownReviewLastEventCapture>[3][]) await assert.rejects(() => reconcileUnknownReviewLastEventCapture(strictNative, "/repo", { lineageId: "reconcile-lineage", targetIdentity: SHA }, selector));
|
|
350
|
+
assert.equal(launches, 0);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// One approved terminal closure carrying the exact continuation the provider
|
|
354
|
+
// renders: closed positional arguments, the single approved precondition, and a
|
|
355
|
+
// binding that names the candidate being burned.
|
|
356
|
+
function approvedClosureWithAcknowledgement(): Record<string, unknown> {
|
|
357
|
+
const lineageId = "review-acknowledgement-fixture";
|
|
358
|
+
const body = closure("review/capture-result", lineageId);
|
|
359
|
+
body.acknowledgement = {
|
|
360
|
+
operation: "review.acknowledge-approved",
|
|
361
|
+
command: "gentle-ai review acknowledge-approved",
|
|
362
|
+
arguments: [
|
|
363
|
+
{ name: "cwd", value: "/repo", token: "--cwd=/repo" },
|
|
364
|
+
{ name: "lineage", value: lineageId, token: `--lineage=${lineageId}` },
|
|
365
|
+
{ name: "target", value: SHA, token: `--target=${SHA}` },
|
|
366
|
+
{ name: "expected-revision", value: SHA, token: `--expected-revision=${SHA}` },
|
|
367
|
+
{ name: "token", value: "a".repeat(64), token: `--token=${"a".repeat(64)}` },
|
|
368
|
+
],
|
|
369
|
+
preconditions: [{ name: "state", value: "approved" }],
|
|
370
|
+
binding: { lineage_id: lineageId, revision: SHA, target_identity: SHA },
|
|
371
|
+
};
|
|
372
|
+
return body;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
test("the approved acknowledgement fixture decodes as a runnable continuation", () => {
|
|
376
|
+
const decoded = decodeReviewLastEventClosureV1(approvedClosureWithAcknowledgement());
|
|
377
|
+
assert.equal(decoded.acknowledgement?.operation, "review.acknowledge-approved");
|
|
378
|
+
assert.equal(decoded.acknowledgementUndecodable, undefined);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("an approved acknowledgement must prove the target its relayed token burns", () => {
|
|
382
|
+
const closure = approvedClosureWithAcknowledgement();
|
|
383
|
+
const acknowledgement = closure.acknowledgement as Record<string, unknown>;
|
|
384
|
+
const args = (acknowledgement.arguments as Record<string, unknown>[]).map((argument) => ({ ...argument }));
|
|
385
|
+
// The relayed token now names a different candidate than the binding does.
|
|
386
|
+
const decoyTarget = `sha256:${"b".repeat(64)}`;
|
|
387
|
+
args[2] = { name: "target", value: decoyTarget, token: `--target=${decoyTarget}` };
|
|
388
|
+
acknowledgement.arguments = args;
|
|
389
|
+
assert.throws(
|
|
390
|
+
() => decodeReviewLastEventClosureV1(closure),
|
|
391
|
+
/acknowledgement target argument does not match its binding/,
|
|
392
|
+
"a relayed target the binding does not commit to must be refused",
|
|
393
|
+
);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
test("an unreadable acknowledgement degrades the continuation, never the approval", () => {
|
|
397
|
+
const closure = approvedClosureWithAcknowledgement();
|
|
398
|
+
// One extra provider-side argument is enough to miss the closed shape.
|
|
399
|
+
const acknowledgement = closure.acknowledgement as Record<string, unknown>;
|
|
400
|
+
acknowledgement.arguments = [
|
|
401
|
+
...(acknowledgement.arguments as unknown[]),
|
|
402
|
+
{ name: "future", value: "1", token: "--future=1" },
|
|
403
|
+
];
|
|
404
|
+
const decoded = decodeReviewLastEventClosureV1(closure);
|
|
405
|
+
assert.equal(decoded.state, "approved", "the approval outcome must survive an unreadable continuation");
|
|
406
|
+
assert.equal(decoded.acknowledgement, undefined, "an unreadable continuation must not be offered as runnable");
|
|
407
|
+
assert.equal(decoded.acknowledgementUndecodable, true, "the host must be told the continuation exists and could not be read");
|
|
408
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import test from "node:test";
|
|
5
5
|
import { REVIEW_LENS_PARITY_PATTERNS } from "./support/review-lens-parity.ts";
|
|
6
6
|
|
|
@@ -85,7 +85,7 @@ const FIX_PATTERNS = [
|
|
|
85
85
|
/Do not add findings, alter frozen claims, authorize transitions, deliver, publish, or start another actor\./,
|
|
86
86
|
] as const;
|
|
87
87
|
|
|
88
|
-
test("canonical contract defines compact risk, causal admission, correction, CAS, compatibility, and
|
|
88
|
+
test("canonical contract defines compact risk, causal admission, correction, CAS, compatibility, and the delivery boundary", () => {
|
|
89
89
|
const content = read(CANONICAL);
|
|
90
90
|
assertMatches(CANONICAL, content, [
|
|
91
91
|
/start -> finalize -> validate/,
|
|
@@ -101,29 +101,25 @@ test("canonical contract defines compact risk, causal admission, correction, CAS
|
|
|
101
101
|
/original budget/i,
|
|
102
102
|
/frozen findings and genesis scope/i,
|
|
103
103
|
/content-derived revisions, compare-and-swap replacement, exact retry idempotency/i,
|
|
104
|
-
/graph-v1 ordinary lineages remain readable
|
|
104
|
+
/graph-v1 ordinary lineages remain readable for compatibility but reject new mutation/i,
|
|
105
105
|
/Legacy graph bundle export\/import is retired/i,
|
|
106
106
|
/Judgment Day remains mutable on graph-v1/i,
|
|
107
|
-
|
|
108
|
-
/
|
|
109
|
-
/
|
|
110
|
-
/
|
|
111
|
-
/
|
|
107
|
+
/--agent=pi --materialize=true/,
|
|
108
|
+
/provider-owned submission form/i,
|
|
109
|
+
/self-contained authority-advancing vectors/i,
|
|
110
|
+
/Commit, push, pull-request creation, and release creation are not RDD gates/i,
|
|
111
|
+
/Review outcomes and receipt state are informational and never authorize, consume, rewrite, or block a Bash delivery command/i,
|
|
112
|
+
/Pi does not inspect RDD mode or native authority for those commands/i,
|
|
113
|
+
/Review transactions, validation, and SDD never perform delivery commands themselves/i,
|
|
112
114
|
/local orchestrator and same-user process are trusted/i,
|
|
113
115
|
/reviewer and validator outputs remain semantically untrusted/i,
|
|
114
116
|
/do not report.*trusted local orchestrator.*security finding/i,
|
|
115
117
|
/untrusted repository content.*malformed inputs.*stale authority.*path drift.*external callers/i,
|
|
116
118
|
...JUDGMENT_DAY_PATTERNS,
|
|
117
119
|
]);
|
|
118
|
-
assert.match(read(README), /
|
|
119
|
-
assert.doesNotMatch(read(README), /
|
|
120
|
-
assert.
|
|
121
|
-
assert.match(read(README), /Residual gap \(separate follow-up\): native first-push authorization remains unsupported until Pi has a persisted explicit advertised-base source\./);
|
|
122
|
-
const lifecycleSpec = read("openspec/specs/review-transaction/spec.md");
|
|
123
|
-
assert.match(lifecycleSpec, /split fetch\/push[\s\S]*upstream contract limitation/i);
|
|
124
|
-
assert.match(lifecycleSpec, /allow response MUST return the exact requested gate/i);
|
|
125
|
-
assert.match(lifecycleSpec, /non-authorizing denial MAY return an empty gate[\s\S]*pre_pr_boundary/i);
|
|
126
|
-
assert.match(lifecycleSpec, /one aggregate bash-time deadline/i);
|
|
120
|
+
assert.match(read(README), /Review outcomes and receipt state are informational; commit, push, pull-request, and release delivery follow ordinary repository policy\./);
|
|
121
|
+
assert.doesNotMatch(read(README), /one one-shot authorization for the exact command/i);
|
|
122
|
+
assert.doesNotMatch(read(README), /review-publication-gate/i);
|
|
127
123
|
});
|
|
128
124
|
|
|
129
125
|
for (const path of REVIEW_LENSES) {
|
|
@@ -133,6 +129,69 @@ for (const path of REVIEW_LENSES) {
|
|
|
133
129
|
});
|
|
134
130
|
}
|
|
135
131
|
|
|
132
|
+
function packagePaths(): string[] {
|
|
133
|
+
const manifest = JSON.parse(read("package.json")) as { files?: unknown };
|
|
134
|
+
if (!Array.isArray(manifest.files)) throw new Error("package.json must declare package files");
|
|
135
|
+
return manifest.files.map((entry) => {
|
|
136
|
+
if (typeof entry !== "string") throw new Error("package.json files entries must be strings");
|
|
137
|
+
return entry.replace(/\/+$/, "");
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function projectPath(absolutePath: string): string | undefined {
|
|
142
|
+
const path = relative(ROOT, absolutePath);
|
|
143
|
+
if (path === "" || path === ".." || path.startsWith(`..${sep}`)) return undefined;
|
|
144
|
+
return path.split(sep).join("/");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isPackagedFile(path: string, packageRoots: readonly string[]): boolean {
|
|
148
|
+
const absolutePath = resolve(ROOT, path);
|
|
149
|
+
const projectRelativePath = projectPath(absolutePath);
|
|
150
|
+
if (!projectRelativePath || !existsSync(absolutePath) || !statSync(absolutePath).isFile()) return false;
|
|
151
|
+
return packageRoots.some((root) => projectRelativePath === root || projectRelativePath.startsWith(`${root}/`));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function packagedOrdinaryReviewLenses(): string[] {
|
|
155
|
+
const packageRoots = packagePaths();
|
|
156
|
+
return readdirSync(join(ROOT, "assets", "agents"), { withFileTypes: true })
|
|
157
|
+
.filter((entry) => entry.isFile() && entry.name.startsWith("review-") && entry.name.endsWith(".md"))
|
|
158
|
+
.map((entry) => `assets/agents/${entry.name}`)
|
|
159
|
+
.filter((path) => isPackagedFile(path, packageRoots))
|
|
160
|
+
.sort();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const MARKDOWN_FILE_REFERENCE = /(?:https?:\/\/[^\s)`\]]+|(?:\.{1,2}\/)?[A-Za-z0-9][A-Za-z0-9_./-]*)\.md\b/g;
|
|
164
|
+
|
|
165
|
+
function assertMarkdownFileDependenciesResolve(path: string, content: string, packageRoots: readonly string[]): void {
|
|
166
|
+
const sourceDirectory = dirname(join(ROOT, path));
|
|
167
|
+
const unresolved = [...content.matchAll(MARKDOWN_FILE_REFERENCE)]
|
|
168
|
+
.map((match) => match[0])
|
|
169
|
+
.filter((reference) => {
|
|
170
|
+
if (reference.startsWith("http://") || reference.startsWith("https://")) return true;
|
|
171
|
+
return ![resolve(ROOT, reference), resolve(sourceDirectory, reference)].some((candidate) => {
|
|
172
|
+
const candidatePath = projectPath(candidate);
|
|
173
|
+
return candidatePath !== undefined && isPackagedFile(candidatePath, packageRoots);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
assert.deepEqual(unresolved, [], `${path} names unavailable Markdown dependencies: ${unresolved.join(", ")}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
test("packaged ordinary review lenses resolve their Markdown file dependencies", () => {
|
|
180
|
+
const packageRoots = packagePaths();
|
|
181
|
+
const lenses = packagedOrdinaryReviewLenses();
|
|
182
|
+
assert.ok(lenses.length > 0, "package must ship at least one ordinary review lens");
|
|
183
|
+
for (const path of lenses) assertMarkdownFileDependenciesResolve(path, read(path), packageRoots);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("ordinary review dependencies allow packaged files and reject unresolved paths", () => {
|
|
187
|
+
const packageRoots = packagePaths();
|
|
188
|
+
assertMarkdownFileDependenciesResolve("assets/agents/review-risk.md", "Sources: docs/native-authority-architecture.md", packageRoots);
|
|
189
|
+
assert.throws(
|
|
190
|
+
() => assertMarkdownFileDependenciesResolve("assets/agents/review-risk.md", "Sources: docs/nonexistent/security.md", packageRoots),
|
|
191
|
+
/unavailable Markdown dependencies: docs\/nonexistent\/security\.md/,
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
136
195
|
test("ordinary lens prompts contain the literal compact-v2 native result envelope", () => {
|
|
137
196
|
const expectedLenses = ["review-risk", "review-resilience", "review-readability", "review-reliability"];
|
|
138
197
|
for (const [index, path] of REVIEW_LENSES.entries()) {
|
|
@@ -251,33 +310,36 @@ test("Judgment Day skill and prompts preserve bounded fix and re-judgment author
|
|
|
251
310
|
assertMatches(FIX_AGENT, read(FIX_AGENT), FIX_PATTERNS);
|
|
252
311
|
});
|
|
253
312
|
|
|
254
|
-
test("orchestrator, skill, and README
|
|
313
|
+
test("orchestrator, injected skill, and README defer RDD lifecycle ownership to Gentle AI", () => {
|
|
314
|
+
const boundary = "Gentle AI dynamically supplies runtime-specific RDD instructions via generated Pi APPEND_SYSTEM composition. Follow only those exact native instructions; if absent or unsupported, this package does not invent or fall back.";
|
|
315
|
+
const orchestrator = union(ORCHESTRATOR);
|
|
316
|
+
assert.ok(orchestrator.includes(boundary), "orchestrator must carry the sole static ownership boundary");
|
|
317
|
+
|
|
255
318
|
for (const [label, content] of [
|
|
256
|
-
["orchestrator", union(ORCHESTRATOR)],
|
|
257
319
|
[GENTLE_SKILL, read(GENTLE_SKILL)],
|
|
258
320
|
[README, read(README)],
|
|
259
321
|
] as const) {
|
|
260
322
|
assertMatches(label, content, [
|
|
261
|
-
/
|
|
262
|
-
|
|
263
|
-
/one correction transaction/i,
|
|
264
|
-
/(?:graph-v1|legacy)[\s\S]*(?:read-only|reject mutation)/i,
|
|
265
|
-
/Judgment Day[\s\S]*(?:explicit|separate)/i,
|
|
266
|
-
/(?:one-shot|one exact one-shot)[\s\S]*(?:bash time|bash-time)/i,
|
|
323
|
+
/Gentle AI dynamically supplies runtime-specific RDD instructions/i,
|
|
324
|
+
/(?:sole lifecycle authority|does not define an RDD lifecycle)/i,
|
|
267
325
|
]);
|
|
268
326
|
}
|
|
327
|
+
|
|
328
|
+
for (const [label, content] of [
|
|
329
|
+
["orchestrator", orchestrator],
|
|
330
|
+
[GENTLE_SKILL, read(GENTLE_SKILL)],
|
|
331
|
+
] as const) {
|
|
332
|
+
assert.doesNotMatch(content, /start -> finalize -> validate|INSPECT before START|next_transition|review\.capture-result/i, label);
|
|
333
|
+
}
|
|
269
334
|
});
|
|
270
335
|
|
|
271
|
-
test("README documents the
|
|
336
|
+
test("README documents the dynamic runtime authority boundary without an old package route", () => {
|
|
272
337
|
const content = read(README);
|
|
273
|
-
assert.match(content, /
|
|
274
|
-
assert.match(content, /
|
|
275
|
-
assert.
|
|
276
|
-
assert.match(content, /
|
|
277
|
-
assert.match(content, /
|
|
278
|
-
assert.match(content, /MUST NOT run a downgraded binary/i);
|
|
279
|
-
assert.match(content, /existing branch.*advertised commit equals.*old object/is);
|
|
280
|
-
assert.match(content, /never guesses? a base.*upstream.*default branch.*nearest ancestor/is);
|
|
338
|
+
assert.match(content, /Gentle AI dynamically supplies runtime-specific RDD instructions/i);
|
|
339
|
+
assert.match(content, /does not define an RDD lifecycle/i);
|
|
340
|
+
assert.doesNotMatch(content, /New ordinary review uses compact `gentle_review` `start -> finalize -> validate`\./);
|
|
341
|
+
assert.match(content, /Dangerous-command safety remains independent and authoritative/);
|
|
342
|
+
assert.match(content, /Project and user overrides may shadow a package asset/);
|
|
281
343
|
});
|
|
282
344
|
|
|
283
345
|
test("managed contracts retain no fresh lifecycle review directive", () => {
|
|
@@ -138,11 +138,8 @@ async function runController(
|
|
|
138
138
|
return await __testing.executeReviewControllerOperation(
|
|
139
139
|
parameters,
|
|
140
140
|
cwd,
|
|
141
|
-
new Map(),
|
|
142
141
|
native,
|
|
143
142
|
undefined,
|
|
144
|
-
undefined,
|
|
145
|
-
undefined,
|
|
146
143
|
candidateViews,
|
|
147
144
|
) as Record<string, unknown>;
|
|
148
145
|
}
|
|
@@ -200,47 +197,3 @@ test("STATUS discovery never hydrates without pending reviewer-result collection
|
|
|
200
197
|
await runController({ operation: "status", lineageId: successor }, cwd, native, registry);
|
|
201
198
|
assert.equal(registry.hasCurrentBinding(), false, "an execute transition offers no reviewer dispatch and must not hydrate");
|
|
202
199
|
});
|
|
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
|
-
});
|