gentle-pi 2.4.0 → 2.6.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 +292 -25
- package/assets/agents/gentle-ai-worker.md +13 -0
- package/assets/agents/jd-fix-agent.md +18 -0
- package/assets/agents/jd-judge-a.md +1 -1
- package/assets/agents/jd-judge-b.md +1 -1
- package/assets/agents/sdd-apply.md +7 -5
- package/assets/agents/sdd-archive.md +5 -3
- package/assets/agents/sdd-design.md +4 -0
- package/assets/agents/sdd-explore.md +4 -0
- package/assets/agents/sdd-init.md +4 -0
- package/assets/agents/sdd-onboard.md +4 -0
- package/assets/agents/sdd-proposal.md +4 -0
- package/assets/agents/sdd-remediate.md +37 -0
- package/assets/agents/sdd-research.md +26 -3
- package/assets/agents/sdd-spec.md +4 -0
- package/assets/agents/sdd-status.md +9 -75
- package/assets/agents/sdd-sync.md +4 -0
- package/assets/agents/sdd-tasks.md +4 -0
- package/assets/agents/sdd-verify.md +5 -3
- package/assets/chains/sdd-full.chain.md +4 -0
- package/assets/chains/sdd-plan.chain.md +4 -0
- package/assets/chains/sdd-verify.chain.md +4 -0
- package/assets/migrations/managed-assets-v2.5.0.json +7 -0
- package/assets/orchestrator-delegation.md +39 -11
- package/assets/orchestrator.md +5 -5
- package/assets/sdd-orchestrator-workflow.md +54 -21
- package/assets/support/sdd-status-contract.md +34 -90
- package/contracts/telemetry/runtime-aggregate-v1.schema.json +65 -0
- package/docs/delegated-verification.md +25 -0
- package/docs/telemetry.md +94 -0
- package/docs/windows-startup-console-visibility.md +18 -0
- package/extensions/ask-user-choice.ts +159 -25
- package/extensions/codegraph-tools.ts +95 -5
- package/extensions/gentle-agents.ts +1337 -0
- package/extensions/gentle-ai.ts +2916 -386
- package/extensions/gentle-shell.ts +650 -0
- package/extensions/gentle-todo.ts +234 -0
- package/extensions/quiet-tools.ts +2 -1
- package/extensions/runtime-metrics.ts +130 -0
- package/extensions/sdd-init.ts +2 -2
- package/extensions/startup-banner.ts +52 -75
- package/lib/agent-profiles.ts +550 -0
- package/lib/agents-completion-delivery.ts +72 -0
- package/lib/agents-config.ts +315 -0
- package/lib/agents-history.ts +88 -0
- package/lib/agents-messaging.ts +187 -0
- package/lib/agents-protocol.ts +501 -0
- package/lib/agents-runner.ts +1012 -0
- package/lib/agents-thread-view.ts +57 -0
- package/lib/agents-transcript.ts +87 -0
- package/lib/agents-view-layout.ts +40 -0
- package/lib/agents-view.ts +914 -0
- package/lib/agents-widget.ts +241 -0
- package/lib/gentle-ai-binary.ts +3 -1
- package/lib/gentle-ai-renderer.ts +143 -25
- package/lib/native-choice-list.ts +194 -0
- package/lib/native-fullscreen-interaction.ts +47 -0
- package/lib/native-pointer-region.ts +164 -0
- package/lib/native-review-cli.ts +371 -13
- package/lib/orchestrator-presence.ts +337 -0
- package/lib/profiles-orchestrator.ts +203 -0
- package/lib/review-candidate-view-owner.ts +427 -0
- package/lib/review-candidate-view.ts +150 -48
- package/lib/review-consent-component.ts +247 -0
- package/lib/review-consent-ui.ts +110 -0
- package/lib/review-host-relay.ts +28 -0
- package/lib/review-integration-v2.ts +243 -11
- package/lib/review-last-event-controller.ts +8 -4
- package/lib/review-relay-contract.ts +11 -0
- package/lib/review-reminder-receipt.ts +74 -0
- package/lib/review-repository.ts +2 -2
- package/lib/review-risk-assessment.ts +339 -0
- package/lib/review-session-standing-permission-ipc.ts +309 -0
- package/lib/review-session-standing-permission.ts +240 -0
- package/lib/runtime-metrics-children.ts +199 -0
- package/lib/runtime-metrics-delivery.ts +68 -0
- package/lib/runtime-metrics-native.ts +166 -0
- package/lib/runtime-metrics-pi-identity.ts +113 -0
- package/lib/runtime-metrics-policy.ts +51 -0
- package/lib/runtime-metrics.ts +255 -0
- package/lib/sdd-preflight.ts +362 -81
- package/lib/sdd-research-capabilities.ts +228 -0
- package/lib/sdd-status.ts +29 -7
- package/lib/session-worktree-registry.ts +118 -0
- package/lib/shell-bar.ts +184 -0
- package/lib/shell-card.ts +133 -0
- package/lib/shell-changes-view.ts +530 -0
- package/lib/shell-changes.ts +290 -0
- package/lib/shell-gauge.ts +40 -0
- package/lib/shell-prompt.ts +115 -0
- package/lib/shell-sidebar-banner.ts +11 -0
- package/lib/shell-sidebar-layout.ts +213 -0
- package/lib/shell-sidebar.ts +41 -0
- package/lib/shell-todo.ts +297 -0
- package/lib/shell-usage-view.ts +76 -0
- package/lib/shell-usage.ts +246 -0
- package/lib/telemetry-trigger.ts +153 -0
- package/package.json +8 -5
- package/runtime/gentle-ai-binary.mjs +3 -1
- package/runtime/native-review-cli.mjs +370 -12
- package/runtime/review-integration-v2.mjs +243 -11
- package/runtime/review-relay-contract.mjs +11 -0
- package/runtime/review-risk-assessment.mjs +340 -0
- package/runtime/telemetry-trigger.mjs +154 -0
- package/scripts/build-runtime-modules.mjs +11 -1
- package/scripts/check-types.mjs +125 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/install-gentle-ai.mjs +12 -0
- package/scripts/install-tui-mode-setting.mjs +114 -0
- package/scripts/test-packed-runner.mjs +38 -2
- package/scripts/types-baseline.json +99 -0
- package/scripts/verify-package-files.mjs +8 -2
- package/skills/_shared/review-ledger-contract.md +20 -2
- package/skills/issue-creation/SKILL.md +3 -3
- package/skills/judgment-day/SKILL.md +17 -3
- package/skills/judgment-day/references/prompts-and-formats.md +14 -3
- package/tests/agent-profiles.test.ts +722 -0
- package/tests/agents-completion-delivery.test.ts +94 -0
- package/tests/agents-config.test.ts +205 -0
- package/tests/agents-fake-child.ts +66 -0
- package/tests/agents-grouping.test.ts +179 -0
- package/tests/agents-history.test.ts +54 -0
- package/tests/agents-integration.test.ts +100 -0
- package/tests/agents-messaging.test.ts +94 -0
- package/tests/agents-protocol.test.ts +198 -0
- package/tests/agents-queries.test.ts +190 -0
- package/tests/agents-responsive.test.ts +43 -0
- package/tests/agents-runner-process.test.ts +111 -0
- package/tests/agents-runner.test.ts +959 -0
- package/tests/agents-thread-view.test.ts +45 -0
- package/tests/agents-transcript.test.ts +30 -0
- package/tests/agents-view.test.ts +685 -0
- package/tests/agents-widget.test.ts +141 -0
- package/tests/artifact-language.test.ts +25 -2
- package/tests/ask-user-choice.test.ts +325 -5
- package/tests/asset-installation-runtime.test.ts +108 -0
- package/tests/autonomous-guard.test.ts +116 -1
- package/tests/codegraph-tools.test.ts +112 -2
- package/tests/delegated-key-learnings-contract.test.ts +1 -1
- package/tests/devbinary/native-review-parity.devtest.ts +110 -0
- package/tests/feature-request-form.test.ts +67 -0
- package/tests/fixtures/agents-messaging-child.mjs +5 -0
- package/tests/fixtures/agents-process-child.mjs +23 -0
- package/tests/fixtures/runtime-metrics-native-batches.json +6 -0
- package/tests/gentle-agents.test.ts +2168 -0
- package/tests/gentle-ai-binary.test.ts +7 -2
- package/tests/gentle-ai-installer.test.ts +47 -47
- package/tests/gentle-ai-renderer.test.ts +103 -0
- package/tests/gentle-ai.test.ts +971 -15
- package/tests/gentle-card-text.ts +35 -0
- package/tests/gentle-shell.test.ts +818 -0
- package/tests/gentle-todo.test.ts +226 -0
- package/tests/install-tui-mode-setting.test.ts +324 -0
- package/tests/issue-creation-skill.test.ts +22 -0
- package/tests/model-routing-authority.test.ts +12 -0
- package/tests/native-choice-list.test.ts +202 -0
- package/tests/native-fullscreen-interaction.test.ts +125 -0
- package/tests/native-pointer-region.test.ts +245 -0
- package/tests/native-review-capability-contract.test.ts +27 -1
- package/tests/native-review-cli.test.ts +317 -3
- package/tests/native-review-consent.test.ts +91 -0
- package/tests/native-review-parity-runtime.test.ts +8 -2
- package/tests/native-review-parity.test.ts +43 -29
- package/tests/native-sdd-attempt-authority.test.ts +7 -2
- package/tests/orchestrator-budget.test.ts +69 -0
- package/tests/orchestrator-presence.test.ts +389 -0
- package/tests/orchestrator-rdd-ownership.test.ts +9 -0
- package/tests/package-manifest.test.ts +243 -7
- package/tests/profiles-orchestrator.test.ts +208 -0
- package/tests/quiet-tool-rendering.test.ts +97 -37
- package/tests/rdd-aware-verification-contract.test.ts +226 -0
- package/tests/rdd-status-line.test.ts +286 -0
- package/tests/review-agent-end-preflight.test.ts +332 -24
- package/tests/review-candidate-view.test.ts +751 -7
- package/tests/review-consent-ui.test.ts +352 -0
- package/tests/review-contract-prompt.test.ts +17 -0
- package/tests/review-controller-native-recovery.test.ts +29 -4
- package/tests/review-controller-native-routing.test.ts +884 -7
- package/tests/review-controller-workspace-root.test.ts +45 -2
- package/tests/review-controller.test.ts +26 -1
- package/tests/review-host-relay-restart-parity.test.ts +142 -1
- package/tests/review-host-relay-routing.test.ts +384 -8
- package/tests/review-host-relay.test.ts +29 -0
- package/tests/review-integration-v2-forward.test.ts +44 -0
- package/tests/review-integration-v2.test.ts +276 -0
- package/tests/review-last-event-closure.test.ts +112 -3
- package/tests/review-ledger-contract.test.ts +61 -6
- package/tests/review-relay-contract.test.ts +26 -0
- package/tests/review-reminder-receipt.test.ts +62 -0
- package/tests/review-repository.test.ts +28 -1
- package/tests/review-risk-assessment.test.ts +626 -0
- package/tests/review-session-standing-permission-controller.test.ts +656 -0
- package/tests/review-session-standing-permission-ipc.test.ts +233 -0
- package/tests/review-session-standing-permission-runtime.test.ts +212 -0
- package/tests/review-session-standing-permission.test.ts +156 -0
- package/tests/runtime-harness.mjs +447 -39
- package/tests/runtime-metrics-children.test.ts +206 -0
- package/tests/runtime-metrics-delivery.test.ts +85 -0
- package/tests/runtime-metrics-extension.test.ts +187 -0
- package/tests/runtime-metrics-native.test.ts +209 -0
- package/tests/runtime-metrics-pi-identity.test.ts +113 -0
- package/tests/runtime-metrics-policy.test.ts +62 -0
- package/tests/runtime-metrics.test.ts +184 -0
- package/tests/sdd-agent-tools.test.ts +10 -1
- package/tests/sdd-execution-routing-contract.test.ts +28 -0
- package/tests/sdd-managed-runtime-settlement.test.ts +331 -0
- package/tests/sdd-native-managed-uptake.test.ts +253 -0
- package/tests/sdd-planning-routing-contract.test.ts +45 -0
- package/tests/sdd-preflight.test.ts +252 -8
- package/tests/sdd-research-capabilities.test.ts +256 -0
- package/tests/sdd-research-live.test.ts +241 -0
- package/tests/sdd-selection-transport.test.ts +504 -0
- package/tests/sdd-status.test.ts +51 -0
- package/tests/session-worktree-registry.test.ts +135 -0
- package/tests/shell-bar.test.ts +176 -0
- package/tests/shell-card.test.ts +139 -0
- package/tests/shell-changes-view.test.ts +609 -0
- package/tests/shell-changes.test.ts +350 -0
- package/tests/shell-prompt.test.ts +140 -0
- package/tests/shell-sidebar-banner.test.ts +23 -0
- package/tests/shell-sidebar-layout.test.ts +387 -0
- package/tests/shell-sidebar.test.ts +50 -0
- package/tests/shell-todo.test.ts +259 -0
- package/tests/shell-usage-view.test.ts +62 -0
- package/tests/shell-usage.test.ts +197 -0
- package/tests/startup-banner.test.ts +126 -0
- package/tests/telemetry-trigger.test.ts +351 -0
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import
|
|
2
|
+
import childProcess from "node:child_process";
|
|
3
|
+
import { syncBuiltinESMExports } from "node:module";
|
|
4
|
+
import { realpathSync } from "node:fs";
|
|
5
|
+
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
3
6
|
import { tmpdir } from "node:os";
|
|
4
7
|
import { join } from "node:path";
|
|
5
8
|
import test from "node:test";
|
|
6
9
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
10
|
import { createGentleAiExtension } from "../extensions/gentle-ai.ts";
|
|
8
|
-
import type
|
|
11
|
+
import { NATIVE_REVIEW_ERROR_CODE, NativeReviewCliError, type NativeReviewCli } from "../lib/native-review-cli.ts";
|
|
9
12
|
import type { ReviewStatusV3 } from "../lib/review-integration-v2.ts";
|
|
10
13
|
|
|
11
14
|
// gentle-pi#556 / gentle-ai#4051: with RDD enabled, the agent finished an
|
|
@@ -14,36 +17,44 @@ import type { ReviewStatusV3 } from "../lib/review-integration-v2.ts";
|
|
|
14
17
|
// that reminds the agent to call gentle_review before reporting completion,
|
|
15
18
|
// without ever starting a review or answering consent itself.
|
|
16
19
|
//
|
|
17
|
-
// gentle-pi#568:
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// pre-session work, not this session's output). These tests point
|
|
20
|
+
// gentle-pi#568/#777: startup negotiates STATUS, but only successful own
|
|
21
|
+
// mutation receipts authorize agent_end to query a candidate. Pre-session
|
|
22
|
+
// work and foreign-session changes are not this session's output. These tests point
|
|
21
23
|
// `GENTLE_PI_AGENT_HOME` and the session `cwd` at fresh temp directories so
|
|
22
24
|
// `session_start`'s real SDD asset install and model config sweep never
|
|
23
|
-
// touch this machine's actual home directory.
|
|
25
|
+
// touch this machine's actual home directory. The cwd is an isolated Git
|
|
26
|
+
// fixture so mutation receipts exercise canonical root resolution.
|
|
24
27
|
|
|
25
28
|
type AnyHandler = (event: unknown, ctx: ExtensionContext) => unknown;
|
|
29
|
+
type RegisteredTool = Parameters<ExtensionAPI["registerTool"]>[0];
|
|
26
30
|
type SentMessage = { message: Record<string, unknown>; options: Record<string, unknown> };
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
type CustomEntry = { type: string; customType: string; data: unknown };
|
|
33
|
+
function harness(nativeReviewCli: NativeReviewCli | null, entries: CustomEntry[] = []): {
|
|
29
34
|
handlers: Map<string, AnyHandler>;
|
|
30
35
|
sent: SentMessage[];
|
|
36
|
+
tools: Map<string, RegisteredTool>;
|
|
31
37
|
} {
|
|
32
38
|
const handlers = new Map<string, AnyHandler>();
|
|
33
39
|
const sent: SentMessage[] = [];
|
|
40
|
+
const tools = new Map<string, RegisteredTool>();
|
|
34
41
|
const pi = {
|
|
42
|
+
appendEntry(customType: string, data: unknown) { entries.push({ type: "custom", customType, data }); },
|
|
35
43
|
on(name: string, handler: AnyHandler) {
|
|
36
|
-
handlers.set(name,
|
|
44
|
+
handlers.set(name, (event, context) => {
|
|
45
|
+
context.sessionManager.getBranch ??= (() => entries) as typeof context.sessionManager.getBranch;
|
|
46
|
+
return handler(event, context);
|
|
47
|
+
});
|
|
37
48
|
},
|
|
38
49
|
events: { emit() {} },
|
|
39
50
|
registerCommand() {},
|
|
40
|
-
registerTool() {},
|
|
51
|
+
registerTool(tool: RegisteredTool) { tools.set(tool.name, tool); },
|
|
41
52
|
sendMessage(message: Record<string, unknown>, options: Record<string, unknown> = {}) {
|
|
42
53
|
sent.push({ message, options });
|
|
43
54
|
},
|
|
44
55
|
} as unknown as ExtensionAPI;
|
|
45
56
|
createGentleAiExtension({ nativeReviewCli })(pi);
|
|
46
|
-
return { handlers, sent };
|
|
57
|
+
return { handlers, sent, tools };
|
|
47
58
|
}
|
|
48
59
|
|
|
49
60
|
function ctx(sessionId: string, hasUI = true, cwd = process.cwd()): ExtensionContext {
|
|
@@ -65,6 +76,9 @@ async function withSessionStartEnv<T>(callback: (cwd: string) => Promise<T>): Pr
|
|
|
65
76
|
process.env.GENTLE_PI_CONFIG_HOME = await mkdtemp(join(tmpdir(), "gentle-pi-session-baseline-config-home-"));
|
|
66
77
|
try {
|
|
67
78
|
const cwd = await mkdtemp(join(tmpdir(), "gentle-pi-session-baseline-cwd-"));
|
|
79
|
+
childProcess.execFileSync("git", ["init", "--quiet", cwd]);
|
|
80
|
+
await mkdir(join(cwd, "src"));
|
|
81
|
+
await writeFile(join(cwd, "src/example.ts"), "export const value = 1;");
|
|
68
82
|
return await callback(cwd);
|
|
69
83
|
} finally {
|
|
70
84
|
if (previousAgentHome === undefined) delete process.env.GENTLE_PI_AGENT_HOME;
|
|
@@ -110,6 +124,123 @@ function stopStatus(targetIdentity: string): ReviewStatusV3 {
|
|
|
110
124
|
}
|
|
111
125
|
|
|
112
126
|
const agentEndEvent = { type: "agent_end", messages: [] };
|
|
127
|
+
let mutationCall = 0;
|
|
128
|
+
async function directWrite(handlers: Map<string, AnyHandler>, session: ExtensionContext): Promise<void> {
|
|
129
|
+
await handlers.get("tool_result")!({ type: "tool_result", toolName: "write", toolCallId: `own-${++mutationCall}`,
|
|
130
|
+
input: { path: session.cwd === process.cwd() || session.cwd === realpathSync(process.cwd()) ? "tests/review-agent-end-preflight.test.ts" : "src/example.ts" },
|
|
131
|
+
content: [], isError: false }, session);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// #772: use the acknowledgement vector and returned-envelope shapes from
|
|
135
|
+
// review-controller-native-routing.test.ts, through the registered tool rather
|
|
136
|
+
// than the controller helper, so agent_end shares the same extension state.
|
|
137
|
+
for (const scenario of ["same", "changed", "sibling-root", "nested-root", "failed", "unknown", "shutdown", "other-session", "legacy-success", "new-write", "concurrent-write"] as const) {
|
|
138
|
+
test(`agent_end after approved acknowledgement: ${scenario}`, async (t) => {
|
|
139
|
+
const changedTarget = scenario === "changed";
|
|
140
|
+
const unsuccessful = scenario === "failed" || scenario === "unknown";
|
|
141
|
+
const cwd = realpathSync(process.cwd());
|
|
142
|
+
const siblingRoot = realpathSync(tmpdir());
|
|
143
|
+
if (scenario === "sibling-root") {
|
|
144
|
+
// Model a sibling worktree sharing the real common directory without
|
|
145
|
+
// creating a worktree or mutating repository state.
|
|
146
|
+
const exec = childProcess.execFileSync;
|
|
147
|
+
const commonDir = exec("git", ["rev-parse", "--git-common-dir"], { cwd, encoding: "utf8" }).trim();
|
|
148
|
+
t.mock.method(childProcess, "execFileSync", (file: string, args: string[], options: { cwd?: string }) => {
|
|
149
|
+
if (file === "git" && options.cwd === siblingRoot) {
|
|
150
|
+
if (args[1] === "--show-toplevel") return siblingRoot;
|
|
151
|
+
if (args[1] === "--git-common-dir") return commonDir;
|
|
152
|
+
}
|
|
153
|
+
return exec(file, args, options);
|
|
154
|
+
});
|
|
155
|
+
syncBuiltinESMExports();
|
|
156
|
+
t.after(() => { t.mock.restoreAll(); syncBuiltinESMExports(); });
|
|
157
|
+
}
|
|
158
|
+
const lineageId = `agent-end-post-ack-${scenario}`;
|
|
159
|
+
const targetIdentity = `sha256:${"a".repeat(64)}`;
|
|
160
|
+
const nextTarget = changedTarget ? `sha256:${"b".repeat(64)}` : targetIdentity;
|
|
161
|
+
const binding = { lineageId, targetIdentity, revision: targetIdentity };
|
|
162
|
+
const arguments_ = [
|
|
163
|
+
["cwd", cwd], ["lineage", lineageId], ["target", targetIdentity],
|
|
164
|
+
["expected-revision", targetIdentity], ["token", "provider-issued-once"],
|
|
165
|
+
].map(([name, value]) => ({ name, value, token: `--${name}=${value}` }));
|
|
166
|
+
const approved = {
|
|
167
|
+
contract: "gentle-ai.review-integration/v2",
|
|
168
|
+
applicability: "current_target",
|
|
169
|
+
authority: { version: "compact-v2", lineageId, state: "approved", generation: 1, revision: targetIdentity },
|
|
170
|
+
receipt: { status: "expected_missing" },
|
|
171
|
+
action: "stop", replayability: "not_replayable", targetIdentity, candidates: [],
|
|
172
|
+
nextTransition: {
|
|
173
|
+
kind: "execute", reasonCode: "approved_acknowledgement_required",
|
|
174
|
+
execute: {
|
|
175
|
+
operation: "review.acknowledge-approved",
|
|
176
|
+
command: "gentle-ai review acknowledge-approved --provider-vector",
|
|
177
|
+
arguments: arguments_,
|
|
178
|
+
preconditions: [{ name: "state", value: "approved", token: "--state=approved" }],
|
|
179
|
+
binding,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
raw: { schema: "gentle-ai.review-integration.status/v5" },
|
|
183
|
+
} as unknown as ReviewStatusV3;
|
|
184
|
+
const statusRequests: unknown[] = [];
|
|
185
|
+
const acknowledgementRequests: unknown[] = [];
|
|
186
|
+
let burned = false;
|
|
187
|
+
const native = {
|
|
188
|
+
reviewMode: onMode("on"),
|
|
189
|
+
targetStatus: async (request: unknown) => {
|
|
190
|
+
statusRequests.push(request);
|
|
191
|
+
return burned ? executeStartStatus(nextTarget) : approved;
|
|
192
|
+
},
|
|
193
|
+
acknowledgeApproved: async (request: unknown) => {
|
|
194
|
+
acknowledgementRequests.push(request);
|
|
195
|
+
if (scenario === "concurrent-write") await directWrite(handlers, session);
|
|
196
|
+
burned = true;
|
|
197
|
+
if (scenario === "failed") throw new TypeError("local acknowledgement validation failed");
|
|
198
|
+
if (scenario === "unknown") throw new NativeReviewCliError(NATIVE_REVIEW_ERROR_CODE.NON_ZERO, "review/acknowledge-approved", true, true, "acknowledgement outcome unknown");
|
|
199
|
+
if (scenario === "legacy-success") return undefined;
|
|
200
|
+
return {
|
|
201
|
+
schema: "gentle-ai.review-acknowledged/v1",
|
|
202
|
+
operation: "review/acknowledge-approved", action: "acknowledged",
|
|
203
|
+
lineageId, targetIdentity, consumedRevision: targetIdentity, authority: "burned",
|
|
204
|
+
raw: { schema: "gentle-ai.review-acknowledged/v1" },
|
|
205
|
+
};
|
|
206
|
+
},
|
|
207
|
+
} as unknown as NativeReviewCli;
|
|
208
|
+
const { handlers, sent, tools } = harness(native);
|
|
209
|
+
const session = ctx(lineageId, true, cwd);
|
|
210
|
+
const review = tools.get("gentle_review");
|
|
211
|
+
assert.ok(review);
|
|
212
|
+
await directWrite(handlers, session);
|
|
213
|
+
const result = await review.execute("post-ack", { operation: "acknowledge-approved", lineageId }, undefined, undefined, session);
|
|
214
|
+
if (unsuccessful) {
|
|
215
|
+
assert.equal(result.details.outcome, scenario === "failed" ? "native-operation-failed" : "native-mutation-status-reconciled");
|
|
216
|
+
assert.equal(result.details.mutation_outcome, scenario === "failed" ? "none" : "unknown");
|
|
217
|
+
} else assert.deepEqual(result.details, {
|
|
218
|
+
operation: "acknowledge-approved", status: "closed",
|
|
219
|
+
outcome: "native-approved-acknowledgement-completed",
|
|
220
|
+
lineage_id: lineageId, target_identity: targetIdentity,
|
|
221
|
+
...(scenario === "legacy-success" ? {} : { consumed_revision: targetIdentity, burn_evidence: "gentle-ai.review-acknowledged/v1" }),
|
|
222
|
+
authority: "burned",
|
|
223
|
+
delivery: "ordinary-repository-policy", mutation_performed: true, mutation_outcome: "committed",
|
|
224
|
+
});
|
|
225
|
+
assert.deepEqual(acknowledgementRequests, [{ cwd, argumentTokens: arguments_.map(({ token }) => token), binding }]);
|
|
226
|
+
if (scenario !== "unknown") assert.deepEqual(statusRequests, [{ cwd, lineageId }], "ACK reports burn without a later STATUS");
|
|
227
|
+
const callsBeforeEnd = statusRequests.length;
|
|
228
|
+
assert.deepEqual(sent, [], "no earlier reminder can mask the post-burn regression");
|
|
229
|
+
|
|
230
|
+
if (scenario === "shutdown") await handlers.get("session_shutdown")!({}, session);
|
|
231
|
+
const endSession = scenario === "sibling-root" ? ctx(lineageId, true, siblingRoot)
|
|
232
|
+
: scenario === "nested-root" ? ctx(lineageId, true, join(cwd, "tests"))
|
|
233
|
+
: scenario === "other-session" ? ctx(`${lineageId}-new`, true, cwd) : session;
|
|
234
|
+
if (changedTarget || scenario === "new-write") await directWrite(handlers, endSession);
|
|
235
|
+
await handlers.get("agent_end")!(agentEndEvent, endSession);
|
|
236
|
+
const shouldRemind = changedTarget || unsuccessful || scenario === "new-write" || scenario === "concurrent-write";
|
|
237
|
+
assert.deepEqual(statusRequests.slice(callsBeforeEnd), shouldRemind ? [{ cwd: endSession.cwd, agent: "pi" }] : []);
|
|
238
|
+
const reminders = sent.filter(({ message }) => message.customType === "gentle-pi.review-preflight");
|
|
239
|
+
assert.equal(reminders.length, shouldRemind ? 1 : 0,
|
|
240
|
+
shouldRemind ? "an unconsumed own mutation still requires preflight" : "an acknowledged or unowned target must not receive another preflight reminder");
|
|
241
|
+
if (changedTarget) assert.ok(String(reminders[0]?.message.content).includes(nextTarget));
|
|
242
|
+
});
|
|
243
|
+
}
|
|
113
244
|
|
|
114
245
|
test("agent_end performs no STATUS call and sends nothing when RDD is off", async () => {
|
|
115
246
|
const statusRequests: unknown[] = [];
|
|
@@ -140,13 +271,17 @@ test("agent_end nudges exactly once when RDD is on and STATUS offers review.star
|
|
|
140
271
|
} as unknown as NativeReviewCli;
|
|
141
272
|
const { handlers, sent } = harness(native);
|
|
142
273
|
const agentEnd = handlers.get("agent_end");
|
|
143
|
-
|
|
274
|
+
const session = ctx("agent-end-execute");
|
|
275
|
+
await directWrite(handlers, session);
|
|
276
|
+
await agentEnd!(agentEndEvent, session);
|
|
144
277
|
|
|
145
278
|
assert.equal(sent.length, 1);
|
|
146
279
|
const [entry] = sent;
|
|
147
280
|
assert.equal(entry?.message.customType, "gentle-pi.review-preflight");
|
|
148
281
|
const content = String(entry?.message.content);
|
|
149
282
|
assert.match(content, /gentle_review/);
|
|
283
|
+
assert.match(content, /first determine whether the user explicitly left this exact target unreviewed\. if yes, do not invoke review; report that disposition and continue\. only otherwise, call the gentle_review tool with \{"operation":"inspect"\}/i);
|
|
284
|
+
assert.doesNotMatch(content, /run the review preflight before reporting completion\. if the user explicitly left/i);
|
|
150
285
|
assert.ok(content.includes(targetIdentity), "message must name the target identity");
|
|
151
286
|
assert.equal(entry?.options.triggerTurn, true);
|
|
152
287
|
assert.equal(statusRequests[0]?.agent, "pi");
|
|
@@ -161,12 +296,14 @@ test("agent_end nudges once per target identity and again for a fresh identity",
|
|
|
161
296
|
const { handlers, sent } = harness(native);
|
|
162
297
|
const agentEnd = handlers.get("agent_end");
|
|
163
298
|
const session = ctx("agent-end-repeat");
|
|
299
|
+
await directWrite(handlers, session);
|
|
164
300
|
|
|
165
301
|
await agentEnd!(agentEndEvent, session);
|
|
166
302
|
await agentEnd!(agentEndEvent, session);
|
|
167
303
|
assert.equal(sent.length, 1, "the same target identity nudges only once");
|
|
168
304
|
|
|
169
305
|
targetIdentity = `sha256:${"e".repeat(64)}`;
|
|
306
|
+
await directWrite(handlers, session);
|
|
170
307
|
await agentEnd!(agentEndEvent, session);
|
|
171
308
|
assert.equal(sent.length, 2, "a different target identity nudges again");
|
|
172
309
|
});
|
|
@@ -182,7 +319,9 @@ test("agent_end sends nothing when STATUS offers collect or stop", async () => {
|
|
|
182
319
|
} as unknown as NativeReviewCli;
|
|
183
320
|
const { handlers, sent } = harness(native);
|
|
184
321
|
const agentEnd = handlers.get("agent_end");
|
|
185
|
-
|
|
322
|
+
const session = ctx(`agent-end-${label}`);
|
|
323
|
+
await directWrite(handlers, session);
|
|
324
|
+
await agentEnd!(agentEndEvent, session);
|
|
186
325
|
assert.deepEqual(sent, [], label);
|
|
187
326
|
}
|
|
188
327
|
});
|
|
@@ -213,6 +352,7 @@ test("agent_end pairs a named agent's start with its own end, then still nudges
|
|
|
213
352
|
const agentEnd = handlers.get("agent_end");
|
|
214
353
|
assert.equal(typeof beforeAgentStart, "function");
|
|
215
354
|
const session = ctx("agent-end-subagent");
|
|
355
|
+
await directWrite(handlers, session);
|
|
216
356
|
|
|
217
357
|
await beforeAgentStart!({ agentName: "review-readability", systemPrompt: "" }, session);
|
|
218
358
|
await agentEnd!(agentEndEvent, session);
|
|
@@ -222,6 +362,27 @@ test("agent_end pairs a named agent's start with its own end, then still nudges
|
|
|
222
362
|
assert.equal(sent.length, 1, "the primary loop's end still nudges once the subagent's end is paired off");
|
|
223
363
|
});
|
|
224
364
|
|
|
365
|
+
test("gentle-ai-worker agent_end never queries native review", async () => {
|
|
366
|
+
const statusRequests: unknown[] = [];
|
|
367
|
+
const native = {
|
|
368
|
+
reviewMode: onMode("on"),
|
|
369
|
+
targetStatus: async (request: unknown) => {
|
|
370
|
+
statusRequests.push(request);
|
|
371
|
+
throw new Error("a bounded worker must not own native review");
|
|
372
|
+
},
|
|
373
|
+
} as unknown as NativeReviewCli;
|
|
374
|
+
const { handlers, sent } = harness(native);
|
|
375
|
+
const beforeAgentStart = handlers.get("before_agent_start");
|
|
376
|
+
const agentEnd = handlers.get("agent_end");
|
|
377
|
+
const session = ctx("agent-end-generic-worker");
|
|
378
|
+
assert.equal(typeof beforeAgentStart, "function");
|
|
379
|
+
await beforeAgentStart!({ agentName: "gentle-ai-worker", systemPrompt: "" }, session);
|
|
380
|
+
await directWrite(handlers, session);
|
|
381
|
+
await agentEnd!(agentEndEvent, session);
|
|
382
|
+
assert.deepEqual(statusRequests, []);
|
|
383
|
+
assert.deepEqual(sent, []);
|
|
384
|
+
});
|
|
385
|
+
|
|
225
386
|
test("agent_end resets the subagent depth when a fresh primary loop starts", async () => {
|
|
226
387
|
const targetIdentity = `sha256:${"1".repeat(64)}`;
|
|
227
388
|
const native = {
|
|
@@ -232,6 +393,7 @@ test("agent_end resets the subagent depth when a fresh primary loop starts", asy
|
|
|
232
393
|
const beforeAgentStart = handlers.get("before_agent_start");
|
|
233
394
|
const agentEnd = handlers.get("agent_end");
|
|
234
395
|
const session = ctx("agent-end-subagent-reset");
|
|
396
|
+
await directWrite(handlers, session);
|
|
235
397
|
|
|
236
398
|
await beforeAgentStart!({ agentName: "review-readability", systemPrompt: "" }, session);
|
|
237
399
|
await beforeAgentStart!({ systemPrompt: "" }, session);
|
|
@@ -257,11 +419,13 @@ test("agent_end sends nothing and does not throw when target STATUS rejects", as
|
|
|
257
419
|
} as unknown as NativeReviewCli;
|
|
258
420
|
const { handlers, sent } = harness(native);
|
|
259
421
|
const agentEnd = handlers.get("agent_end");
|
|
260
|
-
|
|
422
|
+
const session = ctx("agent-end-status-throws");
|
|
423
|
+
await directWrite(handlers, session);
|
|
424
|
+
await assert.doesNotReject(async () => agentEnd!(agentEndEvent, session));
|
|
261
425
|
assert.deepEqual(sent, []);
|
|
262
426
|
});
|
|
263
427
|
|
|
264
|
-
test("session_shutdown
|
|
428
|
+
test("session_shutdown ignores late agent_end after a consumed mutation", async () => {
|
|
265
429
|
const targetIdentity = `sha256:${"9".repeat(64)}`;
|
|
266
430
|
const native = {
|
|
267
431
|
reviewMode: onMode("on"),
|
|
@@ -272,16 +436,91 @@ test("session_shutdown clears the nudged-target set for its session key", async
|
|
|
272
436
|
const shutdown = handlers.get("session_shutdown");
|
|
273
437
|
assert.equal(typeof shutdown, "function");
|
|
274
438
|
const session = ctx("agent-end-shutdown");
|
|
439
|
+
await directWrite(handlers, session);
|
|
275
440
|
|
|
276
441
|
await agentEnd!(agentEndEvent, session);
|
|
277
442
|
assert.equal(sent.length, 1);
|
|
278
443
|
|
|
279
444
|
await shutdown!({}, session);
|
|
280
445
|
await agentEnd!(agentEndEvent, session);
|
|
281
|
-
assert.equal(sent.length,
|
|
446
|
+
assert.equal(sent.length, 1, "late agent_end after shutdown cannot remind");
|
|
282
447
|
});
|
|
283
448
|
|
|
284
|
-
|
|
449
|
+
for (const scenario of ["reload", "fork", "new", "off-branch"] as const) {
|
|
450
|
+
test(`agent_end restores pending receipts only for the active session branch: ${scenario}`, async () => {
|
|
451
|
+
await withSessionStartEnv(async (cwd) => {
|
|
452
|
+
const entries: CustomEntry[] = [];
|
|
453
|
+
let statusCalls = 0;
|
|
454
|
+
const native = { reviewMode: onMode("on"), targetStatus: async () => { statusCalls += 1; return executeStartStatus("whole-target"); } } as unknown as NativeReviewCli;
|
|
455
|
+
const first = harness(native, entries);
|
|
456
|
+
const session = ctx(`receipt-${scenario}`, true, cwd);
|
|
457
|
+
await first.handlers.get("session_start")!({}, session);
|
|
458
|
+
await directWrite(first.handlers, session);
|
|
459
|
+
await first.handlers.get("session_shutdown")!({ reason: "reload" }, session);
|
|
460
|
+
const resumed = harness(native, entries);
|
|
461
|
+
const next = ctx(scenario === "reload" || scenario === "off-branch" ? `receipt-${scenario}` : `replacement-${scenario}`, true, cwd);
|
|
462
|
+
if (scenario === "off-branch") next.sessionManager.getBranch = () => [];
|
|
463
|
+
await resumed.handlers.get("session_start")!({ reason: scenario }, next);
|
|
464
|
+
const beforeEnd = statusCalls;
|
|
465
|
+
await resumed.handlers.get("agent_end")!(agentEndEvent, next);
|
|
466
|
+
assert.equal(statusCalls - beforeEnd, scenario === "reload" ? 1 : 0);
|
|
467
|
+
assert.equal(resumed.sent.length, scenario === "reload" ? 1 : 0);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
for (const scenario of ["concurrent-write", "shutdown"] as const) {
|
|
473
|
+
test(`agent_end binds consumption to the captured mutation during async STATUS: ${scenario}`, async () => {
|
|
474
|
+
const session = ctx(`status-race-${scenario}`);
|
|
475
|
+
let resolveStatus!: (status: ReviewStatusV3) => void;
|
|
476
|
+
let entered!: () => void;
|
|
477
|
+
const statusEntered = new Promise<void>((resolve) => { entered = resolve; });
|
|
478
|
+
let calls = 0;
|
|
479
|
+
const native = { reviewMode: onMode("on"), targetStatus: async () => {
|
|
480
|
+
calls += 1;
|
|
481
|
+
if (calls > 1) return executeStartStatus("next-whole-target");
|
|
482
|
+
entered();
|
|
483
|
+
return new Promise<ReviewStatusV3>((resolve) => { resolveStatus = resolve; });
|
|
484
|
+
} } as unknown as NativeReviewCli;
|
|
485
|
+
const h = harness(native);
|
|
486
|
+
await directWrite(h.handlers, session);
|
|
487
|
+
const ending = h.handlers.get("agent_end")!(agentEndEvent, session);
|
|
488
|
+
await statusEntered;
|
|
489
|
+
if (scenario === "shutdown") await h.handlers.get("session_shutdown")!({}, session);
|
|
490
|
+
await directWrite(h.handlers, session);
|
|
491
|
+
resolveStatus(executeStartStatus("captured-whole-target"));
|
|
492
|
+
await ending;
|
|
493
|
+
await h.handlers.get("agent_end")!(agentEndEvent, session);
|
|
494
|
+
assert.equal(h.sent.length, scenario === "concurrent-write" ? 2 : 0);
|
|
495
|
+
assert.equal(calls, scenario === "concurrent-write" ? 2 : 1);
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
test("successful direct writes in another canonical root do not authorize this root's reminder", async () => {
|
|
500
|
+
await withSessionStartEnv(async (otherRoot) => {
|
|
501
|
+
let statusCalls = 0;
|
|
502
|
+
const h = harness({ reviewMode: onMode("on"), targetStatus: async () => { statusCalls += 1; return executeStartStatus("foreign"); } } as unknown as NativeReviewCli);
|
|
503
|
+
const session = ctx("direct-other-root");
|
|
504
|
+
await h.handlers.get("tool_result")!({ type: "tool_result", toolName: "write", toolCallId: "other-root", input: { path: join(otherRoot, "src/example.ts") }, isError: false, content: [] }, session);
|
|
505
|
+
await h.handlers.get("agent_end")!(agentEndEvent, session);
|
|
506
|
+
assert.equal(statusCalls, 0);
|
|
507
|
+
assert.deepEqual(h.sent, []);
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
for (const toolName of ["read", "bash", "subagent_run", "edit", "write"]) {
|
|
512
|
+
test(`unsuccessful or non-mutating direct ${toolName} never authorizes shared STATUS`, async () => {
|
|
513
|
+
let statusCalls = 0;
|
|
514
|
+
const h = harness({ reviewMode: onMode("on"), targetStatus: async () => { statusCalls += 1; return executeStartStatus("foreign"); } } as unknown as NativeReviewCli);
|
|
515
|
+
const session = ctx(`rejected-${toolName}`);
|
|
516
|
+
await h.handlers.get("tool_result")!({ type: "tool_result", toolName, toolCallId: "rejected", input: { path: "tests/review-agent-end-preflight.test.ts" }, isError: toolName === "write" || toolName === "edit", content: [] }, session);
|
|
517
|
+
await h.handlers.get("agent_end")!(agentEndEvent, session);
|
|
518
|
+
assert.equal(statusCalls, 0);
|
|
519
|
+
assert.deepEqual(h.sent, []);
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
test("session_start negotiates the current target identity when RDD is on", async () => {
|
|
285
524
|
const targetIdentity = `sha256:${"2".repeat(64)}`;
|
|
286
525
|
const statusRequests: Array<{ agent?: string }> = [];
|
|
287
526
|
const native = {
|
|
@@ -307,7 +546,7 @@ test("session_start records the baseline target identity when RDD is on", async
|
|
|
307
546
|
});
|
|
308
547
|
});
|
|
309
548
|
|
|
310
|
-
test("agent_end skips
|
|
549
|
+
test("agent_end skips pre-session work, then nudges after an own mutation", async () => {
|
|
311
550
|
let targetIdentity = `sha256:${"3".repeat(64)}`;
|
|
312
551
|
const native = {
|
|
313
552
|
reviewMode: onMode("on"),
|
|
@@ -324,12 +563,79 @@ test("agent_end skips the candidate that matches the session's recorded baseline
|
|
|
324
563
|
assert.deepEqual(sent, [], "the baseline candidate predates the session and is not nudged");
|
|
325
564
|
|
|
326
565
|
targetIdentity = `sha256:${"4".repeat(64)}`;
|
|
566
|
+
await directWrite(handlers, session);
|
|
327
567
|
await agentEnd!(agentEndEvent, session);
|
|
328
568
|
assert.equal(sent.length, 1, "a candidate identity different from the baseline still nudges");
|
|
329
569
|
});
|
|
330
570
|
});
|
|
331
571
|
|
|
332
|
-
|
|
572
|
+
// #777: a shared worktree's changed target is not evidence that this session
|
|
573
|
+
// mutated it. Exercise real registered handlers; all native operations are mocks.
|
|
574
|
+
for (const ownsMutation of [false, true]) {
|
|
575
|
+
test(ownsMutation
|
|
576
|
+
? "agent_end still checks and reminds after this session's successful direct write"
|
|
577
|
+
: "agent_end ignores a foreign session's changed target after read-only work", async () => {
|
|
578
|
+
const targetA = `sha256:${"a".repeat(64)}`;
|
|
579
|
+
const targetB = `sha256:${"b".repeat(64)}`;
|
|
580
|
+
let targetIdentity = targetA;
|
|
581
|
+
const statusRequests: unknown[] = [];
|
|
582
|
+
const native = {
|
|
583
|
+
reviewMode: onMode("on"),
|
|
584
|
+
targetStatus: async (request: unknown) => {
|
|
585
|
+
statusRequests.push(request);
|
|
586
|
+
return executeStartStatus(targetIdentity);
|
|
587
|
+
},
|
|
588
|
+
} as unknown as NativeReviewCli;
|
|
589
|
+
await withSessionStartEnv(async (cwd) => {
|
|
590
|
+
const { handlers, sent } = harness(native);
|
|
591
|
+
const session = ctx(`session-777-${ownsMutation ? "direct-write" : "read-only"}`, true, cwd);
|
|
592
|
+
const sessionStart = handlers.get("session_start");
|
|
593
|
+
const toolResult = handlers.get("tool_result");
|
|
594
|
+
const agentEnd = handlers.get("agent_end");
|
|
595
|
+
assert.equal(typeof sessionStart, "function");
|
|
596
|
+
assert.equal(typeof agentEnd, "function");
|
|
597
|
+
|
|
598
|
+
await sessionStart!({}, session);
|
|
599
|
+
assert.deepEqual(statusRequests, [{ cwd, agent: "pi" }], "startup records targetA separately");
|
|
600
|
+
assert.deepEqual(sent, []);
|
|
601
|
+
// Like host event delivery, an event without a subscriber is a no-op.
|
|
602
|
+
await toolResult?.({
|
|
603
|
+
type: "tool_result", toolName: "read", toolCallId: "777-read",
|
|
604
|
+
input: { path: "src/example.ts" },
|
|
605
|
+
content: [{ type: "text", text: "export const value = 1;" }],
|
|
606
|
+
isError: false,
|
|
607
|
+
}, session);
|
|
608
|
+
if (ownsMutation) {
|
|
609
|
+
await toolResult?.({
|
|
610
|
+
type: "tool_result", toolName: "write", toolCallId: "777-write",
|
|
611
|
+
input: { path: "src/example.ts", content: "export const value = 2;" },
|
|
612
|
+
content: [{ type: "text", text: "Successfully wrote src/example.ts" }],
|
|
613
|
+
isError: false,
|
|
614
|
+
}, session);
|
|
615
|
+
}
|
|
616
|
+
// In the read-only case, only foreign session B changes the shared
|
|
617
|
+
// candidate. No successful mutation event is delivered to session A.
|
|
618
|
+
// In the positive control, the direct write above owns the change.
|
|
619
|
+
targetIdentity = targetB;
|
|
620
|
+
const callsBeforeEnd = statusRequests.length;
|
|
621
|
+
await agentEnd!(agentEndEvent, session);
|
|
622
|
+
const reminders = sent.filter(({ message }) => message.customType === "gentle-pi.review-preflight");
|
|
623
|
+
assert.deepEqual({
|
|
624
|
+
statusRequests: statusRequests.slice(callsBeforeEnd),
|
|
625
|
+
reminderCount: reminders.length,
|
|
626
|
+
}, ownsMutation ? {
|
|
627
|
+
statusRequests: [{ cwd, agent: "pi" }], reminderCount: 1,
|
|
628
|
+
} : {
|
|
629
|
+
statusRequests: [], reminderCount: 0,
|
|
630
|
+
}, ownsMutation
|
|
631
|
+
? "a successful direct write must retain native preflight and its reminder"
|
|
632
|
+
: "read-only session A must neither query shared STATUS nor remind for session B's target");
|
|
633
|
+
if (ownsMutation) assert.ok(String(reminders[0]?.message.content).includes(targetB));
|
|
634
|
+
});
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
test("agent_end reminds for an own write after startup with RDD off", async () => {
|
|
333
639
|
const targetIdentity = `sha256:${"5".repeat(64)}`;
|
|
334
640
|
let mode: "on" | "off" = "off";
|
|
335
641
|
const statusRequests: unknown[] = [];
|
|
@@ -355,12 +661,13 @@ test("session_start with RDD off records no baseline, so agent_end still reminds
|
|
|
355
661
|
assert.deepEqual(statusRequests, []);
|
|
356
662
|
|
|
357
663
|
mode = "on";
|
|
664
|
+
await directWrite(handlers, session);
|
|
358
665
|
await agentEnd!(agentEndEvent, session);
|
|
359
|
-
assert.equal(sent.length, 1, "
|
|
666
|
+
assert.equal(sent.length, 1, "startup with RDD off does not prevent an own-write reminder after RDD is enabled");
|
|
360
667
|
});
|
|
361
668
|
});
|
|
362
669
|
|
|
363
|
-
test("
|
|
670
|
+
test("agent_end reminds for an own write after startup STATUS rejects", async () => {
|
|
364
671
|
const targetIdentity = `sha256:${"6".repeat(64)}`;
|
|
365
672
|
let shouldThrow = true;
|
|
366
673
|
const native = {
|
|
@@ -379,12 +686,13 @@ test("session_start whose targetStatus rejects records no baseline, so agent_end
|
|
|
379
686
|
await assert.doesNotReject(async () => sessionStart!({}, session));
|
|
380
687
|
|
|
381
688
|
shouldThrow = false;
|
|
689
|
+
await directWrite(handlers, session);
|
|
382
690
|
await agentEnd!(agentEndEvent, session);
|
|
383
|
-
assert.equal(sent.length, 1, "STATUS
|
|
691
|
+
assert.equal(sent.length, 1, "startup STATUS failure does not prevent a subsequent own-write reminder");
|
|
384
692
|
});
|
|
385
693
|
});
|
|
386
694
|
|
|
387
|
-
test("session_shutdown
|
|
695
|
+
test("session_shutdown does not turn the baseline into an own mutation", async () => {
|
|
388
696
|
const targetIdentity = `sha256:${"7".repeat(64)}`;
|
|
389
697
|
const native = {
|
|
390
698
|
reviewMode: onMode("on"),
|
|
@@ -403,6 +711,6 @@ test("session_shutdown clears the recorded baseline so the same identity reminds
|
|
|
403
711
|
|
|
404
712
|
await shutdown!({}, session);
|
|
405
713
|
await agentEnd!(agentEndEvent, session);
|
|
406
|
-
assert.equal(sent.length,
|
|
714
|
+
assert.equal(sent.length, 0, "shutdown never turns a foreign baseline into an own mutation");
|
|
407
715
|
});
|
|
408
716
|
});
|