gentle-pi 2.3.0 → 2.5.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 +195 -11
- package/assets/agents/gentle-ai-worker.md +9 -0
- package/assets/agents/sdd-explore.md +1 -0
- package/assets/orchestrator-delegation.md +21 -10
- package/assets/orchestrator.md +8 -12
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
- package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/README.md +10 -0
- package/contracts/review-provider-contract-mirror/v1.2.0/bundle/manifest.json +74 -0
- package/contracts/review-provider-contract-mirror/v1.2.0/bundle/orchestration/pi.md +53 -0
- package/contracts/review-provider-contract-mirror/v1.2.0/bundle/schemas/targeted-validator.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-capabilities.baseline.json +9 -2
- package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-roles.baseline.json +2 -2
- package/docs/delegated-verification.md +25 -0
- package/docs/review-integration.md +1 -1
- package/docs/telemetry.md +38 -0
- package/extensions/ask-user-choice.ts +26 -20
- package/extensions/codegraph-tools.ts +94 -5
- package/extensions/gentle-agents.ts +588 -0
- package/extensions/gentle-ai.ts +1421 -143
- package/extensions/gentle-shell.ts +547 -0
- package/extensions/gentle-todo.ts +199 -0
- package/extensions/quiet-tools.ts +1 -1
- package/lib/agent-home.ts +8 -0
- package/lib/agents-config.ts +318 -0
- package/lib/agents-history.ts +80 -0
- package/lib/agents-protocol.ts +429 -0
- package/lib/agents-runner.ts +490 -0
- package/lib/agents-transcript.ts +87 -0
- package/lib/agents-view.ts +557 -0
- package/lib/agents-widget.ts +222 -0
- package/lib/gentle-ai-renderer.ts +142 -26
- 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 +103 -12
- package/lib/provider-contract-bundle.ts +88 -6
- package/lib/review-candidate-view-owner.ts +177 -0
- package/lib/review-candidate-view.ts +127 -35
- package/lib/review-consent-ui.ts +65 -0
- package/lib/review-host-relay.ts +146 -60
- package/lib/review-integration-v2.ts +92 -13
- package/lib/review-last-event-controller.ts +1 -0
- package/lib/review-relay-contract.ts +11 -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 +219 -0
- package/lib/sdd-preflight.ts +2 -2
- package/lib/shell-bar.ts +138 -0
- package/lib/shell-card.ts +136 -0
- package/lib/shell-changes-view.ts +205 -0
- package/lib/shell-changes.ts +210 -0
- package/lib/shell-gauge.ts +40 -0
- package/lib/shell-prompt.ts +119 -0
- package/lib/shell-todo.ts +280 -0
- package/lib/shell-usage-view.ts +76 -0
- package/lib/shell-usage.ts +246 -0
- package/lib/telemetry-trigger.ts +151 -0
- package/package.json +4 -4
- package/runtime/native-review-cli.mjs +102 -11
- package/runtime/review-integration-v2.mjs +92 -13
- package/runtime/review-relay-contract.mjs +11 -0
- package/runtime/review-risk-assessment.mjs +340 -0
- package/runtime/telemetry-trigger.mjs +152 -0
- package/scripts/build-runtime-modules.mjs +2 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/test-packed-runner.mjs +22 -0
- package/scripts/verify-package-files.mjs +18 -13
- package/skills/_shared/review-ledger-contract.md +9 -1
- package/skills/issue-creation/SKILL.md +53 -93
- package/tests/agents-config.test.ts +143 -0
- package/tests/agents-fake-child.ts +52 -0
- package/tests/agents-history.test.ts +54 -0
- package/tests/agents-protocol.test.ts +153 -0
- package/tests/agents-runner-process.test.ts +111 -0
- package/tests/agents-runner.test.ts +402 -0
- package/tests/agents-transcript.test.ts +30 -0
- package/tests/agents-view.test.ts +274 -0
- package/tests/agents-widget.test.ts +111 -0
- package/tests/ask-user-choice.test.ts +157 -3
- package/tests/codegraph-tools.test.ts +110 -1
- package/tests/devbinary/native-review-parity.devtest.ts +108 -0
- package/tests/fixtures/agents-process-child.mjs +23 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/README.md +22 -0
- package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/manifest.json +11 -2
- package/tests/fixtures/provider-contract-bundle/v1.2.0/orchestration/pi.md +97 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/targeted-validator.json +1 -0
- package/tests/gentle-agents.test.ts +741 -0
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-installer.test.ts +47 -47
- package/tests/gentle-ai-renderer.test.ts +65 -0
- package/tests/gentle-ai.test.ts +31 -14
- package/tests/gentle-card-text.ts +35 -0
- package/tests/gentle-shell.test.ts +527 -0
- package/tests/gentle-todo.test.ts +182 -0
- package/tests/issue-creation-skill.test.ts +103 -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 +33 -1
- package/tests/native-review-cli.test.ts +40 -0
- 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 +29 -22
- package/tests/orchestrator-budget.test.ts +71 -2
- package/tests/orchestrator-rdd-ownership.test.ts +10 -1
- package/tests/package-manifest.test.ts +134 -9
- package/tests/provider-contract-bundle.test.ts +76 -0
- package/tests/provider-contract-mirror.test.ts +19 -0
- package/tests/quiet-tool-rendering.test.ts +96 -37
- package/tests/rdd-aware-verification-contract.test.ts +216 -0
- package/tests/rdd-status-line.test.ts +286 -0
- package/tests/review-agent-end-preflight.test.ts +408 -0
- package/tests/review-candidate-view.test.ts +452 -6
- package/tests/review-contract-prompt.test.ts +142 -0
- package/tests/review-controller-native-recovery.test.ts +29 -4
- package/tests/review-controller-native-routing.test.ts +321 -4
- package/tests/review-controller-workspace-root.test.ts +45 -2
- package/tests/review-controller.test.ts +26 -1
- package/tests/review-host-relay-routing.test.ts +229 -11
- package/tests/review-host-relay.test.ts +195 -7
- package/tests/review-integration-v2-forward.test.ts +47 -0
- package/tests/review-integration-v2.test.ts +112 -0
- package/tests/review-last-event-closure.test.ts +7 -2
- package/tests/review-ledger-contract.test.ts +1 -1
- package/tests/review-relay-contract.test.ts +26 -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 +608 -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 +126 -0
- package/tests/runtime-harness.mjs +1 -0
- package/tests/shell-bar.test.ts +176 -0
- package/tests/shell-card.test.ts +118 -0
- package/tests/shell-changes-view.test.ts +146 -0
- package/tests/shell-changes.test.ts +182 -0
- package/tests/shell-prompt.test.ts +118 -0
- package/tests/shell-todo.test.ts +170 -0
- package/tests/shell-usage-view.test.ts +62 -0
- package/tests/shell-usage.test.ts +197 -0
- package/tests/telemetry-trigger.test.ts +349 -0
- package/tests/writer-edit-surface-scope.test.ts +153 -17
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/targeted-validator.json +0 -0
- /package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/schemas/targeted-validator.schema.json +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
// gentle-ai#4309 owns anonymous usage telemetry end to end: install and
|
|
4
|
+
// heartbeat events, the notice, every kill switch, and the wire contract.
|
|
5
|
+
// gentle-pi#677 only nudges the local binary once per process, best-effort,
|
|
6
|
+
// and never surfaces the outcome to the user. This module is the pure,
|
|
7
|
+
// testable core of that nudge (`shouldTriggerTelemetry`,
|
|
8
|
+
// `spawnTelemetryTrigger`) plus the decode helper the foreground
|
|
9
|
+
// `/gentle:telemetry` slash command uses to relay `<op> --json` output
|
|
10
|
+
// (`decodeTelemetryTriggerDecision`).
|
|
11
|
+
|
|
12
|
+
export const TELEMETRY_TRIGGER_KILL_TIMEOUT_MS = 3_000;
|
|
13
|
+
|
|
14
|
+
export const TELEMETRY_TRIGGER_CONTRACT = "gentle-ai.telemetry-trigger/v1";
|
|
15
|
+
|
|
16
|
+
export const TELEMETRY_TRIGGER_DECISIONS = [
|
|
17
|
+
"enrolled",
|
|
18
|
+
"sent_install",
|
|
19
|
+
"sent_heartbeat",
|
|
20
|
+
"rate_limited",
|
|
21
|
+
"backoff",
|
|
22
|
+
"disabled",
|
|
23
|
+
] as const;
|
|
24
|
+
export type TelemetryTriggerDecision = (typeof TELEMETRY_TRIGGER_DECISIONS)[number];
|
|
25
|
+
|
|
26
|
+
export interface TelemetryTriggerDecisionV1 {
|
|
27
|
+
schema: typeof TELEMETRY_TRIGGER_CONTRACT;
|
|
28
|
+
decision: TelemetryTriggerDecision;
|
|
29
|
+
source: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Decodes `gentle-ai telemetry trigger --json` stdout. An older binary
|
|
34
|
+
* without the verb prints `unknown telemetry command` and exits non-zero, so
|
|
35
|
+
* its stdout never parses as JSON here; that -- along with malformed JSON, a
|
|
36
|
+
* mismatched schema, an unrecognized decision, or a missing/non-string
|
|
37
|
+
* source -- means "nothing to do" and returns undefined rather than
|
|
38
|
+
* throwing.
|
|
39
|
+
*/
|
|
40
|
+
export function decodeTelemetryTriggerDecision(stdout: string): TelemetryTriggerDecisionV1 | undefined {
|
|
41
|
+
let parsed: unknown;
|
|
42
|
+
try {
|
|
43
|
+
parsed = JSON.parse(stdout);
|
|
44
|
+
} catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return undefined;
|
|
48
|
+
const record = parsed as Record<string, unknown>;
|
|
49
|
+
if (record.schema !== TELEMETRY_TRIGGER_CONTRACT) return undefined;
|
|
50
|
+
if (typeof record.decision !== "string") return undefined;
|
|
51
|
+
if (!(TELEMETRY_TRIGGER_DECISIONS as readonly string[]).includes(record.decision)) return undefined;
|
|
52
|
+
if (typeof record.source !== "string" || record.source.length === 0) return undefined;
|
|
53
|
+
return {
|
|
54
|
+
schema: TELEMETRY_TRIGGER_CONTRACT,
|
|
55
|
+
decision: record.decision as TelemetryTriggerDecision,
|
|
56
|
+
source: record.source,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type TelemetryKillSwitchReason = "do-not-track" | "telemetry-disabled" | "ci";
|
|
61
|
+
|
|
62
|
+
function telemetryKillSwitchReason(env: Readonly<Record<string, string | undefined>>): TelemetryKillSwitchReason | undefined {
|
|
63
|
+
if (env.DO_NOT_TRACK === "1") return "do-not-track";
|
|
64
|
+
if (env.GENTLE_AI_TELEMETRY === "0") return "telemetry-disabled";
|
|
65
|
+
if (env.CI === "true") return "ci";
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* True unless one of Gentle Pi's own kill switches is set
|
|
71
|
+
* (`DO_NOT_TRACK=1`, `GENTLE_AI_TELEMETRY=0`, or `CI=true`). The binary
|
|
72
|
+
* enforces its own opt-out and rate limiting independently; this predicate
|
|
73
|
+
* only decides whether Gentle Pi spawns the trigger at all.
|
|
74
|
+
*/
|
|
75
|
+
export function shouldTriggerTelemetry(env: Readonly<Record<string, string | undefined>>): boolean {
|
|
76
|
+
return telemetryKillSwitchReason(env) === undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// A minimal structural subset of node:child_process's ChildProcess, mirroring
|
|
80
|
+
// the ChildLike/Spawn seam already used by lib/agents-runner.ts: narrow
|
|
81
|
+
// enough that tests can stub it without touching a real process.
|
|
82
|
+
export interface TelemetryTriggerChildLike {
|
|
83
|
+
unref(): unknown;
|
|
84
|
+
kill(signal?: NodeJS.Signals | number): boolean;
|
|
85
|
+
on(event: "error", listener: (error: Error) => void): unknown;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface TelemetryTriggerSpawnOptions {
|
|
89
|
+
cwd: string;
|
|
90
|
+
env: NodeJS.ProcessEnv;
|
|
91
|
+
detached: boolean;
|
|
92
|
+
stdio: "ignore";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type TelemetryTriggerSpawn = (
|
|
96
|
+
command: string,
|
|
97
|
+
args: readonly string[],
|
|
98
|
+
options: TelemetryTriggerSpawnOptions,
|
|
99
|
+
) => TelemetryTriggerChildLike;
|
|
100
|
+
|
|
101
|
+
export interface SpawnTelemetryTriggerOptions {
|
|
102
|
+
executable: string;
|
|
103
|
+
cwd: string;
|
|
104
|
+
env: NodeJS.ProcessEnv;
|
|
105
|
+
spawn?: TelemetryTriggerSpawn;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export type TelemetryTriggerReason = "spawned" | "spawn-error" | TelemetryKillSwitchReason;
|
|
109
|
+
|
|
110
|
+
export interface TelemetryTriggerResult {
|
|
111
|
+
spawned: boolean;
|
|
112
|
+
reason: TelemetryTriggerReason;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Nudges `<executable> telemetry trigger --json`, detached and
|
|
117
|
+
* fire-and-forget. Never throws, never awaits the child's exit, and never
|
|
118
|
+
* blocks session start: a missing binary, an old binary without the verb, or
|
|
119
|
+
* a spawn failure all just report `{ spawned: false, ... }`. A 3 s timer
|
|
120
|
+
* kills a runaway child; the timer is unref'd so it can never keep the host
|
|
121
|
+
* process alive on its own, matching the detached child's own `unref()`.
|
|
122
|
+
*/
|
|
123
|
+
export function spawnTelemetryTrigger(options: SpawnTelemetryTriggerOptions): TelemetryTriggerResult {
|
|
124
|
+
const killSwitch = telemetryKillSwitchReason(options.env);
|
|
125
|
+
if (killSwitch !== undefined) return { spawned: false, reason: killSwitch };
|
|
126
|
+
const spawnImpl = options.spawn ?? (nodeSpawn as unknown as TelemetryTriggerSpawn);
|
|
127
|
+
try {
|
|
128
|
+
const child = spawnImpl(options.executable, ["telemetry", "trigger", "--json"], {
|
|
129
|
+
cwd: options.cwd,
|
|
130
|
+
env: options.env,
|
|
131
|
+
detached: true,
|
|
132
|
+
stdio: "ignore",
|
|
133
|
+
});
|
|
134
|
+
child.on("error", () => {
|
|
135
|
+
// Fire-and-forget: a spawn-time or runtime child error never surfaces
|
|
136
|
+
// to the caller and never affects session start.
|
|
137
|
+
});
|
|
138
|
+
const killTimer = setTimeout(() => {
|
|
139
|
+
try {
|
|
140
|
+
child.kill();
|
|
141
|
+
} catch {
|
|
142
|
+
// Best effort only; the child may already have exited.
|
|
143
|
+
}
|
|
144
|
+
}, TELEMETRY_TRIGGER_KILL_TIMEOUT_MS);
|
|
145
|
+
killTimer.unref?.();
|
|
146
|
+
child.unref();
|
|
147
|
+
return { spawned: true, reason: "spawned" };
|
|
148
|
+
} catch {
|
|
149
|
+
return { spawned: false, reason: "spawn-error" };
|
|
150
|
+
}
|
|
151
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gentle-pi",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Turn Pi into el Gentleman: a senior-architect development harness with SDD/OpenSpec, subagents, strict TDD evidence, review guardrails, and skill discovery.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -66,11 +66,11 @@
|
|
|
66
66
|
]
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@earendil-works/pi-tui": "
|
|
69
|
+
"@earendil-works/pi-tui": "0.85.1",
|
|
70
70
|
"@heyhuynhgiabuu/pi-pretty": "0.6.14"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"@earendil-works/pi-coding-agent": "
|
|
73
|
+
"@earendil-works/pi-coding-agent": ">=0.85.1",
|
|
74
74
|
"typebox": "*"
|
|
75
75
|
},
|
|
76
76
|
"peerDependenciesMeta": {
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
}
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
|
-
"@earendil-works/pi-coding-agent": "
|
|
82
|
+
"@earendil-works/pi-coding-agent": "0.85.1"
|
|
83
83
|
},
|
|
84
84
|
"packageManager": "pnpm@11.1.1"
|
|
85
85
|
}
|
|
@@ -6,6 +6,7 @@ import { dirname, isAbsolute, join, posix, win32 } from "node:path";
|
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import { PackageLocalGentleAiBinaryMissingError, resolveGentleAiBinary } from "./gentle-ai-binary.mjs";
|
|
8
8
|
import { GENTLE_PI_REVIEW_RELAY_CONTRACT, GENTLE_PI_REVIEW_RELAY_CONTRACT_ENV } from "./review-relay-contract.mjs";
|
|
9
|
+
import { decodeReviewAssessmentV1, } from "./review-risk-assessment.mjs";
|
|
9
10
|
import {
|
|
10
11
|
REVIEW_INTEGRATION_CONTRACT,
|
|
11
12
|
decodeReviewAcknowledgedV1,
|
|
@@ -60,6 +61,7 @@ export const NATIVE_REVIEW_OPERATION = {
|
|
|
60
61
|
RECONCILE_AUTHORITY: "review/reconcile-authority",
|
|
61
62
|
REPAIR_LEGACY_ALIAS: "review/repair-legacy-alias",
|
|
62
63
|
MODE: "review/mode",
|
|
64
|
+
ASSESS: "review/assess",
|
|
63
65
|
REPAIR: "review/repair",
|
|
64
66
|
CAPTURE_RESULT: "review/capture-result",
|
|
65
67
|
CAPTURE_CORRECTION_PLAN: "review/capture-correction-plan",
|
|
@@ -112,6 +114,11 @@ export const NATIVE_REVIEW_ERROR_CODE = {
|
|
|
112
114
|
|
|
113
115
|
|
|
114
116
|
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
115
122
|
export const NATIVE_REVIEW_MODE_OPERATION = {
|
|
116
123
|
STATUS: "status",
|
|
117
124
|
ENABLE: "enable",
|
|
@@ -152,6 +159,17 @@ export const NATIVE_REVIEW_MODE_SCOPE = {
|
|
|
152
159
|
|
|
153
160
|
|
|
154
161
|
|
|
162
|
+
// Read-only risk assessment request (gentle-pi#662). `baseRef` requires
|
|
163
|
+
// explicit `committedOnly` acknowledgement, exactly like Native START's
|
|
164
|
+
// baseRef/committedOnly pairing, because both select a committed range
|
|
165
|
+
// instead of the ambient working tree.
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
155
173
|
|
|
156
174
|
|
|
157
175
|
|
|
@@ -305,7 +323,6 @@ export const NATIVE_REVIEW_LEGACY_ALIAS_REPAIR = {
|
|
|
305
323
|
|
|
306
324
|
|
|
307
325
|
|
|
308
|
-
|
|
309
326
|
|
|
310
327
|
|
|
311
328
|
/** Raw audited native record; Pi relays it verbatim and never reinterprets it. */
|
|
@@ -499,7 +516,12 @@ export const NATIVE_REVIEW_AUTHORITY_ENTRY_VERSION = {
|
|
|
499
516
|
} ;
|
|
500
517
|
|
|
501
518
|
|
|
502
|
-
export const NATIVE_REVIEW_AUTHORITY_ENTRY_STATUS =
|
|
519
|
+
export const NATIVE_REVIEW_AUTHORITY_ENTRY_STATUS = {
|
|
520
|
+
...NATIVE_REVIEW_AUTHORITY_STATUS,
|
|
521
|
+
INCOMPLETE_STORE_ENTRY: "incomplete-store-entry",
|
|
522
|
+
HISTORICAL_PRE_RECEIPT: "historical-pre-receipt",
|
|
523
|
+
INVALIDATED: "invalidated",
|
|
524
|
+
} ;
|
|
503
525
|
|
|
504
526
|
|
|
505
527
|
export const NATIVE_REVIEW_LOCK_STATUS = {
|
|
@@ -570,6 +592,11 @@ export const NATIVE_REVIEW_RECOVERY_DISPOSITION = {
|
|
|
570
592
|
|
|
571
593
|
|
|
572
594
|
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
|
|
573
600
|
|
|
574
601
|
|
|
575
602
|
|
|
@@ -790,6 +817,33 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
|
|
|
790
817
|
// the columns match the 2.5.0-rc.3 row. riskEvidence and hint stay dark:
|
|
791
818
|
// still not proven to reach the negotiated START path Pi reads.
|
|
792
819
|
"2.5.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
|
|
820
|
+
// Ground-truthed against the published v2.6.0 binary installed from its
|
|
821
|
+
// signed archive: `review capabilities` on the v2 lane advertises
|
|
822
|
+
// capabilities/v2.5 (protocol minor 5) and answers status/v7, consent/v3,
|
|
823
|
+
// and the `start/v4` continuation, all of which the decoders already read.
|
|
824
|
+
// `review mode status --json` still answers `gentle-ai.rdd-mode-status/v1`
|
|
825
|
+
// and `review validate --gate pre-commit` still answers
|
|
826
|
+
// `gentle-ai.review-gate-result/v1` carrying `delivery`, both verified
|
|
827
|
+
// against the binary. Declining the consent/v3 prompt during `review start`
|
|
828
|
+
// still returns `risk_evidence` only on that blocking envelope, never on
|
|
829
|
+
// the negotiated `start/v4` continuation, so riskEvidence and hint stay
|
|
830
|
+
// dark for the same reason they have on every row since 2.2.0: still not
|
|
831
|
+
// proven to reach the negotiated START path Pi reads. The closed fields Pi
|
|
832
|
+
// consumes did not change between 2.5.0 and 2.6.0, so the columns match
|
|
833
|
+
// the 2.5.0 row.
|
|
834
|
+
"2.6.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
|
|
835
|
+
// Ground-truthed against the published v2.7.0 binary installed from its
|
|
836
|
+
// signed archive: `review capabilities` on the v2 lane still advertises
|
|
837
|
+
// capabilities/v2.5 (protocol minor 5) and answers status/v7, consent/v3,
|
|
838
|
+
// and the `start/v4` continuation, all of which the decoders already read.
|
|
839
|
+
// `review mode status --json` still answers `gentle-ai.rdd-mode-status/v1`
|
|
840
|
+
// and `review validate --gate pre-commit` still answers
|
|
841
|
+
// `gentle-ai.review-gate-result/v1` carrying `delivery`, both verified
|
|
842
|
+
// against the binary. riskEvidence and hint stay dark for the same reason
|
|
843
|
+
// they have on every row since 2.2.0: still not proven to reach the
|
|
844
|
+
// negotiated START path Pi reads. The closed fields Pi consumes did not
|
|
845
|
+
// change between 2.6.0 and 2.7.0, so the columns match the 2.6.0 row.
|
|
846
|
+
"2.7.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
|
|
793
847
|
});
|
|
794
848
|
|
|
795
849
|
|
|
@@ -1032,8 +1086,15 @@ function decodeNativeReviewRecovery(value ) {
|
|
|
1032
1086
|
...(recovery.maintainer_authorization === undefined ? {} : { maintainerAuthorization: requiredString(recovery.maintainer_authorization) }),
|
|
1033
1087
|
};
|
|
1034
1088
|
}
|
|
1089
|
+
function decodeNativeReviewDiscardedWorkSummary(value ) {
|
|
1090
|
+
const discardedWork = exactObject(value, ["captured_lens_results", "findings_present"]);
|
|
1091
|
+
return {
|
|
1092
|
+
capturedLensResults: stringArray(discardedWork.captured_lens_results),
|
|
1093
|
+
findingsPresent: booleanValue(discardedWork.findings_present),
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1035
1096
|
function decodeNativeReviewStatusEntry(value ) {
|
|
1036
|
-
const entry = exactObject(value, ["version", "path", "status", "problems"], ["lineage_id", "state", "revision", "snapshot_identity", "chain_identity", "recovery"]);
|
|
1097
|
+
const entry = exactObject(value, ["version", "path", "status", "problems"], ["lineage_id", "state", "revision", "snapshot_identity", "chain_identity", "recovery", "discarded_work"]);
|
|
1037
1098
|
return {
|
|
1038
1099
|
version: enumString(entry.version, Object.values(NATIVE_REVIEW_AUTHORITY_ENTRY_VERSION)) ,
|
|
1039
1100
|
...(entry.lineage_id === undefined ? {} : { lineageId: requiredString(entry.lineage_id) }),
|
|
@@ -1044,6 +1105,7 @@ function decodeNativeReviewStatusEntry(value )
|
|
|
1044
1105
|
...(entry.snapshot_identity === undefined ? {} : { snapshotIdentity: sha256Identity(entry.snapshot_identity) }),
|
|
1045
1106
|
...(entry.chain_identity === undefined ? {} : { chainIdentity: requiredString(entry.chain_identity) }),
|
|
1046
1107
|
...(entry.recovery === undefined ? {} : { recovery: decodeNativeReviewRecovery(entry.recovery) }),
|
|
1108
|
+
...(entry.discarded_work === undefined ? {} : { discardedWork: decodeNativeReviewDiscardedWorkSummary(entry.discarded_work) }),
|
|
1047
1109
|
problems: stringArray(entry.problems),
|
|
1048
1110
|
};
|
|
1049
1111
|
}
|
|
@@ -1358,9 +1420,7 @@ class NativeReviewPlainCli {
|
|
|
1358
1420
|
if (!isCanonicalProcessString(value)) throw new TypeError(`Native ABANDON ${name} must be a non-empty, trimmed, NUL-free string`);
|
|
1359
1421
|
}
|
|
1360
1422
|
if (!Array.isArray(request.capturedLensResults) || request.capturedLensResults.some((entry) => !isCanonicalProcessString(entry))) throw new TypeError("Native ABANDON capturedLensResults must be an array of non-empty, trimmed, NUL-free strings");
|
|
1361
|
-
|
|
1362
|
-
if (typeof value !== "boolean") throw new TypeError(`Native ABANDON ${name} must be a boolean`);
|
|
1363
|
-
}
|
|
1423
|
+
if (typeof request.findingsPresent !== "boolean") throw new TypeError("Native ABANDON findingsPresent must be a boolean");
|
|
1364
1424
|
if (request.maintainerAuthorization !== nativeReviewAbandonAuthorization(request)) throw new TypeError("Native ABANDON maintainerAuthorization must match the exact lineage, revision, snapshot, reason, discarded-work, and actor binding");
|
|
1365
1425
|
const execution = await this.execute(NATIVE_REVIEW_OPERATION.ABANDON, request.cwd, [
|
|
1366
1426
|
"review", "abandon", "--cwd", request.cwd,
|
|
@@ -1449,7 +1509,11 @@ class NativeReviewPlainCli {
|
|
|
1449
1509
|
}
|
|
1450
1510
|
}
|
|
1451
1511
|
|
|
1452
|
-
export function nativeReviewAbandonAuthorization(request
|
|
1512
|
+
export function nativeReviewAbandonAuthorization(request ) {
|
|
1513
|
+
// gentle-ai 0ed9225f removed evidence records from the discarded-work summary,
|
|
1514
|
+
// so the native v2 gate verifies an exact eight-line binding (schema, lineage,
|
|
1515
|
+
// revision, snapshot_identity, reason, captured_lens_results, findings_present,
|
|
1516
|
+
// actor) — there is no evidence_records_present line to derive or relay.
|
|
1453
1517
|
return [
|
|
1454
1518
|
"gentle-ai.review-abandon-authorization/v2",
|
|
1455
1519
|
`lineage=${request.lineage}`,
|
|
@@ -1458,7 +1522,6 @@ export function nativeReviewAbandonAuthorization(request
|
|
|
1458
1522
|
`reason=${request.reason}`,
|
|
1459
1523
|
`captured_lens_results=${request.capturedLensResults.join(",")}`,
|
|
1460
1524
|
`findings_present=${request.findingsPresent}`,
|
|
1461
|
-
`evidence_records_present=${request.evidenceRecordsPresent}`,
|
|
1462
1525
|
`actor=${request.actor.trim()}`,
|
|
1463
1526
|
].join("\n");
|
|
1464
1527
|
}
|
|
@@ -1581,14 +1644,20 @@ function splitNativeConsentInvocation(invocation ) {
|
|
|
1581
1644
|
let quote ;
|
|
1582
1645
|
let escaping = false;
|
|
1583
1646
|
let started = false;
|
|
1584
|
-
|
|
1647
|
+
const source = invocation.trim();
|
|
1648
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
1649
|
+
const character = source[index] ;
|
|
1585
1650
|
if (escaping) {
|
|
1586
1651
|
current += character;
|
|
1587
1652
|
escaping = false;
|
|
1588
1653
|
started = true;
|
|
1589
1654
|
continue;
|
|
1590
1655
|
}
|
|
1591
|
-
|
|
1656
|
+
// Provider invocations are not shell commands. Keep every path backslash
|
|
1657
|
+
// verbatim, including quoted UNC and drive-root paths. Outside quotes,
|
|
1658
|
+
// only a backslash before whitespace joins a space-containing token.
|
|
1659
|
+
const next = source[index + 1];
|
|
1660
|
+
if (character === "\\" && quote === undefined && next !== undefined && /\s/.test(next)) {
|
|
1592
1661
|
escaping = true;
|
|
1593
1662
|
started = true;
|
|
1594
1663
|
continue;
|
|
@@ -1679,7 +1748,9 @@ function consentInvocationArguments(request )
|
|
|
1679
1748
|
if (words[0] !== "gentle-ai" || words[1] !== "review" || words[2] !== "start") throw new NativeReviewConsentBindingError("consent-invocation-not-start", "Native consent invocation is not a provider review START");
|
|
1680
1749
|
const arguments_ = words.slice(1);
|
|
1681
1750
|
if (exactConsentOption(arguments_, "--contract") !== REVIEW_INTEGRATION_CONTRACT) throw new NativeReviewConsentBindingError("consent-invocation-contract-changed", "Native consent invocation contract changed");
|
|
1682
|
-
|
|
1751
|
+
const providerCwd = exactConsentOption(arguments_, "--cwd");
|
|
1752
|
+
const requestedCwd = normalizeNativeReviewCwd(request.cwd);
|
|
1753
|
+
if (normalizeNativeReviewCwd(providerCwd) !== requestedCwd) throw new NativeReviewConsentBindingError("consent-invocation-cwd-changed", "Native consent invocation repository binding changed");
|
|
1683
1754
|
if (exactConsentOption(arguments_, "--target") !== request.consent.targetIdentity) throw new NativeReviewConsentBindingError("consent-invocation-target-changed", "Native consent invocation target binding changed");
|
|
1684
1755
|
if (exactConsentOption(arguments_, "--projection") !== request.consent.projection) throw new NativeReviewConsentBindingError("consent-invocation-projection-changed", "Native consent invocation projection binding changed");
|
|
1685
1756
|
const lineageId = optionalConsentLineageOption(arguments_);
|
|
@@ -2179,6 +2250,26 @@ export class NativeReviewCliV216 {
|
|
|
2179
2250
|
return decode(NATIVE_REVIEW_OPERATION.MODE, mutating, () => decodeNativeReviewMode(execution.body, request.operation));
|
|
2180
2251
|
}
|
|
2181
2252
|
|
|
2253
|
+
// Read-only risk assessment (gentle-ai#4295, gentle-pi#662). Never mutates;
|
|
2254
|
+
// an older binary without the verb, or any other process/decode failure,
|
|
2255
|
+
// rejects -- callers (the `gentle_review` tool's `assess` operation) fail
|
|
2256
|
+
// closed to `high`.
|
|
2257
|
+
async assess(request ) {
|
|
2258
|
+
if (request.baseRef !== undefined && !isCanonicalProcessString(request.baseRef)) throw new TypeError("Native ASSESS baseRef must be a non-empty, trimmed, NUL-free string");
|
|
2259
|
+
if (request.baseRef !== undefined && request.committedOnly !== true) throw new TypeError("Native ASSESS baseRef requires explicit committedOnly acknowledgement");
|
|
2260
|
+
if (request.baseRef === undefined && request.committedOnly !== undefined) throw new TypeError("Native ASSESS committedOnly requires an explicit baseRef");
|
|
2261
|
+
const cwd = await canonicalNativeReviewCwd(request.cwd);
|
|
2262
|
+
const execution = await this.invoke(
|
|
2263
|
+
NATIVE_REVIEW_OPERATION.ASSESS,
|
|
2264
|
+
cwd,
|
|
2265
|
+
["review", "assess", "--cwd", cwd, ...(request.baseRef === undefined ? [] : ["--base-ref", request.baseRef, "--committed-only"]), "--json"],
|
|
2266
|
+
false,
|
|
2267
|
+
request.signal,
|
|
2268
|
+
this.executablePath(NATIVE_REVIEW_OPERATION.ASSESS, false),
|
|
2269
|
+
);
|
|
2270
|
+
return decode(NATIVE_REVIEW_OPERATION.ASSESS, false, () => decodeReviewAssessmentV1(execution.body));
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2182
2273
|
// Recovery commands are version-gated plain CLI operations outside the
|
|
2183
2274
|
// negotiated integration-v1 contract, exactly like reviewStatus.
|
|
2184
2275
|
reclaim(request ) {
|
|
@@ -80,7 +80,10 @@ const RISK_LEVELS = ["low", "medium", "high"] ;
|
|
|
80
80
|
const REVIEW_LENSES = ["review-risk", "review-resilience", "review-readability", "review-reliability"] ;
|
|
81
81
|
const RISK_REASON_CODES = ["configuration_change", "empty_content", "executable_change", "executable_mode", "hot_path", "large_change", "non_executable_only", "process_boundary", "process_scan_limit", "service_token", "shell_source"] ;
|
|
82
82
|
const RISK_SIGNALS = ["auth", "update", "security", "payments", "permissions", "shell_process"] ;
|
|
83
|
-
|
|
83
|
+
// "collect" and "execute" are the v2 envelope's projection of a live
|
|
84
|
+
// transaction whose next_transition is mandatory: the root action names the
|
|
85
|
+
// transition kind instead of reading as a terminal stop.
|
|
86
|
+
const STATUS_ACTIONS = ["start", "recover", "maintainer_action", "select_lineage", "repair_authority", "stop", "collect", "execute"] ;
|
|
84
87
|
const RECEIPT_STATUSES = ["expected_missing", "present", "publication_pending", "not_applicable"] ;
|
|
85
88
|
|
|
86
89
|
export const REVIEW_STATUS_ACTION_DISPOSITION = {
|
|
@@ -184,6 +187,19 @@ const CAPABILITIES_SCHEMA_IDENTITIES
|
|
|
184
187
|
requiredMandatoryFeatures: REQUIRED_MANDATORY_FEATURES_V23,
|
|
185
188
|
optionalFeatureFloor: 14,
|
|
186
189
|
}),
|
|
190
|
+
// Ground-truthed against the published v2.6.0 binary: capabilities/v2.5
|
|
191
|
+
// advertises the same required surface as v2.4 (status/v6 is still
|
|
192
|
+
// advertised for compatibility) plus the new status/v7 schema, which is a
|
|
193
|
+
// superset-checked addition, not a requirement -- decodeReviewStatusV3
|
|
194
|
+
// accepts v7 as an additive extension of v6, so the required-schema floor
|
|
195
|
+
// stays unchanged. The v2.6.0 binary advertised 15 optional features
|
|
196
|
+
// (floor stays at 14, its established minimum).
|
|
197
|
+
"gentle-ai.review-integration.capabilities/v2.5": Object.freeze({
|
|
198
|
+
protocolMinor: 5,
|
|
199
|
+
requiredSchemas: Object.freeze([...REQUIRED_SCHEMAS_COMMON_V23, "gentle-ai.review-integration.capabilities/v2.5", "gentle-ai.review-integration.consent/v3", "gentle-ai.review-integration.start/v4", "gentle-ai.review-integration.status/v6", "gentle-ai.review-intended-untracked-selection/v1"]),
|
|
200
|
+
requiredMandatoryFeatures: REQUIRED_MANDATORY_FEATURES_V23,
|
|
201
|
+
optionalFeatureFloor: 14,
|
|
202
|
+
}),
|
|
187
203
|
});
|
|
188
204
|
const OPTIONAL_FEATURE_NAMES = Object.freeze([
|
|
189
205
|
"base_ref_workspace_overlay",
|
|
@@ -391,6 +407,8 @@ const REPOSITORY_CONTEXT_OUTCOMES = ["applied", "pending", "blocked_conflict", "
|
|
|
391
407
|
|
|
392
408
|
|
|
393
409
|
|
|
410
|
+
|
|
411
|
+
|
|
394
412
|
|
|
395
413
|
|
|
396
414
|
|
|
@@ -533,6 +551,18 @@ export const REVIEW_PROVIDER_ROLE_CAPTURE_OPERATIONS = Object.freeze(Object.valu
|
|
|
533
551
|
|
|
534
552
|
|
|
535
553
|
|
|
554
|
+
// gentle-pi#627: the exact `gentle-ai sync` invocation that resolves a stale
|
|
555
|
+
// managed-asset set (STATUS next_transition stop, and START's preflight
|
|
556
|
+
// failure envelope both carry this same shape).
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
|
|
536
566
|
|
|
537
567
|
|
|
538
568
|
|
|
@@ -597,6 +627,8 @@ export const REVIEW_PROVIDER_ROLE_CAPTURE_OPERATIONS = Object.freeze(Object.valu
|
|
|
597
627
|
|
|
598
628
|
|
|
599
629
|
|
|
630
|
+
|
|
631
|
+
|
|
600
632
|
|
|
601
633
|
|
|
602
634
|
|
|
@@ -691,6 +723,8 @@ const FAILURE_CAUSE_CATEGORIES = ["inventory_io_or_layout", "lock_ambiguous", "r
|
|
|
691
723
|
|
|
692
724
|
|
|
693
725
|
|
|
726
|
+
|
|
727
|
+
|
|
694
728
|
|
|
695
729
|
|
|
696
730
|
|
|
@@ -1625,8 +1659,11 @@ function decodeCollectInput(value , label , v5 , v6
|
|
|
1625
1659
|
}
|
|
1626
1660
|
|
|
1627
1661
|
if (captureOperation === "review.capture-result") {
|
|
1628
|
-
|
|
1629
|
-
|
|
1662
|
+
// changed_path_manifest is optional here: the artifact subject's
|
|
1663
|
+
// changed_path_manifest_sha256 already binds the frozen manifest, and
|
|
1664
|
+
// gentle-ai stops inlining one copy per lens (gentle-ai#3922).
|
|
1665
|
+
if (input.artifact_subject === undefined || input.base_tree === undefined || input.candidate_tree === undefined) {
|
|
1666
|
+
throw new TypeError(`${label} requires artifact_subject, base_tree, and candidate_tree`);
|
|
1630
1667
|
}
|
|
1631
1668
|
if (schema !== "https://gentle-ai.dev/schema/review/reviewer/v1") throw new TypeError(`${label}.schema must be https://gentle-ai.dev/schema/review/reviewer/v1`);
|
|
1632
1669
|
} else if (input.artifact_subject !== undefined || input.base_tree !== undefined || input.candidate_tree !== undefined || input.changed_path_manifest !== undefined) {
|
|
@@ -1659,12 +1696,32 @@ function decodeCollectInput(value , label , v5 , v6
|
|
|
1659
1696
|
};
|
|
1660
1697
|
}
|
|
1661
1698
|
|
|
1699
|
+
// gentle-pi#627: gentle-ai's managed-assets continuation (STATUS next_transition
|
|
1700
|
+
// stop reason_code=managed_assets_outdated, and START's preflight failure
|
|
1701
|
+
// envelope) names the exact `gentle-ai sync` invocation that resolves a stale
|
|
1702
|
+
// managed-asset set without abandoning the frozen candidate. Decoded verbatim
|
|
1703
|
+
// from internal/cli/review_operation_contract.go's ReviewManagedAssetsContinuation.
|
|
1704
|
+
export function decodeReviewManagedAssetsContinuationV1(value , label ) {
|
|
1705
|
+
const body = exactRecord(value, label, ["operation", "command"], ["agent", "stale_assets"]);
|
|
1706
|
+
const operation = enumeration(body.operation, ["sync"] , `${label}.operation`);
|
|
1707
|
+
const command = nonempty(body.command, `${label}.command`);
|
|
1708
|
+
const agent = body.agent === undefined ? undefined : nonempty(body.agent, `${label}.agent`);
|
|
1709
|
+
const staleAssets = body.stale_assets === undefined ? undefined : stringArray(body.stale_assets, `${label}.stale_assets`, { minimum: 1 });
|
|
1710
|
+
return { operation, command, ...(agent === undefined ? {} : { agent }), ...(staleAssets === undefined ? {} : { staleAssets }) };
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1662
1713
|
export function decodeReviewNextTransitionV3(value , options = {}) {
|
|
1663
1714
|
const v6 = options.v6 === true;
|
|
1664
1715
|
const v5 = options.v5 === true || v6;
|
|
1665
|
-
const transition = exactRecord(value, "next_transition", ["kind", "reason_code"], ["execute", "collect", ...(v5 ? ["correction_request"] : [])]);
|
|
1716
|
+
const transition = exactRecord(value, "next_transition", ["kind", "reason_code"], ["execute", "collect", ...(v5 ? ["correction_request"] : []), "continuation"]);
|
|
1666
1717
|
const kind = enumeration(transition.kind, ["execute", "collect", "stop"] , "next_transition.kind");
|
|
1667
1718
|
const reasonCode = text(transition.reason_code, "next_transition.reason_code", { minimum: 1, pattern: /^[a-z0-9_]+$/ });
|
|
1719
|
+
const continuation = transition.continuation === undefined ? undefined : decodeReviewManagedAssetsContinuationV1(transition.continuation, "next_transition.continuation");
|
|
1720
|
+
// gentle-pi#627: continuation is optional on every status version an older
|
|
1721
|
+
// gentle-ai may emit a managed_assets_outdated stop with no continuation at
|
|
1722
|
+
// all, and decode must not refuse the whole envelope for that. When present
|
|
1723
|
+
// it is only valid on a stop with this exact reason_code.
|
|
1724
|
+
if (continuation !== undefined && !(kind === "stop" && reasonCode === "managed_assets_outdated")) throw new TypeError("next_transition.continuation is only valid for a stop transition with reason_code managed_assets_outdated");
|
|
1668
1725
|
|
|
1669
1726
|
// status/v5: the bounded correction plan request rides exactly its two
|
|
1670
1727
|
// reason codes and never any other (vendored status-v5.schema.json).
|
|
@@ -1714,7 +1771,7 @@ export function decodeReviewNextTransitionV3(value , options
|
|
|
1714
1771
|
return { kind, reasonCode, collect: { inputs }, ...(correctionRequest === undefined ? {} : { correctionRequest }) };
|
|
1715
1772
|
}
|
|
1716
1773
|
if (transition.execute !== undefined || transition.collect !== undefined) throw new TypeError("next_transition stop cannot carry a transition");
|
|
1717
|
-
return { kind, reasonCode, ...(correctionRequest === undefined ? {} : { correctionRequest }) };
|
|
1774
|
+
return { kind, reasonCode, ...(correctionRequest === undefined ? {} : { correctionRequest }), ...(continuation === undefined ? {} : { continuation }) };
|
|
1718
1775
|
}
|
|
1719
1776
|
|
|
1720
1777
|
// ---------------------------------------------------------------------------
|
|
@@ -1762,15 +1819,19 @@ export function decodeReviewStatusV3(value ) {
|
|
|
1762
1819
|
// Additive forward acceptance: status/v5 (gentle-ai main; ground-truthed
|
|
1763
1820
|
// against a live capture and the vendored status-v5.schema.json) is the v3
|
|
1764
1821
|
// key set plus the optional forecast and the v5-only next_transition
|
|
1765
|
-
// surfaces. status/v6 adds the intended-untracked selection;
|
|
1766
|
-
//
|
|
1822
|
+
// surfaces. status/v6 adds the intended-untracked selection; status/v7
|
|
1823
|
+
// (gentle-ai v2.6.0, advertised through capabilities/v2.5 alongside v6)
|
|
1824
|
+
// adds only the top-level optional `eligible_untracked_inventory` digest,
|
|
1825
|
+
// so it is decoded on the v6 surface. v3 keeps rejecting every v5/v6/v7-only
|
|
1826
|
+
// field.
|
|
1767
1827
|
const schema = typeof value === "object" && value !== null ? (value ).schema : undefined;
|
|
1768
|
-
const
|
|
1828
|
+
const v7 = schema === "gentle-ai.review-integration.status/v7";
|
|
1829
|
+
const v6 = v7 || schema === "gentle-ai.review-integration.status/v6";
|
|
1769
1830
|
const v5 = v6 || schema === "gentle-ai.review-integration.status/v5";
|
|
1770
1831
|
const body = exactRecord(value, "status", [
|
|
1771
1832
|
"schema", "contract", "operation", "applicability", "action", "replayability", "target_identity", "projection", "repair", "candidates",
|
|
1772
|
-
], ["authority", "frozen", "action_disposition", "eligibility", "next_transition", "authority_target_identity", ...(v5 ? ["receipt", "forecast", "repository_context", "validation_request"] : [])]);
|
|
1773
|
-
requireIdentity(body, v6 ? "gentle-ai.review-integration.status/v6" : v5 ? "gentle-ai.review-integration.status/v5" : "gentle-ai.review-integration.status/v3", REVIEW_INTEGRATION_OPERATION.STATUS);
|
|
1833
|
+
], ["authority", "frozen", "action_disposition", "eligibility", "next_transition", "authority_target_identity", ...(v5 ? ["receipt", "forecast", "repository_context", "validation_request"] : []), ...(v7 ? ["eligible_untracked_inventory"] : [])]);
|
|
1834
|
+
requireIdentity(body, v7 ? "gentle-ai.review-integration.status/v7" : v6 ? "gentle-ai.review-integration.status/v6" : v5 ? "gentle-ai.review-integration.status/v5" : "gentle-ai.review-integration.status/v3", REVIEW_INTEGRATION_OPERATION.STATUS);
|
|
1774
1835
|
|
|
1775
1836
|
const applicability = enumeration(body.applicability, ["current_target", "unrelated", "ambiguous", "corrupted"] , "status.applicability");
|
|
1776
1837
|
let receipt ;
|
|
@@ -1799,11 +1860,12 @@ export function decodeReviewStatusV3(value ) {
|
|
|
1799
1860
|
|
|
1800
1861
|
let frozen ;
|
|
1801
1862
|
if (body.frozen !== undefined) {
|
|
1802
|
-
const source = exactRecord(body.frozen, "status.frozen", ["tier", "original_changed_lines", "correction_budget"]);
|
|
1863
|
+
const source = exactRecord(body.frozen, "status.frozen", ["tier", "original_changed_lines", "correction_budget"], ["changed_path_manifest_sha256"]);
|
|
1803
1864
|
frozen = {
|
|
1804
1865
|
tier: enumeration(source.tier, RISK_LEVELS, "status.frozen.tier"),
|
|
1805
1866
|
originalChangedLines: integer(source.original_changed_lines, "status.frozen.original_changed_lines"),
|
|
1806
1867
|
correctionBudget: integer(source.correction_budget, "status.frozen.correction_budget", 0, 200),
|
|
1868
|
+
...(source.changed_path_manifest_sha256 === undefined ? {} : { changedPathManifestSha256: sha256(source.changed_path_manifest_sha256, "status.frozen.changed_path_manifest_sha256") }),
|
|
1807
1869
|
};
|
|
1808
1870
|
}
|
|
1809
1871
|
if (authority?.version === REVIEW_AUTHORITY_VERSION.COMPACT_V2 && frozen === undefined) throw new TypeError("compact-v2 status requires frozen metadata");
|
|
@@ -1852,6 +1914,15 @@ export function decodeReviewStatusV3(value ) {
|
|
|
1852
1914
|
};
|
|
1853
1915
|
}
|
|
1854
1916
|
|
|
1917
|
+
// status/v7 top-level optional digest (gentle-ai v2.6.0): resolves #4066's
|
|
1918
|
+
// closed loop where `sdd-attempt finish` named a digest status never
|
|
1919
|
+
// published. Absent on the `staged` projection, which never resolves an
|
|
1920
|
+
// inventory, so it stays structurally optional rather than a required v7
|
|
1921
|
+
// field.
|
|
1922
|
+
const eligibleUntrackedInventory = v7 && body.eligible_untracked_inventory !== undefined
|
|
1923
|
+
? sha256(body.eligible_untracked_inventory, "status.eligible_untracked_inventory")
|
|
1924
|
+
: undefined;
|
|
1925
|
+
|
|
1855
1926
|
return {
|
|
1856
1927
|
contract: REVIEW_INTEGRATION_CONTRACT,
|
|
1857
1928
|
applicability,
|
|
@@ -1870,6 +1941,7 @@ export function decodeReviewStatusV3(value ) {
|
|
|
1870
1941
|
...(forecast === undefined ? {} : { forecast }),
|
|
1871
1942
|
...(repositoryContext === undefined ? {} : { repositoryContext }),
|
|
1872
1943
|
...(validationRequest === undefined ? {} : { validationRequest }),
|
|
1944
|
+
...(eligibleUntrackedInventory === undefined ? {} : { eligibleUntrackedInventory }),
|
|
1873
1945
|
raw: body,
|
|
1874
1946
|
};
|
|
1875
1947
|
}
|
|
@@ -2020,9 +2092,10 @@ function decodeFailureContext(value , label )
|
|
|
2020
2092
|
export function decodeReviewFailureV2(value ) {
|
|
2021
2093
|
const body = exactRecord(value, "failure", [
|
|
2022
2094
|
"schema", "contract", "operation", "phase", "code", "message", "mutation_outcome", "authority_applicability", "retry_safe", "replayability", "required_inputs", "next_action",
|
|
2023
|
-
], ["lineage_id", "request_digest", "progress_identity", "cause_category", "cause", "context"]);
|
|
2095
|
+
], ["lineage_id", "request_digest", "progress_identity", "cause_category", "cause", "context", "continuation"]);
|
|
2024
2096
|
requireIdentity(body, "gentle-ai.review-integration.failure/v2");
|
|
2025
2097
|
const operation = enumeration(body.operation, FAILURE_OPERATIONS, "failure.operation");
|
|
2098
|
+
const code = text(body.code, "failure.code", { pattern: /^[a-z0-9]+(?:_[a-z0-9]+)*$/ });
|
|
2026
2099
|
|
|
2027
2100
|
if (body.progress_identity !== undefined) {
|
|
2028
2101
|
if (body.lineage_id === undefined || body.request_digest === undefined) throw new TypeError("failure.progress_identity requires lineage_id and request_digest");
|
|
@@ -2031,13 +2104,18 @@ export function decodeReviewFailureV2(value ) {
|
|
|
2031
2104
|
if (body.request_digest !== undefined && operation === REVIEW_INTEGRATION_OPERATION.REPAIR && body.progress_identity === undefined) {
|
|
2032
2105
|
throw new TypeError("failure.request_digest with review.repair requires progress_identity");
|
|
2033
2106
|
}
|
|
2107
|
+
// gentle-pi#627: continuation is optional on every version — an older
|
|
2108
|
+
// gentle-ai may emit managed_assets_outdated with no continuation, and
|
|
2109
|
+
// decode must not refuse the whole envelope for that. When present it is
|
|
2110
|
+
// only valid on this exact code.
|
|
2111
|
+
if (body.continuation !== undefined && code !== "managed_assets_outdated") throw new TypeError("failure.continuation is only valid for code managed_assets_outdated");
|
|
2034
2112
|
|
|
2035
2113
|
return {
|
|
2036
2114
|
schema: "gentle-ai.review-integration.failure/v2",
|
|
2037
2115
|
contract: REVIEW_INTEGRATION_CONTRACT,
|
|
2038
2116
|
operation,
|
|
2039
2117
|
phase: enumeration(body.phase, ["preflight", "pre_native", "native_running", "native_committed", "reconciliation"] , "failure.phase"),
|
|
2040
|
-
code
|
|
2118
|
+
code,
|
|
2041
2119
|
message: text(body.message, "failure.message", { minimum: 1, maximum: 240, pattern: /^[^\r\n]+$/ }),
|
|
2042
2120
|
mutationOutcome: enumeration(body.mutation_outcome, Object.values(REVIEW_MUTATION_OUTCOME), "failure.mutation_outcome"),
|
|
2043
2121
|
authorityApplicability: enumeration(body.authority_applicability, Object.values(REVIEW_AUTHORITY_APPLICABILITY), "failure.authority_applicability"),
|
|
@@ -2051,6 +2129,7 @@ export function decodeReviewFailureV2(value ) {
|
|
|
2051
2129
|
...(body.cause_category === undefined ? {} : { causeCategory: text(body.cause_category, "failure.cause_category", { minimum: 1, pattern: /^[a-z0-9]+(?:_[a-z0-9]+)*$/ }) }),
|
|
2052
2130
|
...(body.cause === undefined ? {} : { cause: text(body.cause, "failure.cause", { minimum: 1, maximum: 4000, pattern: /^[^\r\n]+$/ }) }),
|
|
2053
2131
|
...(body.context === undefined ? {} : { context: decodeFailureContext(body.context, "failure.context") }),
|
|
2132
|
+
...(body.continuation === undefined ? {} : { continuation: decodeReviewManagedAssetsContinuationV1(body.continuation, "failure.continuation") }),
|
|
2054
2133
|
raw: body,
|
|
2055
2134
|
};
|
|
2056
2135
|
}
|