gentle-pi 2.5.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.
Files changed (189) hide show
  1. package/README.md +139 -30
  2. package/assets/agents/gentle-ai-worker.md +4 -0
  3. package/assets/agents/jd-fix-agent.md +18 -0
  4. package/assets/agents/jd-judge-a.md +1 -1
  5. package/assets/agents/jd-judge-b.md +1 -1
  6. package/assets/agents/sdd-apply.md +7 -5
  7. package/assets/agents/sdd-archive.md +5 -3
  8. package/assets/agents/sdd-design.md +4 -0
  9. package/assets/agents/sdd-explore.md +4 -0
  10. package/assets/agents/sdd-init.md +4 -0
  11. package/assets/agents/sdd-onboard.md +4 -0
  12. package/assets/agents/sdd-proposal.md +4 -0
  13. package/assets/agents/sdd-remediate.md +37 -0
  14. package/assets/agents/sdd-research.md +26 -3
  15. package/assets/agents/sdd-spec.md +4 -0
  16. package/assets/agents/sdd-status.md +9 -75
  17. package/assets/agents/sdd-sync.md +4 -0
  18. package/assets/agents/sdd-tasks.md +4 -0
  19. package/assets/agents/sdd-verify.md +5 -3
  20. package/assets/chains/sdd-full.chain.md +4 -0
  21. package/assets/chains/sdd-plan.chain.md +4 -0
  22. package/assets/chains/sdd-verify.chain.md +4 -0
  23. package/assets/migrations/managed-assets-v2.5.0.json +7 -0
  24. package/assets/orchestrator-delegation.md +21 -3
  25. package/assets/sdd-orchestrator-workflow.md +54 -21
  26. package/assets/support/sdd-status-contract.md +34 -90
  27. package/contracts/telemetry/runtime-aggregate-v1.schema.json +65 -0
  28. package/docs/telemetry.md +57 -1
  29. package/docs/windows-startup-console-visibility.md +18 -0
  30. package/extensions/ask-user-choice.ts +143 -15
  31. package/extensions/codegraph-tools.ts +1 -0
  32. package/extensions/gentle-agents.ts +799 -50
  33. package/extensions/gentle-ai.ts +2033 -322
  34. package/extensions/gentle-shell.ts +145 -42
  35. package/extensions/gentle-todo.ts +47 -12
  36. package/extensions/quiet-tools.ts +1 -0
  37. package/extensions/runtime-metrics.ts +130 -0
  38. package/extensions/sdd-init.ts +2 -2
  39. package/extensions/startup-banner.ts +52 -75
  40. package/lib/agent-profiles.ts +550 -0
  41. package/lib/agents-completion-delivery.ts +72 -0
  42. package/lib/agents-config.ts +7 -10
  43. package/lib/agents-history.ts +9 -1
  44. package/lib/agents-messaging.ts +187 -0
  45. package/lib/agents-protocol.ts +77 -5
  46. package/lib/agents-runner.ts +548 -26
  47. package/lib/agents-thread-view.ts +57 -0
  48. package/lib/agents-view-layout.ts +40 -0
  49. package/lib/agents-view.ts +548 -191
  50. package/lib/agents-widget.ts +33 -14
  51. package/lib/gentle-ai-binary.ts +3 -1
  52. package/lib/gentle-ai-renderer.ts +8 -6
  53. package/lib/native-review-cli.ts +283 -1
  54. package/lib/orchestrator-presence.ts +337 -0
  55. package/lib/profiles-orchestrator.ts +203 -0
  56. package/lib/review-candidate-view-owner.ts +296 -46
  57. package/lib/review-candidate-view.ts +30 -20
  58. package/lib/review-consent-component.ts +247 -0
  59. package/lib/review-consent-ui.ts +53 -8
  60. package/lib/review-host-relay.ts +28 -0
  61. package/lib/review-integration-v2.ts +187 -5
  62. package/lib/review-last-event-controller.ts +7 -4
  63. package/lib/review-reminder-receipt.ts +74 -0
  64. package/lib/review-session-standing-permission.ts +27 -6
  65. package/lib/runtime-metrics-children.ts +199 -0
  66. package/lib/runtime-metrics-delivery.ts +68 -0
  67. package/lib/runtime-metrics-native.ts +166 -0
  68. package/lib/runtime-metrics-pi-identity.ts +113 -0
  69. package/lib/runtime-metrics-policy.ts +51 -0
  70. package/lib/runtime-metrics.ts +255 -0
  71. package/lib/sdd-preflight.ts +362 -81
  72. package/lib/sdd-research-capabilities.ts +228 -0
  73. package/lib/sdd-status.ts +29 -7
  74. package/lib/session-worktree-registry.ts +118 -0
  75. package/lib/shell-bar.ts +47 -1
  76. package/lib/shell-card.ts +1 -4
  77. package/lib/shell-changes-view.ts +362 -37
  78. package/lib/shell-changes.ts +81 -1
  79. package/lib/shell-prompt.ts +11 -15
  80. package/lib/shell-sidebar-banner.ts +11 -0
  81. package/lib/shell-sidebar-layout.ts +213 -0
  82. package/lib/shell-sidebar.ts +41 -0
  83. package/lib/shell-todo.ts +28 -11
  84. package/lib/telemetry-trigger.ts +2 -0
  85. package/package.json +6 -3
  86. package/runtime/gentle-ai-binary.mjs +3 -1
  87. package/runtime/native-review-cli.mjs +283 -1
  88. package/runtime/review-integration-v2.mjs +187 -5
  89. package/runtime/telemetry-trigger.mjs +2 -0
  90. package/scripts/build-runtime-modules.mjs +9 -1
  91. package/scripts/check-types.mjs +125 -0
  92. package/scripts/gentle-ai-installer.mjs +10 -10
  93. package/scripts/install-gentle-ai.mjs +12 -0
  94. package/scripts/install-tui-mode-setting.mjs +114 -0
  95. package/scripts/test-packed-runner.mjs +16 -2
  96. package/scripts/types-baseline.json +99 -0
  97. package/scripts/verify-package-files.mjs +4 -2
  98. package/skills/_shared/review-ledger-contract.md +17 -1
  99. package/skills/issue-creation/SKILL.md +3 -3
  100. package/skills/judgment-day/SKILL.md +17 -3
  101. package/skills/judgment-day/references/prompts-and-formats.md +14 -3
  102. package/tests/agent-profiles.test.ts +722 -0
  103. package/tests/agents-completion-delivery.test.ts +94 -0
  104. package/tests/agents-config.test.ts +62 -0
  105. package/tests/agents-fake-child.ts +15 -1
  106. package/tests/agents-grouping.test.ts +179 -0
  107. package/tests/agents-integration.test.ts +100 -0
  108. package/tests/agents-messaging.test.ts +94 -0
  109. package/tests/agents-protocol.test.ts +45 -0
  110. package/tests/agents-queries.test.ts +190 -0
  111. package/tests/agents-responsive.test.ts +43 -0
  112. package/tests/agents-runner.test.ts +571 -14
  113. package/tests/agents-thread-view.test.ts +45 -0
  114. package/tests/agents-view.test.ts +476 -65
  115. package/tests/agents-widget.test.ts +31 -1
  116. package/tests/artifact-language.test.ts +25 -2
  117. package/tests/ask-user-choice.test.ts +169 -3
  118. package/tests/asset-installation-runtime.test.ts +108 -0
  119. package/tests/autonomous-guard.test.ts +116 -1
  120. package/tests/codegraph-tools.test.ts +2 -1
  121. package/tests/delegated-key-learnings-contract.test.ts +1 -1
  122. package/tests/devbinary/native-review-parity.devtest.ts +2 -0
  123. package/tests/feature-request-form.test.ts +67 -0
  124. package/tests/fixtures/agents-messaging-child.mjs +5 -0
  125. package/tests/fixtures/runtime-metrics-native-batches.json +6 -0
  126. package/tests/gentle-agents.test.ts +1460 -33
  127. package/tests/gentle-ai-binary.test.ts +7 -2
  128. package/tests/gentle-ai-installer.test.ts +47 -47
  129. package/tests/gentle-ai-renderer.test.ts +38 -0
  130. package/tests/gentle-ai.test.ts +944 -4
  131. package/tests/gentle-shell.test.ts +303 -12
  132. package/tests/gentle-todo.test.ts +54 -10
  133. package/tests/install-tui-mode-setting.test.ts +324 -0
  134. package/tests/issue-creation-skill.test.ts +22 -0
  135. package/tests/model-routing-authority.test.ts +12 -0
  136. package/tests/native-review-capability-contract.test.ts +12 -1
  137. package/tests/native-review-cli.test.ts +277 -3
  138. package/tests/native-review-parity.test.ts +14 -7
  139. package/tests/native-sdd-attempt-authority.test.ts +7 -2
  140. package/tests/orchestrator-presence.test.ts +389 -0
  141. package/tests/package-manifest.test.ts +232 -7
  142. package/tests/profiles-orchestrator.test.ts +208 -0
  143. package/tests/quiet-tool-rendering.test.ts +1 -0
  144. package/tests/rdd-aware-verification-contract.test.ts +10 -0
  145. package/tests/review-agent-end-preflight.test.ts +332 -24
  146. package/tests/review-candidate-view.test.ts +304 -6
  147. package/tests/review-consent-ui.test.ts +352 -0
  148. package/tests/review-contract-prompt.test.ts +14 -0
  149. package/tests/review-controller-native-routing.test.ts +563 -3
  150. package/tests/review-controller.test.ts +1 -1
  151. package/tests/review-host-relay-restart-parity.test.ts +142 -1
  152. package/tests/review-host-relay-routing.test.ts +364 -4
  153. package/tests/review-host-relay.test.ts +29 -0
  154. package/tests/review-integration-v2-forward.test.ts +44 -0
  155. package/tests/review-integration-v2.test.ts +164 -0
  156. package/tests/review-last-event-closure.test.ts +105 -1
  157. package/tests/review-ledger-contract.test.ts +61 -6
  158. package/tests/review-reminder-receipt.test.ts +62 -0
  159. package/tests/review-session-standing-permission-controller.test.ts +52 -4
  160. package/tests/review-session-standing-permission.test.ts +30 -0
  161. package/tests/runtime-harness.mjs +447 -39
  162. package/tests/runtime-metrics-children.test.ts +206 -0
  163. package/tests/runtime-metrics-delivery.test.ts +85 -0
  164. package/tests/runtime-metrics-extension.test.ts +187 -0
  165. package/tests/runtime-metrics-native.test.ts +209 -0
  166. package/tests/runtime-metrics-pi-identity.test.ts +113 -0
  167. package/tests/runtime-metrics-policy.test.ts +62 -0
  168. package/tests/runtime-metrics.test.ts +184 -0
  169. package/tests/sdd-agent-tools.test.ts +10 -1
  170. package/tests/sdd-execution-routing-contract.test.ts +28 -0
  171. package/tests/sdd-managed-runtime-settlement.test.ts +331 -0
  172. package/tests/sdd-native-managed-uptake.test.ts +253 -0
  173. package/tests/sdd-planning-routing-contract.test.ts +45 -0
  174. package/tests/sdd-preflight.test.ts +252 -8
  175. package/tests/sdd-research-capabilities.test.ts +256 -0
  176. package/tests/sdd-research-live.test.ts +241 -0
  177. package/tests/sdd-selection-transport.test.ts +504 -0
  178. package/tests/sdd-status.test.ts +51 -0
  179. package/tests/session-worktree-registry.test.ts +135 -0
  180. package/tests/shell-card.test.ts +24 -3
  181. package/tests/shell-changes-view.test.ts +471 -8
  182. package/tests/shell-changes.test.ts +168 -0
  183. package/tests/shell-prompt.test.ts +28 -6
  184. package/tests/shell-sidebar-banner.test.ts +23 -0
  185. package/tests/shell-sidebar-layout.test.ts +387 -0
  186. package/tests/shell-sidebar.test.ts +50 -0
  187. package/tests/shell-todo.test.ts +100 -11
  188. package/tests/startup-banner.test.ts +126 -0
  189. package/tests/telemetry-trigger.test.ts +3 -1
@@ -66,7 +66,11 @@ export const NATIVE_REVIEW_OPERATION = {
66
66
  CAPTURE_RESULT: "review/capture-result",
67
67
  CAPTURE_CORRECTION_PLAN: "review/capture-correction-plan",
68
68
  CAPTURE_PROVIDER_ROLE: "review/capture-provider-role",
69
+ CAPTURE_UNACHIEVABLE: "review/capture-unachievable",
69
70
  ACKNOWLEDGE_APPROVED: "review/acknowledge-approved",
71
+ SDD_STATUS: "sdd-status",
72
+ SDD_ATTEMPT: "sdd-attempt",
73
+ SDD_CONTINUE: "sdd-continue",
70
74
  } ;
71
75
 
72
76
 
@@ -106,6 +110,8 @@ export const NATIVE_REVIEW_ERROR_CODE = {
106
110
 
107
111
 
108
112
 
113
+
114
+
109
115
 
110
116
 
111
117
 
@@ -119,6 +125,44 @@ export const NATIVE_REVIEW_ERROR_CODE = {
119
125
 
120
126
 
121
127
 
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+
149
+ export const SDD_ATTEMPT_OUTCOME = { PASSED: "passed", FAILED: "failed", INTERRUPTED: "interrupted" } ;
150
+
151
+
152
+
153
+
154
+
155
+
156
+
157
+
158
+
159
+
160
+
161
+
162
+
163
+
164
+
165
+
122
166
  export const NATIVE_REVIEW_MODE_OPERATION = {
123
167
  STATUS: "status",
124
168
  ENABLE: "enable",
@@ -158,6 +202,35 @@ export const NATIVE_REVIEW_MODE_SCOPE = {
158
202
 
159
203
 
160
204
 
205
+
206
+
207
+
208
+
209
+ /**
210
+ * The native CLI owns this complete v2 record. The decoder validates the fields
211
+ * Pi relies on and returns the original object without adding, omitting, or
212
+ * reconciling local SDD state.
213
+ */
214
+
215
+
216
+
217
+
218
+
219
+
220
+
221
+
222
+
223
+
224
+
225
+
226
+
227
+
228
+
229
+
230
+
231
+
232
+
233
+
161
234
 
162
235
  // Read-only risk assessment request (gentle-pi#662). `baseRef` requires
163
236
  // explicit `committedOnly` acknowledgement, exactly like Native START's
@@ -432,6 +505,49 @@ function isNativeReviewProviderRoleCaptureOperation(operation )
432
505
 
433
506
 
434
507
 
508
+ // gentle-pi#638: the typed declaration that one bound selected lens slot cannot be completed under current conditions. Mirrored from Go's reviewUnachievableLensCaptureArtifact (internal/cli/review_capture_unachievable.go): same schema identity, same closed field set, and the same 512-byte detail bound Go enforces, so a declaration this client refuses locally can never reach a binary that would accept it, and vice versa.
509
+ export const NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA = "gentle-ai.review-capture-unachievable/v1";
510
+ export const NATIVE_REVIEW_UNACHIEVABLE_LENS_DETAIL_LIMIT = 512;
511
+
512
+
513
+
514
+
515
+
516
+
517
+
518
+
519
+
520
+
521
+
522
+
523
+
524
+
525
+
526
+
527
+
528
+
529
+
530
+
531
+
532
+
533
+
534
+
535
+
536
+
537
+
538
+
539
+
540
+
541
+
542
+ // gentle-pi#638 fail-open capability gate: `review capture-unachievable` is younger than every released binary pinned in NATIVE_CLI_CONTRACTS, so the verb is gated invocation-adjacent instead of by a capability row. An older binary renders Go's exact `unknown review command "capture-unachievable"` refusal (internal/cli/review_facade.go) on stderr with no stdout, so the invocation rejects before any decode and the captured diagnostics are the only place that text survives. Every other failure -- a typed binding-mismatch refusal, a timeout, a decode failure -- is a real outcome the caller must surface, never a capability signal. Duck-typed on purpose: the classifier must survive a duplicated module instance exactly like the error it inspects.
543
+ const NATIVE_REVIEW_UNKNOWN_UNACHIEVABLE_VERB_REFUSAL = /unknown review command "capture-unachievable"/;
544
+
545
+ export function isNativeReviewUnachievableVerbRefused(error ) {
546
+ if (typeof error !== "object" || error === null) return false;
547
+ const stderr = (error ).diagnostics?.stderr;
548
+ return typeof stderr === "string" && NATIVE_REVIEW_UNKNOWN_UNACHIEVABLE_VERB_REFUSAL.test(stderr);
549
+ }
550
+
435
551
  /** Refuter and validator captures close only when they are the native last event. */
436
552
 
437
553
 
@@ -844,6 +960,13 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
844
960
  // negotiated START path Pi reads. The closed fields Pi consumes did not
845
961
  // change between 2.6.0 and 2.7.0, so the columns match the 2.6.0 row.
846
962
  "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 }),
963
+ // Ground-truthed against the published v2.8.0 linux/amd64 binary from its
964
+ // signed release archive. The v2 lane still advertises capabilities/v2.5
965
+ // (protocol minor 5) with status/v7, consent/v3, and start/v4 schemas.
966
+ // The closed fields Pi consumes did not change between 2.7.0 and 2.8.0,
967
+ // so this row repeats 2.7.0. riskEvidence and hint remain dark because
968
+ // neither is proven to reach the negotiated START path Pi consumes.
969
+ "2.8.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 }),
847
970
  });
848
971
 
849
972
 
@@ -875,7 +998,7 @@ export class NativeReviewCliError extends Error {
875
998
  this.launchAttempted = launchAttempted;
876
999
  this.mutating = mutating;
877
1000
  this.mutationOutcome = launchAttempted && mutating ? "unknown" : "none";
878
- this.nextAction = this.mutationOutcome === "unknown" ? "review.status" : undefined;
1001
+ this.nextAction = this.mutationOutcome === "unknown" && operation !== NATIVE_REVIEW_OPERATION.SDD_ATTEMPT ? "review.status" : undefined;
879
1002
  this.diagnostics = diagnostics ?? { operation, error_code: code, timed_out: false, output_limit_exceeded: false };
880
1003
  this.auditRecord = auditRecord;
881
1004
  }
@@ -1300,6 +1423,46 @@ function nativeError(code , operation
1300
1423
 
1301
1424
 
1302
1425
 
1426
+ const NATIVE_SDD_DEPENDENCIES = ["proposal", "specs", "design", "tasks", "apply", "verify", "archive"] ;
1427
+ const NATIVE_SDD_INSTRUCTION_PHASES = ["apply", "verify", "remediate", "archive"] ;
1428
+ const NATIVE_SDD_NEXT_RECOMMENDATIONS = ["apply", "verify", "remediate", "archive", "archived", "resolve-blockers", "sdd-new", "select-change", "propose", "spec", "design", "tasks"] ;
1429
+ const NATIVE_SDD_DEPENDENCY_STATES = ["blocked", "ready", "all_done"] ;
1430
+
1431
+ /** Strictly validates the native v2 contract while preserving its whole record. */
1432
+ export function decodeNativeSddStatusV2(value , request ) {
1433
+ const status = object(value);
1434
+ if (status.schemaName !== "gentle-ai.sdd-status" || status.schemaVersion !== 2) throw new Error("wrong native SDD status schema");
1435
+ if ((request.changeName !== undefined && status.changeName !== request.changeName) || (status.changeName !== null && !isCanonicalProcessString(status.changeName))) throw new Error("native SDD status change identity mismatch");
1436
+ const artifactStore = enumString(status.artifactStore, ["openspec", "engram", "hybrid", "none"]);
1437
+ const planningHome = object(status.planningHome);
1438
+ if (planningHome.mode !== "repo-local" || !isCanonicalProcessString(planningHome.path)) throw new Error("invalid native SDD planning home");
1439
+ const expectedOpenSpecHome = join(request.workspaceRoot, "openspec");
1440
+ if (planningHome.path !== expectedOpenSpecHome && !((artifactStore === "engram" || artifactStore === "hybrid") && planningHome.path === "engram:sdd")) throw new Error("native SDD planning home escaped its workspace");
1441
+ if (status.changeRoot !== null && !isCanonicalProcessString(status.changeRoot)) throw new Error("invalid native SDD change root");
1442
+ const actionContext = object(status.actionContext);
1443
+ if (actionContext.mode !== "repo-local" || actionContext.workspaceRoot !== request.workspaceRoot || !isCanonicalProcessString(actionContext.workspaceRoot)) throw new Error("native SDD status workspace root mismatch");
1444
+ const allowedEditRoots = stringArray(actionContext.allowedEditRoots);
1445
+ if (!allowedEditRoots.includes(request.workspaceRoot) || allowedEditRoots.some((root) => !isAbsolute(root) || root !== join(root))) throw new Error("invalid native SDD allowed edit roots");
1446
+ const dependencies = object(status.dependencies);
1447
+ for (const phase of NATIVE_SDD_DEPENDENCIES) {
1448
+ if (enumString(dependencies[phase], NATIVE_SDD_DEPENDENCY_STATES) !== dependencies[phase]) throw new Error("invalid native SDD dependency");
1449
+ }
1450
+ if (Object.keys(dependencies).length !== NATIVE_SDD_DEPENDENCIES.length) throw new Error("native SDD dependencies have an unsupported shape");
1451
+ if (status.instructions !== undefined) throw new Error("native SDD status uses phaseInstructions, not instructions");
1452
+ if (status.phaseInstructions !== undefined) {
1453
+ const instructions = object(status.phaseInstructions);
1454
+ for (const phase of NATIVE_SDD_INSTRUCTION_PHASES) stringArray(instructions[phase]);
1455
+ if (Object.keys(instructions).length !== NATIVE_SDD_INSTRUCTION_PHASES.length) throw new Error("native SDD instructions have an unsupported shape");
1456
+ }
1457
+ if (status.nextRecommended === "remediate" || status.remediationState !== undefined) {
1458
+ const remediation = object(status.remediationState);
1459
+ if (typeof remediation.required !== "boolean" || typeof remediation.complete !== "boolean" || typeof remediation.failedEvidenceRevision !== "string" || (remediation.failedEvidenceRevision !== "" && !/^sha256:[0-9a-f]{64}$/.test(remediation.failedEvidenceRevision)) || (status.nextRecommended === "remediate" && (!remediation.required || remediation.complete || !remediation.failedEvidenceRevision))) throw new Error("Invalid native remediation state");
1460
+ }
1461
+ stringArray(status.blockedReasons);
1462
+ enumString(status.nextRecommended, NATIVE_SDD_NEXT_RECOMMENDATIONS);
1463
+ return status ;
1464
+ }
1465
+
1303
1466
  class NativeReviewPlainCli {
1304
1467
  adapter ;
1305
1468
  executable ;
@@ -1830,6 +1993,23 @@ function decodeNativeProviderRoleCaptureArtifact(value )
1830
1993
  });
1831
1994
  }
1832
1995
 
1996
+ // gentle-pi#638: the recorded declaration Go prints for one unachievable slot. Closed key set, exact schema identity, and the lens Go itself resolved from the request hash -- the host never asserts which lens failed, only reads back the one the provider bound.
1997
+ function decodeNativeUnachievableLensCaptureArtifact(value ) {
1998
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("native unachievable lens capture artifact must be an object");
1999
+ const body = value ;
2000
+ const allowed = new Set(["schema", "lineage_id", "target_identity", "lens", "selected_order", "reason", "recorded"]);
2001
+ for (const key of Object.keys(body)) if (!allowed.has(key)) throw new TypeError(`native unachievable lens capture artifact carries unexpected key ${key}`);
2002
+ const text = (key ) => {
2003
+ const found = body[key];
2004
+ if (typeof found !== "string" || found.trim() !== found || found.length === 0) throw new TypeError(`native unachievable lens capture artifact ${key} must be a non-empty trimmed string`);
2005
+ return found;
2006
+ };
2007
+ if (text("schema") !== NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA) throw new TypeError(`native unachievable lens capture artifact schema must be ${NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA}`);
2008
+ if (typeof body.selected_order !== "number" || !Number.isSafeInteger(body.selected_order) || body.selected_order < 0) throw new TypeError("native unachievable lens capture artifact selected_order must be a non-negative integer");
2009
+ if (body.recorded !== true) throw new TypeError("native unachievable lens capture artifact must report recorded: true");
2010
+ return Object.freeze({ schema: NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA, lineageId: text("lineage_id"), targetIdentity: text("target_identity"), lens: text("lens"), selectedOrder: body.selected_order, reason: text("reason"), recorded: true });
2011
+ }
2012
+
1833
2013
  export class NativeReviewCliV216 {
1834
2014
  plain ;
1835
2015
  adapter ;
@@ -1921,6 +2101,89 @@ export class NativeReviewCliV216 {
1921
2101
  return this.invoke(operation, cwd, arguments_, mutating, signal, this.executablePath(operation, mutating), toleratedStderr);
1922
2102
  }
1923
2103
 
2104
+ async sddAttemptAcquire(request ) {
2105
+ return this.sddAttempt("acquire", request);
2106
+ }
2107
+
2108
+ async sddAttemptSettle(request ) {
2109
+ return this.sddAttempt("settle", request);
2110
+ }
2111
+
2112
+ async sddAttempt(verb , request ) {
2113
+ const args = ["sdd-attempt", verb, "--cwd", request.workspaceRoot, "--change", request.changeName, "--request-id", request.requestId];
2114
+ if (!isAbsolute(request.workspaceRoot) || !isCanonicalProcessString(request.workspaceRoot) || !isCanonicalProcessString(request.changeName) || !/^[a-z0-9][a-z0-9._-]{0,127}$/.test(request.requestId)) throw new TypeError("Invalid SDD attempt identity");
2115
+ const text = (flag , value , max ) => {
2116
+ if (!isCanonicalProcessString(value) || /[\r\n]/.test(value) || Buffer.byteLength(value) > max) throw new TypeError(`Invalid ${flag}`);
2117
+ args.push(flag, value);
2118
+ };
2119
+ const revision = (flag , value ) => {
2120
+ if (value === undefined) return;
2121
+ if (!/^sha256:[0-9a-f]{64}$/.test(value)) throw new TypeError(`Invalid ${flag}`);
2122
+ args.push(flag, value);
2123
+ };
2124
+ if (verb === "acquire") {
2125
+ const acquire = request ;
2126
+ text("--work-unit", acquire.workUnit, 160);
2127
+ text("--evidence-goal", acquire.evidenceGoal, 240);
2128
+ for (const [flag, value, max] of [["--max-attempts", acquire.maxAttempts, 100], ["--max-changed-lines", acquire.maxChangedLines, 1_000_000]] ) {
2129
+ if (value === undefined) continue;
2130
+ if (!Number.isInteger(value) || value < 1 || value > max) throw new TypeError(`Invalid ${flag}`);
2131
+ args.push(flag, String(value));
2132
+ }
2133
+ if (acquire.expectedRevision === "") args.push("--expected-revision", "");
2134
+ else revision("--expected-revision", acquire.expectedRevision);
2135
+ if (acquire.token !== undefined) text("--token", acquire.token, 500);
2136
+ } else {
2137
+ const settle = request ;
2138
+ text("--token", settle.token, 500);
2139
+ if (!Object.values(SDD_ATTEMPT_OUTCOME).includes(settle.outcome)) throw new TypeError("Invalid outcome");
2140
+ args.push("--outcome", settle.outcome);
2141
+ if (settle.outcome === "interrupted" ? settle.evidenceRevision !== undefined || settle.remediationEvidence !== undefined : !settle.evidenceRevision && !(settle.outcome === "passed" && settle.remediationEvidence)) throw new TypeError("Invalid terminal evidence");
2142
+ revision("--evidence-revision", settle.evidenceRevision);
2143
+ text("--diagnosis", settle.diagnosis, 500);
2144
+ if (!["reused", "invalidated"].includes(settle.harnessDisposition)) throw new TypeError("Invalid harness disposition");
2145
+ args.push("--harness-disposition", settle.harnessDisposition);
2146
+ text("--cleanup-evidence", settle.cleanupEvidence, 500);
2147
+ text("--process-evidence", settle.processEvidence, 500);
2148
+ if (settle.remediationEvidence !== undefined) args.push("--remediation-evidence", settle.remediationEvidence);
2149
+ }
2150
+ revision("--remediates-evidence-revision", request.remediatesEvidenceRevision);
2151
+ args.push(...nativeUntrackedSelectionArguments(nativeUntrackedSelection(request)));
2152
+ const operation = NATIVE_REVIEW_OPERATION.SDD_ATTEMPT;
2153
+ const { body } = await this.negotiated(operation, request.workspaceRoot, args, true);
2154
+ return decode(operation, true, () => {
2155
+ const result = body ;
2156
+ if (!result || !["proceed", "blocked", "complete"].includes(result.state)) throw new TypeError("Invalid compact state");
2157
+ if (verb === "acquire" && result.state === "proceed" && !isCanonicalProcessString(result.token)) throw new TypeError("Missing compact token");
2158
+ if (result.reason !== undefined && typeof result.reason !== "string") throw new TypeError("Invalid compact reason");
2159
+ return { state: result.state, ...(result.token === undefined ? {} : { token: result.token }), ...(result.reason === undefined ? {} : { reason: result.reason }) };
2160
+ });
2161
+ }
2162
+
2163
+ async sddStatus(request ) {
2164
+ return this.sddProjection(request, false);
2165
+ }
2166
+
2167
+ async sddContinue(request ) {
2168
+ if (!isCanonicalProcessString(request.changeName)) throw new TypeError("Native SDD continuation requires an exact selected change");
2169
+ return this.sddProjection(request, true);
2170
+ }
2171
+
2172
+ async sddProjection(request , mutating ) {
2173
+ if ((request.changeName !== undefined && !isCanonicalProcessString(request.changeName)) || !isCanonicalProcessString(request.workspaceRoot) || !isAbsolute(request.workspaceRoot)) {
2174
+ throw new TypeError("Native SDD status requires a canonical change and absolute workspace root");
2175
+ }
2176
+ const operation = mutating ? NATIVE_REVIEW_OPERATION.SDD_CONTINUE : NATIVE_REVIEW_OPERATION.SDD_STATUS;
2177
+ const execution = await this.negotiated(
2178
+ operation,
2179
+ request.workspaceRoot,
2180
+ [operation, ...(request.changeName === undefined ? [] : [request.changeName]), "--cwd", request.workspaceRoot, "--json", "--instructions"],
2181
+ mutating,
2182
+ request.signal,
2183
+ );
2184
+ return decode(operation, mutating, () => decodeNativeSddStatusV2(execution.body, request));
2185
+ }
2186
+
1924
2187
  async start(request ) {
1925
2188
  if (request.baseRef !== undefined && !isCanonicalProcessString(request.baseRef)) throw new TypeError("Native START baseRef must be a non-empty, trimmed, NUL-free string");
1926
2189
  if (request.baseRef !== undefined && request.committedOnly !== true) throw new TypeError("Native START baseRef requires explicit committedOnly acknowledgement");
@@ -2195,6 +2458,25 @@ export class NativeReviewCliV216 {
2195
2458
  });
2196
2459
  }
2197
2460
 
2461
+ // gentle-pi#638: `gentle-ai review capture-unachievable` records one bound declaration that the exact slot the collect transition offered cannot be completed under current conditions. Unlike every other capture verb the host runs, this one reports a failure instead of a result, so it never takes the provider-issued tokens verbatim: the declaration's binding values are named fields re-derived from the slot, and Go verifies them against the frozen lineage, revision, target, and subject hash before recording anything (internal/cli/review_capture_unachievable.go). A binding that no longer matches is a typed refusal the caller surfaces, never a host-side reconstruction.
2462
+ async captureUnachievableLens(request ) {
2463
+ for (const [field, value] of (["lineageId", "reason"] ).map((field) => [field, request[field]] )) {
2464
+ if (!isCanonicalProcessString(value)) throw new TypeError(`Native CAPTURE_UNACHIEVABLE ${field} must be a non-empty, trimmed, NUL-free string`);
2465
+ }
2466
+ // The three identity fields are digests on every slot the provider offers, so a non-sha value means the declaration binding was parsed wrong; refuse it locally instead of invoking a doomed process.
2467
+ for (const [field, value] of (["targetIdentity", "expectedRevision", "requestHash"] ).map((field) => [field, request[field]] )) {
2468
+ if (!/^sha256:[0-9a-f]{64}$/.test(value)) throw new TypeError(`Native CAPTURE_UNACHIEVABLE ${field} must be a canonical SHA-256 identity`);
2469
+ }
2470
+ const detail = request.detail === undefined ? "" : request.detail.trim();
2471
+ // gentle-pi#822: the native limit is 512 UTF-8 bytes, not 512 UTF-16 code units — the Go side measures the encoded payload, so a multibyte detail needs Buffer.byteLength; a .length check would let 300 two-byte characters through.
2472
+ if (Buffer.byteLength(detail, "utf8") > NATIVE_REVIEW_UNACHIEVABLE_LENS_DETAIL_LIMIT) throw new TypeError(`Native CAPTURE_UNACHIEVABLE detail exceeds ${NATIVE_REVIEW_UNACHIEVABLE_LENS_DETAIL_LIMIT} bytes`);
2473
+ if (request.repositoryContext !== undefined && !isCanonicalProcessString(request.repositoryContext)) throw new TypeError("Native CAPTURE_UNACHIEVABLE repositoryContext must be a non-empty, trimmed, NUL-free string");
2474
+ const executable = this.executablePath(NATIVE_REVIEW_OPERATION.CAPTURE_UNACHIEVABLE, true);
2475
+ // gentle-pi#822: --repository-context is authoritative and mutually exclusive with a path (the captureProviderRole discipline), so --cwd rides the invocation only when no repository context names it; the process working directory stays request.cwd either way.
2476
+ const execution = await this.invoke(NATIVE_REVIEW_OPERATION.CAPTURE_UNACHIEVABLE, request.cwd, ["review", "capture-unachievable", "--lineage", request.lineageId, "--target", request.targetIdentity, "--expected-revision", request.expectedRevision, "--request-hash", request.requestHash, "--reason", request.reason, ...(detail === "" ? [] : ["--detail", detail]), ...(request.repositoryContext === undefined ? ["--cwd", request.cwd] : ["--repository-context", request.repositoryContext])], true, request.signal, executable);
2477
+ return decode(NATIVE_REVIEW_OPERATION.CAPTURE_UNACHIEVABLE, true, () => decodeNativeUnachievableLensCaptureArtifact(execution.body));
2478
+ }
2479
+
2198
2480
  // gentle-pi#311 P5: executes one provider-rendered `review.finalize`
2199
2481
  // execute transition exactly as rendered. The tokens come verbatim from
2200
2482
  // the negotiated next_transition (e.g. `--lineage=<id>
@@ -561,6 +561,46 @@ export const REVIEW_PROVIDER_ROLE_CAPTURE_OPERATIONS = Object.freeze(Object.valu
561
561
 
562
562
 
563
563
 
564
+ // gentle-pi#638: one selected lens slot the host declared unachievable
565
+ // through the native capture-unachievable verb, mirrored from Go's
566
+ // ReviewUnachievableLensSlot (internal/cli/review_next_transition.go). The
567
+ // withdraw form is deliberately narrower than a full execute transition:
568
+ // it is the literally runnable retraction command for exactly this slot, so
569
+ // a restart that never saw the collect offer can still take the declaration
570
+ // back from the stop alone.
571
+
572
+
573
+
574
+
575
+
576
+
577
+
578
+
579
+
580
+
581
+
582
+
583
+
584
+
585
+
586
+
587
+
588
+
589
+
590
+
591
+
592
+
593
+
594
+
595
+
596
+
597
+
598
+
599
+
600
+ const ESCALATION_CAUSES = ["unknown_causality", "insufficient_evidence", "missing_refuter_outcome", "targeted_validator_rejected", "correction_budget_exceeded", "unresolved_severe_findings"] ;
601
+ const ESCALATION_REFUTER_OUTCOMES = ["corroborated", "refuted", "inconclusive"] ;
602
+
603
+
564
604
 
565
605
 
566
606
 
@@ -1713,7 +1753,7 @@ export function decodeReviewManagedAssetsContinuationV1(value , label
1713
1753
  export function decodeReviewNextTransitionV3(value , options = {}) {
1714
1754
  const v6 = options.v6 === true;
1715
1755
  const v5 = options.v5 === true || v6;
1716
- const transition = exactRecord(value, "next_transition", ["kind", "reason_code"], ["execute", "collect", ...(v5 ? ["correction_request"] : []), "continuation"]);
1756
+ const transition = exactRecord(value, "next_transition", ["kind", "reason_code"], ["execute", "collect", ...(v5 ? ["correction_request"] : []), "continuation", "unachievable_lens_slots"]);
1717
1757
  const kind = enumeration(transition.kind, ["execute", "collect", "stop"] , "next_transition.kind");
1718
1758
  const reasonCode = text(transition.reason_code, "next_transition.reason_code", { minimum: 1, pattern: /^[a-z0-9_]+$/ });
1719
1759
  const continuation = transition.continuation === undefined ? undefined : decodeReviewManagedAssetsContinuationV1(transition.continuation, "next_transition.continuation");
@@ -1723,6 +1763,13 @@ export function decodeReviewNextTransitionV3(value , options
1723
1763
  // it is only valid on a stop with this exact reason_code.
1724
1764
  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");
1725
1765
 
1766
+ // gentle-pi#638: the unachievable stop names one entry per declared slot, each carrying its complete withdraw command (schemas/status-v7.schema.json, the stop variant). Like the continuation, the field is only valid on its own exact stop reason code, and an entry is refused rather than silently dropped when the provider drifts.
1767
+ let unachievableLensSlots ;
1768
+ if (transition.unachievable_lens_slots !== undefined) {
1769
+ if (!(kind === "stop" && reasonCode === "unachievable_lens_slot")) throw new TypeError("next_transition.unachievable_lens_slots is only valid for a stop transition with reason_code unachievable_lens_slot");
1770
+ unachievableLensSlots = array(transition.unachievable_lens_slots, "next_transition.unachievable_lens_slots", decodeUnachievableLensSlot, { minimum: 1 });
1771
+ }
1772
+
1726
1773
  // status/v5: the bounded correction plan request rides exactly its two
1727
1774
  // reason codes and never any other (vendored status-v5.schema.json).
1728
1775
  let correctionRequest ;
@@ -1771,7 +1818,64 @@ export function decodeReviewNextTransitionV3(value , options
1771
1818
  return { kind, reasonCode, collect: { inputs }, ...(correctionRequest === undefined ? {} : { correctionRequest }) };
1772
1819
  }
1773
1820
  if (transition.execute !== undefined || transition.collect !== undefined) throw new TypeError("next_transition stop cannot carry a transition");
1774
- return { kind, reasonCode, ...(correctionRequest === undefined ? {} : { correctionRequest }), ...(continuation === undefined ? {} : { continuation }) };
1821
+ return { kind, reasonCode, ...(correctionRequest === undefined ? {} : { correctionRequest }), ...(continuation === undefined ? {} : { continuation }), ...(unachievableLensSlots === undefined ? {} : { unachievableLensSlots }) };
1822
+ }
1823
+
1824
+ // Mirrors NATIVE_REVIEW_UNACHIEVABLE_LENS_DETAIL_LIMIT (lib/native-review-cli.ts) and Go's 512-byte CAPTURE_UNACHIEVABLE detail bound.
1825
+ export const REVIEW_INTEGRATION_UNACHIEVABLE_LENS_DETAIL_LIMIT = 512;
1826
+
1827
+ function decodeUnachievableLensSlot(value , label ) {
1828
+ const body = exactRecord(value, label, ["lens", "selected_order", "subject_hash", "reason", "withdraw"], ["detail"]);
1829
+ const lens = nonempty(body.lens, `${label}.lens`);
1830
+ const selectedOrder = integer(body.selected_order, `${label}.selected_order`);
1831
+ const subjectHash = sha256(body.subject_hash, `${label}.subject_hash`);
1832
+ const reason = nonempty(body.reason, `${label}.reason`);
1833
+ const detail = body.detail === undefined ? undefined : nonempty(body.detail, `${label}.detail`);
1834
+ // gentle-pi#822: Go refuses a CAPTURE_UNACHIEVABLE detail above 512 UTF-8 bytes, so the stop decoder mirrors the same bound — measured in bytes, not UTF-16 code units — and a STATUS stop can never carry a detail this client would have refused to declare.
1835
+ if (detail !== undefined && Buffer.byteLength(detail, "utf8") > REVIEW_INTEGRATION_UNACHIEVABLE_LENS_DETAIL_LIMIT) throw new TypeError(`${label}.detail exceeds ${REVIEW_INTEGRATION_UNACHIEVABLE_LENS_DETAIL_LIMIT} bytes`);
1836
+ const withdraw = exactRecord(body.withdraw, `${label}.withdraw`, ["operation", "command", "arguments", "binding"]);
1837
+ const operation = enumeration(withdraw.operation, ["review.capture-unachievable"] , `${label}.withdraw.operation`);
1838
+ const command = nonempty(withdraw.command, `${label}.withdraw.command`);
1839
+ const arguments_ = decodeTransitionArguments(withdraw.arguments, `${label}.withdraw.arguments`);
1840
+ // Withdrawal is a single affirmative native action, never an optional or
1841
+ // negatable provider argument. The general token and command checks below
1842
+ // then prove its exact --withdraw=true rendering is the complete vector.
1843
+ const withdrawArguments = arguments_.filter((argument) => argument.name === "withdraw");
1844
+ if (withdrawArguments.length !== 1) throw new TypeError(`${label}.withdraw.arguments withdraw must appear exactly once`);
1845
+ if (withdrawArguments[0] .value !== "true") throw new TypeError(`${label}.withdraw.arguments withdraw must be true`);
1846
+ // The binding keeps the execute branch's open-record discipline: the Go shape is target_identity plus optional lineage_id, revision, and repository_context, and closing it here would make Pi stricter than the contract it implements.
1847
+ const binding = exactRecord(withdraw.binding, `${label}.withdraw.binding`, ["target_identity"], ["lineage_id", "revision", "repository_context"], true);
1848
+ const targetIdentity = sha256(binding.target_identity, `${label}.withdraw.binding.target_identity`);
1849
+ const lineageId = binding.lineage_id === undefined ? undefined : lineage(binding.lineage_id, `${label}.withdraw.binding.lineage_id`);
1850
+ const revision = binding.revision === undefined ? undefined : sha256(binding.revision, `${label}.withdraw.binding.revision`);
1851
+ // gentle-pi#822: the withdraw form names the slot identity twice — as named arguments and as the binding object — and the two renderings must agree before the slot decodes. Strict equality also enforces both-or-neither on the optional lineage and revision fields, so a partially rendered binding never slips through and restart cannot withdraw a different slot. Each identity argument must appear EXACTLY once: a first-match lookup let a duplicate {name} entry smuggle a second value past the identity checks.
1852
+ const withdrawIdentityArgument = (name ) => {
1853
+ const matches = arguments_.filter((argument) => argument.name === name);
1854
+ if (matches.length > 1) throw new TypeError(`${label}.withdraw.arguments ${name} must appear exactly once`);
1855
+ return matches[0];
1856
+ };
1857
+ const withdrawRequestHash = withdrawIdentityArgument("request-hash");
1858
+ if (withdrawRequestHash?.value !== subjectHash) throw new TypeError(`${label}.withdraw.arguments request-hash does not match the slot subject_hash`);
1859
+ const withdrawTarget = withdrawIdentityArgument("target");
1860
+ if (withdrawTarget?.value !== targetIdentity) throw new TypeError(`${label}.withdraw.arguments target does not match the withdraw binding target_identity`);
1861
+ const withdrawLineage = withdrawIdentityArgument("lineage");
1862
+ if (withdrawLineage?.value !== lineageId) throw new TypeError(`${label}.withdraw.arguments lineage does not match the withdraw binding lineage_id`);
1863
+ const withdrawExpectedRevision = withdrawIdentityArgument("expected-revision");
1864
+ if (withdrawExpectedRevision?.value !== revision) throw new TypeError(`${label}.withdraw.arguments expected-revision does not match the withdraw binding revision`);
1865
+ // gentle-pi#822: command is executable authority, not a display hint. Every
1866
+ // provider-issued argument token must be the canonical --name=value rendering,
1867
+ // and command must render the canonical operation plus those tokens exactly in
1868
+ // order. This rejects omitted tokens, a second command, suffixes, and shell
1869
+ // payloads rather than merely finding identity-token substrings.
1870
+ const tokens = arguments_.map((argument, index) => {
1871
+ if (argument.token === undefined) throw new TypeError(`${label}.withdraw.arguments[${index}].token is required`);
1872
+ const expectedToken = `--${argument.name}=${argument.value}`;
1873
+ if (argument.token !== expectedToken) throw new TypeError(`${label}.withdraw.arguments[${index}].token must exactly render its name and value`);
1874
+ return argument.token;
1875
+ });
1876
+ const expectedCommand = `gentle-ai review capture-unachievable ${tokens.join(" ")}`;
1877
+ if (command !== expectedCommand) throw new TypeError(`${label}.withdraw.command must exactly render the canonical operation and withdraw arguments`);
1878
+ return { lens, selectedOrder, subjectHash, reason, ...(detail === undefined ? {} : { detail }), withdraw: { operation, command, arguments: arguments_, binding: { targetIdentity, ...(lineageId === undefined ? {} : { lineageId }), ...(revision === undefined ? {} : { revision }) } } };
1775
1879
  }
1776
1880
 
1777
1881
  // ---------------------------------------------------------------------------
@@ -1821,7 +1925,7 @@ export function decodeReviewStatusV3(value ) {
1821
1925
  // key set plus the optional forecast and the v5-only next_transition
1822
1926
  // surfaces. status/v6 adds the intended-untracked selection; status/v7
1823
1927
  // (gentle-ai v2.6.0, advertised through capabilities/v2.5 alongside v6)
1824
- // adds only the top-level optional `eligible_untracked_inventory` digest,
1928
+ // adds optional `eligible_untracked_inventory` and escalation metadata,
1825
1929
  // so it is decoded on the v6 surface. v3 keeps rejecting every v5/v6/v7-only
1826
1930
  // field.
1827
1931
  const schema = typeof value === "object" && value !== null ? (value ).schema : undefined;
@@ -1830,7 +1934,7 @@ export function decodeReviewStatusV3(value ) {
1830
1934
  const v5 = v6 || schema === "gentle-ai.review-integration.status/v5";
1831
1935
  const body = exactRecord(value, "status", [
1832
1936
  "schema", "contract", "operation", "applicability", "action", "replayability", "target_identity", "projection", "repair", "candidates",
1833
- ], ["authority", "frozen", "action_disposition", "eligibility", "next_transition", "authority_target_identity", ...(v5 ? ["receipt", "forecast", "repository_context", "validation_request"] : []), ...(v7 ? ["eligible_untracked_inventory"] : [])]);
1937
+ ], ["authority", "frozen", "action_disposition", "eligibility", "next_transition", "authority_target_identity", ...(v5 ? ["receipt", "forecast", "repository_context", "validation_request"] : []), ...(v7 ? ["eligible_untracked_inventory", "escalation"] : [])]);
1834
1938
  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);
1835
1939
 
1836
1940
  const applicability = enumeration(body.applicability, ["current_target", "unrelated", "ambiguous", "corrupted"] , "status.applicability");
@@ -1914,6 +2018,24 @@ export function decodeReviewStatusV3(value ) {
1914
2018
  };
1915
2019
  }
1916
2020
 
2021
+ let escalation ;
2022
+ if (Object.hasOwn(body, "escalation")) {
2023
+ const label = "status.escalation";
2024
+ const source = exactRecord(body.escalation, label, ["cause", "finding_ids"], ["refuter_outcomes"]);
2025
+ escalation = {
2026
+ cause: enumeration(source.cause, ESCALATION_CAUSES, `${label}.cause`),
2027
+ findingIds: stringArray(source.finding_ids, `${label}.finding_ids`),
2028
+ };
2029
+ if (Object.hasOwn(source, "refuter_outcomes")) escalation.refuterOutcomes = array(source.refuter_outcomes, `${label}.refuter_outcomes`, (entry, itemLabel) => {
2030
+ const row = exactRecord(entry, itemLabel, ["finding_id", "outcome", "proof"]);
2031
+ return {
2032
+ findingId: nonempty(row.finding_id, `${itemLabel}.finding_id`),
2033
+ outcome: enumeration(row.outcome, ESCALATION_REFUTER_OUTCOMES, `${itemLabel}.outcome`),
2034
+ proof: nonempty(row.proof, `${itemLabel}.proof`),
2035
+ };
2036
+ });
2037
+ }
2038
+
1917
2039
  // status/v7 top-level optional digest (gentle-ai v2.6.0): resolves #4066's
1918
2040
  // closed loop where `sdd-attempt finish` named a digest status never
1919
2041
  // published. Absent on the `staged` projection, which never resolves an
@@ -1941,6 +2063,7 @@ export function decodeReviewStatusV3(value ) {
1941
2063
  ...(forecast === undefined ? {} : { forecast }),
1942
2064
  ...(repositoryContext === undefined ? {} : { repositoryContext }),
1943
2065
  ...(validationRequest === undefined ? {} : { validationRequest }),
2066
+ ...(escalation === undefined ? {} : { escalation }),
1944
2067
  ...(eligibleUntrackedInventory === undefined ? {} : { eligibleUntrackedInventory }),
1945
2068
  raw: body,
1946
2069
  };
@@ -2451,6 +2574,29 @@ export function decodeReviewAcknowledgedV1(value , expected
2451
2574
  return decoded;
2452
2575
  }
2453
2576
 
2577
+ const REVIEW_LAST_EVENT_FINDING_SEVERITIES = ["BLOCKER", "CRITICAL", "WARNING", "SUGGESTION"] ;
2578
+ const REVIEW_LAST_EVENT_EVIDENCE_CLASSES = ["deterministic", "inferential", "insufficient"] ;
2579
+ const REVIEW_LAST_EVENT_CAUSAL_DISPOSITIONS = ["introduced", "behavior-activated", "worsened", "pre-existing", "base-only", "unknown"] ;
2580
+
2581
+
2582
+
2583
+
2584
+
2585
+
2586
+
2587
+
2588
+
2589
+
2590
+
2591
+
2592
+
2593
+
2594
+
2595
+
2596
+
2597
+
2598
+
2599
+
2454
2600
 
2455
2601
 
2456
2602
 
@@ -2573,8 +2719,38 @@ function decodeReviewStatusContinuationV1(value )
2573
2719
  };
2574
2720
  }
2575
2721
 
2722
+ function decodeReviewLastEventReviewerFindingV1(value , label ) {
2723
+ const finding = exactRecord(value, label, ["id", "lens", "location", "severity", "claim", "proof_refs"], ["evidence_class", "causal_disposition"]);
2724
+ const evidenceClass = finding.evidence_class === undefined
2725
+ ? undefined
2726
+ : enumeration(finding.evidence_class, REVIEW_LAST_EVENT_EVIDENCE_CLASSES, `${label}.evidence_class`);
2727
+ const causalDisposition = finding.causal_disposition === undefined
2728
+ ? undefined
2729
+ : enumeration(finding.causal_disposition, REVIEW_LAST_EVENT_CAUSAL_DISPOSITIONS, `${label}.causal_disposition`);
2730
+ return {
2731
+ id: nonempty(finding.id, `${label}.id`),
2732
+ lens: nonempty(finding.lens, `${label}.lens`),
2733
+ location: nonempty(finding.location, `${label}.location`),
2734
+ severity: enumeration(finding.severity, REVIEW_LAST_EVENT_FINDING_SEVERITIES, `${label}.severity`),
2735
+ claim: nonempty(finding.claim, `${label}.claim`),
2736
+ proofRefs: stringArray(finding.proof_refs, `${label}.proof_refs`, { minimum: 1 }),
2737
+ ...(evidenceClass === undefined ? {} : { evidenceClass }),
2738
+ ...(causalDisposition === undefined ? {} : { causalDisposition }),
2739
+ };
2740
+ }
2741
+
2742
+ function decodeReviewLastEventReviewerResultV1(value , label ) {
2743
+ const result = exactRecord(value, label, ["lens", "findings", "evidence", "result_hash"]);
2744
+ return {
2745
+ lens: nonempty(result.lens, `${label}.lens`),
2746
+ findings: array(result.findings, `${label}.findings`, decodeReviewLastEventReviewerFindingV1),
2747
+ evidence: stringArray(result.evidence, `${label}.evidence`),
2748
+ resultHash: sha256(result.result_hash, `${label}.result_hash`),
2749
+ };
2750
+ }
2751
+
2576
2752
  export function decodeReviewLastEventClosureV1(value ) {
2577
- const body = exactRecord(value, "last_event_closure", ["schema", "operation", "lineage_id", "state", "store_revision"], ["target_identity", "request_hash", "correction_lines", "action", "advisory_findings", "status_continuation", "acknowledgement"]);
2753
+ const body = exactRecord(value, "last_event_closure", ["schema", "operation", "lineage_id", "state", "store_revision"], ["target_identity", "request_hash", "correction_lines", "action", "advisory_findings", "reviewer_results", "status_continuation", "acknowledgement"]);
2578
2754
  if (body.schema !== REVIEW_LAST_EVENT_CLOSURE_SCHEMA) throw new TypeError(`last_event_closure.schema must be ${REVIEW_LAST_EVENT_CLOSURE_SCHEMA}`);
2579
2755
  const operation = enumeration(body.operation, Object.values(REVIEW_LAST_EVENT_CLOSURE_OPERATION), "last_event_closure.operation") ;
2580
2756
  const state = enumeration(body.state, REVIEW_LAST_EVENT_TERMINAL_STATES, "last_event_closure.state") ;
@@ -2586,6 +2762,7 @@ export function decodeReviewLastEventClosureV1(value )
2586
2762
  storeRevision: sha256(body.store_revision, "last_event_closure.store_revision"),
2587
2763
  };
2588
2764
  if (operation === REVIEW_LAST_EVENT_CLOSURE_OPERATION.CAPTURE_CORRECTION_PLAN) {
2765
+ if (body.reviewer_results !== undefined) throw new TypeError("last_event_closure reviewer_results requires approved state");
2589
2766
  if (body.action !== undefined || body.advisory_findings !== undefined || body.status_continuation !== undefined) throw new TypeError("last_event_closure correction-plan cannot carry action, advisory_findings, or status_continuation");
2590
2767
  if (state !== "correction_required") throw new TypeError("last_event_closure correction-plan requires correction_required state");
2591
2768
  return {
@@ -2653,10 +2830,15 @@ export function decodeReviewLastEventClosureV1(value )
2653
2830
  ? undefined
2654
2831
  : decodeReviewAdvisoryFindingsV1(body.advisory_findings, "last_event_closure.advisory_findings");
2655
2832
  if (advisoryFindings !== undefined && state !== "approved") throw new TypeError("last_event_closure advisory_findings requires approved state");
2833
+ const reviewerResults = body.reviewer_results === undefined
2834
+ ? undefined
2835
+ : array(body.reviewer_results, "last_event_closure.reviewer_results", decodeReviewLastEventReviewerResultV1);
2836
+ if (reviewerResults !== undefined && state !== "approved") throw new TypeError("last_event_closure reviewer_results requires approved state");
2656
2837
  return {
2657
2838
  ...shared,
2658
2839
  action,
2659
2840
  ...(advisoryFindings === undefined ? {} : { advisoryFindings }),
2841
+ ...(reviewerResults === undefined ? {} : { reviewerResults }),
2660
2842
  ...(statusContinuation === undefined ? {} : { statusContinuation }),
2661
2843
  ...(acknowledgement === undefined ? {} : { acknowledgement }),
2662
2844
  ...(acknowledgementUndecodable ? { acknowledgementUndecodable: true } : {}),
@@ -111,6 +111,7 @@ export function shouldTriggerTelemetry(env
111
111
 
112
112
 
113
113
 
114
+
114
115
 
115
116
 
116
117
  /**
@@ -130,6 +131,7 @@ export function spawnTelemetryTrigger(options )
130
131
  cwd: options.cwd,
131
132
  env: options.env,
132
133
  detached: true,
134
+ windowsHide: true,
133
135
  stdio: "ignore",
134
136
  });
135
137
  child.on("error", () => {