gentle-pi 2.1.2 → 2.2.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 (171) hide show
  1. package/README.md +70 -12
  2. package/assets/agents/gentle-ai-worker.md +7 -3
  3. package/assets/agents/jd-fix-agent.md +1 -1
  4. package/assets/agents/jd-judge-a.md +3 -1
  5. package/assets/agents/jd-judge-b.md +3 -1
  6. package/assets/agents/review-readability.md +4 -1
  7. package/assets/agents/review-reliability.md +4 -1
  8. package/assets/agents/review-resilience.md +4 -1
  9. package/assets/agents/review-risk.md +4 -1
  10. package/assets/agents/sdd-apply.md +6 -1
  11. package/assets/agents/sdd-archive.md +6 -1
  12. package/assets/agents/sdd-design.md +6 -1
  13. package/assets/agents/sdd-explore.md +6 -2
  14. package/assets/agents/sdd-init.md +10 -2
  15. package/assets/agents/sdd-onboard.md +6 -1
  16. package/assets/agents/sdd-proposal.md +6 -1
  17. package/assets/agents/sdd-spec.md +6 -1
  18. package/assets/agents/sdd-status.md +6 -1
  19. package/assets/agents/sdd-sync.md +6 -1
  20. package/assets/agents/sdd-tasks.md +6 -1
  21. package/assets/agents/sdd-verify.md +6 -1
  22. package/assets/chains/4r-review.chain.md +2 -0
  23. package/assets/chains/sdd-full.chain.md +1 -1
  24. package/assets/chains/sdd-plan.chain.md +1 -1
  25. package/assets/chains/sdd-verify.chain.md +1 -1
  26. package/assets/orchestrator-delegation.md +246 -67
  27. package/assets/orchestrator.md +7 -14
  28. package/assets/sdd-orchestrator-workflow.md +154 -9
  29. package/assets/support/sdd-status-contract.md +19 -1
  30. package/contracts/review-integration/v1/fixtures/consent.fixture.json +3 -3
  31. package/contracts/review-integration/v1/fixtures/start-v2.fixture.json +19 -28
  32. package/contracts/review-integration/v1/fixtures/start.fixture.json +1 -10
  33. package/contracts/review-integration/v1/fixtures/status-v2.fixture.json +12 -21
  34. package/contracts/review-integration/v1/schemas/correction-plan-request.schema.json +49 -0
  35. package/contracts/review-integration/v1/schemas/operation.schema.json +76 -0
  36. package/contracts/review-integration/v1/schemas/repair.schema.json +39 -0
  37. package/contracts/review-integration/v1/schemas/status-v2.schema.json +4 -2
  38. package/contracts/review-integration/v1/schemas/status.schema.json +4 -2
  39. package/contracts/review-integration/v2/fixtures/consent.fixture.json +1 -1
  40. package/contracts/review-integration/v2/fixtures/start.fixture.json +1 -10
  41. package/contracts/review-integration/v2/fixtures/status.fixture.json +1 -10
  42. package/contracts/review-integration/v2/schemas/failure.schema.json +5 -1
  43. package/contracts/review-integration/v2/schemas/operation.schema.json +6 -1
  44. package/contracts/review-integration/v2/schemas/repair.schema.json +4 -2
  45. package/contracts/review-integration/v2/schemas/start.schema.json +5 -2
  46. package/contracts/review-integration/v2/schemas/status.schema.json +4 -2
  47. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +30 -0
  48. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/README.md +12 -0
  49. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/manifest.json +65 -0
  50. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/lens.schema.json +16 -0
  51. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/refuter.schema.json +1 -0
  52. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/targeted-validator.schema.json +1 -0
  53. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/lens.json +1 -0
  54. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/refuter.json +1 -0
  55. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/targeted-validator.json +1 -0
  56. package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-capabilities.baseline.json +15 -0
  57. package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-roles.baseline.json +42 -0
  58. package/docs/native-authority-architecture.md +5 -5
  59. package/docs/review-integration.md +22 -2
  60. package/extensions/gentle-ai.ts +1595 -201
  61. package/extensions/sdd-init.ts +19 -6
  62. package/extensions/skill-registry.ts +10 -2
  63. package/extensions/startup-banner.ts +10 -4
  64. package/lib/gentle-ai-binary.ts +173 -2
  65. package/lib/git-commit-transaction.ts +77 -17
  66. package/lib/native-review-cli.ts +528 -65
  67. package/lib/provider-contract-bundle.ts +704 -0
  68. package/lib/review-candidate-view.ts +527 -18
  69. package/lib/review-compact-contract.ts +59 -248
  70. package/lib/review-host-relay.ts +436 -0
  71. package/lib/review-integration-v2.ts +537 -36
  72. package/lib/review-relay-contract.ts +16 -0
  73. package/lib/sdd-preflight.ts +53 -1
  74. package/package.json +5 -2
  75. package/runtime/gentle-ai-binary.mjs +173 -2
  76. package/runtime/git-commit-transaction.mjs +75 -15
  77. package/runtime/native-review-cli.mjs +524 -61
  78. package/runtime/review-integration-v2.mjs +536 -35
  79. package/runtime/review-relay-contract.mjs +17 -0
  80. package/scripts/build-git-commit-transaction-runner.mjs +1 -0
  81. package/scripts/check-provider-contract.mjs +138 -0
  82. package/scripts/gentle-ai-installer.mjs +23 -13
  83. package/scripts/maintainer/provider-relay-matrix.mjs +219 -0
  84. package/scripts/mirror-provider-contract.mjs +143 -0
  85. package/scripts/test-packed-runner.mjs +16 -2
  86. package/scripts/verify-package-files.mjs +110 -33
  87. package/skills/_shared/review-ledger-contract.md +4 -6
  88. package/skills/gentle-ai/SKILL.md +4 -4
  89. package/skills/issue-creation/SKILL.md +94 -168
  90. package/skills/judgment-day/SKILL.md +7 -1
  91. package/skills/judgment-day/references/prompts-and-formats.md +2 -0
  92. package/skills/rdd-defect-workflow/SKILL.md +54 -0
  93. package/tests/background-subagents.test.ts +771 -0
  94. package/tests/crosslane/cross-lane.mjs +1169 -0
  95. package/tests/delegated-key-learnings-contract.test.ts +238 -0
  96. package/tests/fixtures/devbinary/capabilities-v2.1.derived.json +331 -0
  97. package/tests/fixtures/devbinary/capabilities-v2.2.captured.json +340 -0
  98. package/tests/fixtures/devbinary/consent-v3.captured.json +37 -0
  99. package/tests/fixtures/devbinary/failure-v2-capture-evidence.captured.json +16 -0
  100. package/tests/fixtures/devbinary/result-artifact-v2-path.captured.json +12 -0
  101. package/tests/fixtures/devbinary/result-artifact-v2.captured.json +12 -0
  102. package/tests/fixtures/devbinary/start-v3-consent-declined.captured.json +19 -0
  103. package/tests/fixtures/devbinary/start-v3-consent-granted.captured.json +109 -0
  104. package/tests/fixtures/devbinary/status-v5-capture-result-submission.captured.json +184 -0
  105. package/tests/fixtures/devbinary/status-v5-repository-context.captured.json +138 -0
  106. package/tests/fixtures/devbinary/status-v5.captured.json +88 -0
  107. package/tests/fixtures/provider-contract-bundle/v1.1.0/README.md +12 -0
  108. package/tests/fixtures/provider-contract-bundle/v1.1.0/manifest.json +65 -0
  109. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/lens.schema.json +16 -0
  110. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/refuter.schema.json +1 -0
  111. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/targeted-validator.schema.json +1 -0
  112. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/lens.json +1 -0
  113. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/refuter.json +1 -0
  114. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/targeted-validator.json +1 -0
  115. package/tests/gentle-ai-binary.test.ts +1 -1
  116. package/tests/gentle-ai-dev-binary-surfacing.test.ts +195 -0
  117. package/tests/gentle-ai-dev-binary.test.ts +336 -0
  118. package/tests/gentle-ai-installer.test.ts +46 -46
  119. package/tests/git-commit-transaction.test.ts +229 -1
  120. package/tests/maintainer/provider-relay.maintest.ts +265 -0
  121. package/tests/native-review-capability-contract.test.ts +48 -2
  122. package/tests/native-review-cli.test.ts +56 -0
  123. package/tests/native-review-consent.test.ts +164 -3
  124. package/tests/native-review-parity-runtime.test.ts +37 -0
  125. package/tests/native-review-parity.test.ts +218 -15
  126. package/tests/native-sdd-attempt-authority.test.ts +235 -0
  127. package/tests/orchestrator-budget.test.ts +30 -5
  128. package/tests/package-manifest.test.ts +98 -72
  129. package/tests/provider-contract-bundle.test.ts +385 -0
  130. package/tests/provider-contract-mirror.test.ts +206 -0
  131. package/tests/provider-defect-handoff.test.ts +355 -0
  132. package/tests/review-actor-tool-deny.test.ts +12 -13
  133. package/tests/review-candidate-view.test.ts +489 -9
  134. package/tests/review-compact-contract.test.ts +52 -119
  135. package/tests/review-controller-native-recovery.test.ts +643 -47
  136. package/tests/review-controller-native-routing.test.ts +1667 -222
  137. package/tests/review-controller-workspace-root.test.ts +17 -2
  138. package/tests/review-corrected-finalize-binding.test.ts +175 -0
  139. package/tests/review-dispatch-hydration-gap.test.ts +197 -0
  140. package/tests/review-host-relay-routing.test.ts +317 -0
  141. package/tests/review-host-relay.test.ts +520 -0
  142. package/tests/review-integration-v2-forward.test.ts +631 -0
  143. package/tests/review-integration-v2.test.ts +114 -0
  144. package/tests/review-ledger-contract.test.ts +12 -28
  145. package/tests/review-recovered-lineage-routing.test.ts +246 -0
  146. package/tests/review-relay-transport-agent.test.ts +249 -0
  147. package/tests/runtime-harness.mjs +242 -14
  148. package/tests/sdd-agent-tools.test.ts +18 -33
  149. package/tests/skill-collision-prefixes.test.ts +1 -0
  150. package/tests/skill-registry.test.ts +50 -1
  151. package/tests/verify-package-files.test.ts +62 -0
  152. package/assets/agents/review-refuter.md +0 -40
  153. package/assets/agents/review-validator.md +0 -23
  154. package/lib/native-review-remediation.ts +0 -49
  155. package/lib/review-compact.ts +0 -947
  156. package/lib/review-refuter-adapter.ts +0 -129
  157. package/lib/review-runtime-contract.ts +0 -68
  158. package/prompts/gcl.md +0 -54
  159. package/prompts/gis.md +0 -25
  160. package/prompts/gpr.md +0 -41
  161. package/prompts/gwr.md +0 -31
  162. package/tests/fixtures/native-review-cli/v2.1.2/bind-sdd.json +0 -25
  163. package/tests/fixtures/native-review-cli/v2.1.2/finalize.json +0 -8
  164. package/tests/fixtures/native-review-cli/v2.1.2/sdd-status-engram.json +0 -139
  165. package/tests/fixtures/native-review-cli/v2.1.2/sdd-status.json +0 -200
  166. package/tests/fixtures/native-review-cli/v2.1.2/start.json +0 -12
  167. package/tests/fixtures/native-review-cli/v2.1.2/validate-allow.json +0 -24
  168. package/tests/fixtures/native-review-cli/v2.1.2/validate-deny-empty-context.json +0 -20
  169. package/tests/fixtures/native-review-cli/v2.1.2/validate-deny.json +0 -28
  170. package/tests/review-compact.test.ts +0 -243
  171. package/tests/review-refuter-adapter.test.ts +0 -89
@@ -1,18 +1,16 @@
1
- import {
2
- CAUSAL_DISPOSITION,
3
- COMPACT_EVIDENCE_CLASS,
4
- COMPACT_FINDING_OUTCOME,
5
- COMPACT_SEVERITY,
6
- type CompactRefuterResultInput,
7
- type CompactFinding,
8
- type CompactLensResultInput,
9
- type CompactValidationProofInput,
10
- type CompactReviewResultInput,
11
- type CompactTargetedValidationInput,
12
- } from "./review-compact.ts";
13
- import { REVIEW_LENS } from "./review-triggers.ts";
14
- import { canonicalJsonV1, domainHashV1 } from "./review-canonical.ts";
15
- import { normalizeRefuterBatch, type RefuterBatch } from "./review-refuter-adapter.ts";
1
+ // The reduced Pi FINALIZE input contract (gentle-pi#311 P5).
2
+ //
3
+ // Pi no longer authors or transports reviewer, refuter, or validator
4
+ // verdicts: lens results are admitted natively through the pi host relay,
5
+ // the adversarial roles execute through Go-owned pi processes via
6
+ // provider-rendered self-contained vectors, and the terminal FINALIZE runs
7
+ // the provider's own negotiated transition (captured-results discovery).
8
+ // What remains here are the negotiated collection ANSWERS the pinned
9
+ // provider still consumes from the host: the pre-edit correction forecast,
10
+ // the targeted validation document requested by the exact
11
+ // `external.run_targeted_validation` collection input, and the final
12
+ // verification evidence with its explicit outcome.
13
+
16
14
  import { CORRECTION_OUTCOMES, type CorrectionOutcome } from "./review-correction-lifecycle.ts";
17
15
 
18
16
  const DIGEST = /^[0-9a-f]{64}$/;
@@ -30,51 +28,46 @@ export class CompactReviewContractError extends Error {
30
28
  }
31
29
  }
32
30
 
33
- export interface CompactStartContractInput {
34
- cwd: string;
35
- lineageId?: string;
36
- policyHash: string;
37
- projection?: { kind: "complete" };
31
+ // Relocated from the deleted lib/review-compact.ts (gentle-pi#311 P5): the
32
+ // only compact shapes with surviving production consumers are the targeted
33
+ // validation document and its component rows.
34
+ export interface CompactValidationCheckInput {
35
+ passed: boolean;
36
+ evidence: string[];
37
+ }
38
+
39
+ export interface CompactFollowUp {
40
+ finding_id: string;
41
+ location: string;
42
+ summary: string;
43
+ proof_refs: string[];
44
+ }
45
+
46
+ export interface CompactTargetedValidationInput {
47
+ request_hash: string;
48
+ correction_ids: string[];
49
+ original_criteria: CompactValidationCheckInput;
50
+ correction_regression: CompactValidationCheckInput;
51
+ fix_caused_findings?: unknown[];
52
+ follow_ups: CompactFollowUp[];
38
53
  }
39
54
 
40
55
  export interface CompactFinalizeContractInput {
41
56
  cwd: string;
42
57
  lineageId?: string;
43
- review_result?: CompactReviewResultInput;
44
58
  correction_line_forecast?: number;
45
- validation_proof?: CompactValidationProofInput;
46
59
  validation?: CompactTargetedValidationInput;
47
60
  final_evidence?: string;
48
61
  final_verification_passed?: boolean;
49
62
  final_verification_outcome?: CorrectionOutcome;
50
- refuter_batch?: unknown;
51
- }
52
-
53
- export interface NativeRefuterRequestInput {
54
- lineageId: string;
55
- candidateTree?: string;
56
- reviewResult: CompactReviewResultInput;
57
- }
58
-
59
- export interface NativeRefuterRequest {
60
- request_hash: string;
61
- findings: CompactFinding[];
62
- }
63
-
64
- export interface NativeValidationRequestInput {
65
- lineageId: string;
66
- candidateTree: string;
67
- state: unknown;
68
- }
69
-
70
- export interface NativeValidationRequest {
71
- request_hash: string;
72
- lineage_id: string;
73
- blocking_finding_ids: string[];
74
- fix_finding_ids: string[];
75
- correction_candidate_tree: string;
76
- correction_identity: string;
77
- validation_criteria: readonly string[];
63
+ /**
64
+ * Explicit acknowledgement that this FINALIZE may spend real model tokens:
65
+ * a provider host-relay slot runs one locked-down `pi` reviewer subprocess
66
+ * per outstanding lens. Absent, FINALIZE forecasts the run and spends
67
+ * nothing. Same shape as the existing `committedOnly` acknowledgement — the
68
+ * caller states the consequence it accepts.
69
+ */
70
+ reviewer_run_acknowledged?: boolean;
78
71
  }
79
72
 
80
73
  function fail(area: string, code: string, message: string): never {
@@ -112,70 +105,12 @@ function strings(value: unknown, area: string): string[] {
112
105
  return parsed;
113
106
  }
114
107
 
115
- function enumValue<T extends Record<string, string>>(value: unknown, values: T, area: string): T[keyof T] {
116
- const parsed = string(value, area);
117
- if (!Object.values(values).includes(parsed)) return fail(area, "enum", "contains an unsupported value");
118
- return parsed as T[keyof T];
119
- }
120
-
121
108
  function optionalLineage(value: unknown, area: string): string | undefined {
122
109
  const parsed = optionalString(value, area);
123
110
  if (parsed !== undefined && !LINEAGE_ID.test(parsed)) fail(area, "lineage", "is malformed");
124
111
  return parsed;
125
112
  }
126
113
 
127
- function parseFinding(value: unknown, area: string) {
128
- const row = exact(value, area, ["location", "severity", "claim", "proof_refs"], ["id", "lens", "evidence_class", "causal_disposition"]);
129
- const severity = enumValue(row.severity, COMPACT_SEVERITY, `${area}.severity`);
130
- const severe = severity === COMPACT_SEVERITY.BLOCKER || severity === COMPACT_SEVERITY.CRITICAL;
131
- if (severe && (row.evidence_class === undefined || row.causal_disposition === undefined)) fail(area, "required", "severe findings require evidence_class and causal_disposition");
132
- return {
133
- ...(row.id === undefined ? {} : { id: string(row.id, `${area}.id`) }),
134
- ...(row.lens === undefined ? {} : { lens: enumValue(row.lens, REVIEW_LENS, `${area}.lens`) }),
135
- location: string(row.location, `${area}.location`),
136
- severity,
137
- claim: string(row.claim, `${area}.claim`),
138
- ...(row.evidence_class === undefined ? {} : { evidence_class: enumValue(row.evidence_class, COMPACT_EVIDENCE_CLASS, `${area}.evidence_class`) }),
139
- ...(row.causal_disposition === undefined ? {} : { causal_disposition: enumValue(row.causal_disposition, CAUSAL_DISPOSITION, `${area}.causal_disposition`) }),
140
- proof_refs: strings(row.proof_refs, `${area}.proof_refs`),
141
- };
142
- }
143
-
144
- function parseReviewResult(value: unknown, area: string): CompactReviewResultInput {
145
- const input = exact(value, area, ["lens_results"], ["refuter_request_hash", "refuter_results"]);
146
- if (!Array.isArray(input.lens_results)) fail(`${area}.lens_results`, "type", "must be an array");
147
- const lens_results = input.lens_results.map((item, index) => {
148
- const row = exact(item, `${area}.lens_results[${index}]`, ["findings", "evidence"], ["lens"]);
149
- if (!Array.isArray(row.findings)) fail(`${area}.lens_results[${index}].findings`, "type", "must be an array");
150
- return {
151
- ...(row.lens === undefined ? {} : { lens: enumValue(row.lens, REVIEW_LENS, `${area}.lens_results[${index}].lens`) }),
152
- findings: row.findings.map((finding, findingIndex) => parseFinding(finding, `${area}.lens_results[${index}].findings[${findingIndex}]`)),
153
- evidence: strings(row.evidence, `${area}.lens_results[${index}].evidence`),
154
- };
155
- });
156
- const refuter_request_hash = optionalString(input.refuter_request_hash, `${area}.refuter_request_hash`);
157
- if (refuter_request_hash !== undefined && !DIGEST.test(refuter_request_hash)) fail(`${area}.refuter_request_hash`, "digest", "is malformed");
158
- let refuter_results: CompactRefuterResultInput[] | undefined;
159
- if (input.refuter_results !== undefined) {
160
- if (!Array.isArray(input.refuter_results)) fail(`${area}.refuter_results`, "type", "must be an array");
161
- refuter_results = input.refuter_results.map((item, index) => {
162
- const row = exact(item, `${area}.refuter_results[${index}]`, ["finding_id", "outcome", "proof_refs"]);
163
- return { finding_id: string(row.finding_id, `${area}.refuter_results[${index}].finding_id`), outcome: enumValue(row.outcome, COMPACT_FINDING_OUTCOME, `${area}.refuter_results[${index}].outcome`), proof_refs: strings(row.proof_refs, `${area}.refuter_results[${index}].proof_refs`) };
164
- });
165
- }
166
- return { lens_results, ...(refuter_request_hash === undefined ? {} : { refuter_request_hash }), ...(refuter_results === undefined ? {} : { refuter_results }) };
167
- }
168
-
169
- function parseValidationProof(value: unknown, area: string): CompactValidationProofInput {
170
- const input = exact(value, area, ["original_criteria", "correction_regression"]);
171
- const check = (item: unknown, label: string) => {
172
- const row = exact(item, label, ["passed", "evidence"]);
173
- if (typeof row.passed !== "boolean") fail(`${label}.passed`, "type", "must be boolean");
174
- return { passed: row.passed, evidence: strings(row.evidence, `${label}.evidence`) };
175
- };
176
- return { original_criteria: check(input.original_criteria, `${area}.original_criteria`), correction_regression: check(input.correction_regression, `${area}.correction_regression`) };
177
- }
178
-
179
114
  function parseValidation(value: unknown, area: string): CompactTargetedValidationInput {
180
115
  const input = exact(value, area, ["request_hash", "correction_ids", "original_criteria", "correction_regression", "fix_caused_findings", "follow_ups"]);
181
116
  const check = (item: unknown, label: string) => {
@@ -194,78 +129,9 @@ function parseValidation(value: unknown, area: string): CompactTargetedValidatio
194
129
  return { request_hash, correction_ids: strings(input.correction_ids, `${area}.correction_ids`), original_criteria: check(input.original_criteria, `${area}.original_criteria`), correction_regression: check(input.correction_regression, `${area}.correction_regression`), fix_caused_findings: [], follow_ups };
195
130
  }
196
131
 
197
- const CANDIDATE_CAUSAL_DISPOSITIONS = new Set<string>([
198
- CAUSAL_DISPOSITION.INTRODUCED,
199
- CAUSAL_DISPOSITION.BEHAVIOR_ACTIVATED,
200
- CAUSAL_DISPOSITION.WORSENED,
201
- ]);
202
-
203
- export function deriveNativeRefuterRequest(input: NativeRefuterRequestInput): NativeRefuterRequest | undefined {
204
- const lens_results = input.reviewResult.lens_results.map((result, index) => ({
205
- lens: result.lens ?? `lens-${index}`,
206
- findings: result.findings.map((finding) => ({ ...finding, proof_refs: [...finding.proof_refs].toSorted() })).toSorted((left, right) => (left.id ?? "").localeCompare(right.id ?? "")),
207
- evidence: [...result.evidence].toSorted(),
208
- })).toSorted((left, right) => left.lens.localeCompare(right.lens));
209
- const findings = lens_results.flatMap(({ findings }) => findings).filter((finding) =>
210
- (finding.severity === COMPACT_SEVERITY.BLOCKER || finding.severity === COMPACT_SEVERITY.CRITICAL) &&
211
- finding.evidence_class === COMPACT_EVIDENCE_CLASS.INFERENTIAL &&
212
- CANDIDATE_CAUSAL_DISPOSITIONS.has(finding.causal_disposition ?? ""),
213
- );
214
- if (findings.some((finding) => finding.id === undefined || finding.lens === undefined)) fail("review/finalize.review_result", "finding-id", "inferential severe findings require stable IDs and lenses for refuter derivation");
215
- if (findings.length === 0) return undefined;
216
- if (input.candidateTree === undefined) fail("review/finalize", "candidate-tree", "requires a frozen candidate tree for refuter derivation");
217
- return {
218
- request_hash: domainHashV1("compact-refuter-request", {
219
- lineage_id: input.lineageId,
220
- candidate_tree: input.candidateTree,
221
- lens_results,
222
- finding_ids: findings.map(({ id }) => id),
223
- }),
224
- findings: findings as CompactFinding[],
225
- };
226
- }
227
-
228
- export function deriveNativeValidationRequest(input: NativeValidationRequestInput): NativeValidationRequest {
229
- const state = record(input.state, "review/finalize.validation-request");
230
- if (string(state.lineage_id, "review/finalize.validation-request.lineage_id") !== input.lineageId || state.state !== "correction_required") fail("review/finalize.validation-request", "state", "requires the authoritative correction-required lineage");
231
- const initial = record(state.initial_snapshot, "review/finalize.validation-request.initial_snapshot");
232
- const initialCandidateTree = string(initial.candidate_tree, "review/finalize.validation-request.initial_snapshot.candidate_tree");
233
- const fix_finding_ids = strings(state.fix_finding_ids, "review/finalize.validation-request.fix_finding_ids");
234
- if (!Array.isArray(state.findings)) fail("review/finalize.validation-request.findings", "type", "must be an array");
235
- const outcomes = state.outcomes === undefined ? undefined : record(state.outcomes, "review/finalize.validation-request.outcomes");
236
- const blocking_finding_ids = state.findings.flatMap((finding, index) => {
237
- const row = record(finding, `review/finalize.validation-request.findings[${index}]`);
238
- if (row.severity !== COMPACT_SEVERITY.BLOCKER && row.severity !== COMPACT_SEVERITY.CRITICAL) return [];
239
- const id = string(row.id, `review/finalize.validation-request.findings[${index}].id`);
240
- // Mirror native FINALIZE semantics (#171): only corroborated severe findings
241
- // block correction validation. Severe findings classified pre-existing or
242
- // base-only carry the non-blocking `info` outcome, are routed to follow-ups,
243
- // and must stay inert in the targeted-validation request.
244
- if (outcomes === undefined) return [id];
245
- const outcome = outcomes[id] === undefined ? undefined : enumValue(outcomes[id], COMPACT_FINDING_OUTCOME, `review/finalize.validation-request.outcomes.${id}`);
246
- return outcome === COMPACT_FINDING_OUTCOME.CORROBORATED ? [id] : [];
247
- });
248
- if (canonicalJsonV1(fix_finding_ids.toSorted()) !== canonicalJsonV1(blocking_finding_ids.toSorted())) fail("review/finalize.validation-request", "finding-ids", "fix IDs must exactly match frozen blocking findings");
249
- const correction_identity = domainHashV1("compact-correction-identity", { initial_candidate_tree: initialCandidateTree, correction_candidate_tree: input.candidateTree });
250
- const validation_criteria = ["original_criteria", "correction_regression", "follow_ups"] as const;
251
- return { lineage_id: input.lineageId, blocking_finding_ids, fix_finding_ids, correction_candidate_tree: input.candidateTree, correction_identity, validation_criteria, request_hash: domainHashV1("compact-validation-request", { lineage_id: input.lineageId, blocking_finding_ids, fix_finding_ids, correction_identity, validation_criteria }) };
252
- }
253
-
254
- export function parseCompactStartInput(value: unknown): CompactStartContractInput {
255
- const input = exact(value, "review/start", ["cwd", "policyHash"], ["lineageId", "projection"]);
256
- const policyHash = string(input.policyHash, "review/start.policyHash");
257
- if (!DIGEST.test(policyHash)) fail("review/start.policyHash", "digest", "is malformed");
258
- let projection: { kind: "complete" } | undefined;
259
- if (input.projection !== undefined) {
260
- const raw = exact(input.projection, "review/start.projection", ["kind"]);
261
- if (raw.kind !== "complete") fail("review/start.projection.kind", "enum", "must be complete");
262
- projection = { kind: "complete" };
263
- }
264
- return { cwd: string(input.cwd, "review/start.cwd"), ...(optionalLineage(input.lineageId, "review/start.lineageId") === undefined ? {} : { lineageId: optionalLineage(input.lineageId, "review/start.lineageId")! }), policyHash, ...(projection === undefined ? {} : { projection }) };
265
- }
266
-
267
- function parseCompactFinalizeInputValue(value: unknown, preserveFinalEvidence: boolean): CompactFinalizeContractInput {
268
- const input = exact(value, "review/finalize", ["cwd"], ["lineageId", "review_result", "correction_line_forecast", "validation_proof", "validation", "final_evidence", "final_verification_passed", "final_verification_outcome", "refuter_batch"]);
132
+ function parseCompactFinalizeInputValue(value: unknown): CompactFinalizeContractInput {
133
+ const input = exact(value, "review/finalize", ["cwd"], ["lineageId", "correction_line_forecast", "validation", "final_evidence", "final_verification_passed", "final_verification_outcome", "reviewer_run_acknowledged"]);
134
+ if (input.reviewer_run_acknowledged !== undefined && typeof input.reviewer_run_acknowledged !== "boolean") fail("review/finalize.reviewer_run_acknowledged", "type", "must be boolean");
269
135
  const outcomeFields = Number(input.final_verification_passed !== undefined) + Number(input.final_verification_outcome !== undefined);
270
136
  if ((input.final_evidence === undefined && outcomeFields !== 0) || (input.final_evidence !== undefined && outcomeFields !== 1)) fail("review/finalize", "field-pair", "final evidence requires exactly one verification result or outcome");
271
137
  let correction_line_forecast: number | undefined;
@@ -282,83 +148,28 @@ function parseCompactFinalizeInputValue(value: unknown, preserveFinalEvidence: b
282
148
  }
283
149
  let final_evidence: string | undefined;
284
150
  if (input.final_evidence !== undefined) {
285
- if (preserveFinalEvidence) {
286
- if (typeof input.final_evidence !== "string" || input.final_evidence.length === 0) fail("review/finalize.final_evidence", "empty", "must contain at least one byte");
287
- final_evidence = input.final_evidence;
288
- } else {
289
- final_evidence = string(input.final_evidence, "review/finalize.final_evidence");
290
- }
151
+ // Final evidence is preserved BYTE-FOR-BYTE: it is staged into the native
152
+ // --evidence file untouched, so the canonical trimmed-string rule does
153
+ // not apply — only zero-length evidence is refused.
154
+ if (typeof input.final_evidence !== "string" || input.final_evidence.length === 0) fail("review/finalize.final_evidence", "empty", "must contain at least one byte");
155
+ final_evidence = input.final_evidence;
291
156
  }
292
- return { cwd: string(input.cwd, "review/finalize.cwd"), ...(optionalLineage(input.lineageId, "review/finalize.lineageId") === undefined ? {} : { lineageId: optionalLineage(input.lineageId, "review/finalize.lineageId")! }), ...(input.review_result === undefined ? {} : { review_result: parseReviewResult(input.review_result, "review/finalize.review_result") }), ...(correction_line_forecast === undefined ? {} : { correction_line_forecast }), ...(input.validation_proof === undefined ? {} : { validation_proof: parseValidationProof(input.validation_proof, "review/finalize.validation_proof") }), ...(input.validation === undefined ? {} : { validation: parseValidation(input.validation, "review/finalize.validation") }), ...(final_evidence === undefined ? {} : { final_evidence }), ...(input.final_verification_passed === undefined ? {} : { final_verification_passed: input.final_verification_passed }), ...(final_verification_outcome === undefined ? {} : { final_verification_outcome }), ...(input.refuter_batch === undefined ? {} : { refuter_batch: input.refuter_batch }) };
157
+ return { cwd: string(input.cwd, "review/finalize.cwd"), ...(optionalLineage(input.lineageId, "review/finalize.lineageId") === undefined ? {} : { lineageId: optionalLineage(input.lineageId, "review/finalize.lineageId")! }), ...(correction_line_forecast === undefined ? {} : { correction_line_forecast }), ...(input.validation === undefined ? {} : { validation: parseValidation(input.validation, "review/finalize.validation") }), ...(final_evidence === undefined ? {} : { final_evidence }), ...(input.final_verification_passed === undefined ? {} : { final_verification_passed: input.final_verification_passed }), ...(final_verification_outcome === undefined ? {} : { final_verification_outcome }), ...(input.reviewer_run_acknowledged === undefined ? {} : { reviewer_run_acknowledged: input.reviewer_run_acknowledged as boolean }) };
293
158
  }
294
159
 
295
- export function parseCompactFinalizeInput(value: unknown): CompactFinalizeContractInput {
296
- return parseCompactFinalizeInputValue(value, false);
297
- }
298
-
299
- export function parseNativeCompactFinalizeInput(value: unknown): CompactFinalizeContractInput & { refuter_batch?: RefuterBatch } {
300
- const input = parseCompactFinalizeInputValue(value, true);
301
- for (const lens of input.review_result?.lens_results ?? []) {
302
- if (lens.evidence.length === 0 || lens.findings.some((finding) => finding.proof_refs?.length === 0)) fail("review/finalize.review_result", "empty", "reviewer evidence and proof_refs must be non-empty");
303
- if (lens.findings.some((finding) => finding.evidence_class === COMPACT_EVIDENCE_CLASS.INFO)) fail("review/finalize.review_result", "enum", "reviewer evidence_class must match the published native schema");
304
- }
305
- for (const validation of [input.validation_proof, input.validation]) {
306
- if (validation && (validation.original_criteria.evidence.length === 0 || validation.correction_regression.evidence.length === 0)) fail("review/finalize.validation", "empty", "validator evidence must be non-empty");
160
+ export function parseNativeCompactFinalizeInput(value: unknown): CompactFinalizeContractInput {
161
+ const input = parseCompactFinalizeInputValue(value);
162
+ if (input.validation && (input.validation.original_criteria.evidence.length === 0 || input.validation.correction_regression.evidence.length === 0)) {
163
+ fail("review/finalize.validation", "empty", "validator evidence must be non-empty");
307
164
  }
308
165
  if (input.validation?.follow_ups.some((row) => row.proof_refs.length === 0)) fail("review/finalize.validation.follow_ups", "empty", "follow-up proof_refs must be non-empty");
309
- let refuter_batch: RefuterBatch | undefined;
310
- if (input.refuter_batch !== undefined) {
311
- const reviewResult = input.review_result;
312
- if (reviewResult?.refuter_request_hash === undefined) fail("review/finalize.refuter_batch", "request-hash", "requires the expected review_result.refuter_request_hash");
313
- const candidateCausality = new Set<string>([CAUSAL_DISPOSITION.INTRODUCED, CAUSAL_DISPOSITION.BEHAVIOR_ACTIVATED, CAUSAL_DISPOSITION.WORSENED]);
314
- const findings: CompactFinding[] = [];
315
- for (const lens of reviewResult.lens_results) {
316
- for (const finding of lens.findings) {
317
- if ((finding.severity !== COMPACT_SEVERITY.BLOCKER && finding.severity !== COMPACT_SEVERITY.CRITICAL) || finding.evidence_class !== COMPACT_EVIDENCE_CLASS.INFERENTIAL || !candidateCausality.has(finding.causal_disposition ?? "")) continue;
318
- if (finding.id === undefined || (finding.lens ?? lens.lens) === undefined) fail("review/finalize.refuter_batch", "finding-id", "requires stable IDs and lenses for every refuted finding");
319
- findings.push({
320
- id: finding.id,
321
- lens: (finding.lens ?? lens.lens)!,
322
- location: finding.location!,
323
- severity: finding.severity,
324
- claim: finding.claim!,
325
- evidence_class: finding.evidence_class,
326
- causal_disposition: finding.causal_disposition!,
327
- proof_refs: [...finding.proof_refs!],
328
- });
329
- }
330
- }
331
- const normalized = normalizeRefuterBatch({ request_hash: reviewResult.refuter_request_hash, findings }, input.refuter_batch);
332
- if (normalized.status !== "normalized") fail("review/finalize.refuter_batch", normalized.reason_code, "must match the frozen request hash and IDs with complete concrete proof rows");
333
- refuter_batch = {
334
- schema: "gentle-ai.refuter-result-batch/v1",
335
- request_hash: normalized.refuter_request_hash,
336
- results: normalized.refuter_results,
337
- };
338
- }
339
- return { ...input, ...(refuter_batch === undefined ? {} : { refuter_batch }) };
340
- }
341
-
342
- const NATIVE_REVIEWER_LENS = {
343
- [REVIEW_LENS.RISK]: "risk",
344
- [REVIEW_LENS.RESILIENCE]: "resilience",
345
- [REVIEW_LENS.READABILITY]: "readability",
346
- [REVIEW_LENS.RELIABILITY]: "reliability",
347
- } as const;
348
-
349
- export function toNativeReviewerDocument(input: CompactLensResultInput) {
350
- const lens = input.lens === undefined ? undefined : NATIVE_REVIEWER_LENS[input.lens as keyof typeof NATIVE_REVIEWER_LENS];
351
- return {
352
- ...(lens === undefined ? {} : { lens }),
353
- findings: input.findings.map((finding) => ({ ...finding, ...(finding.lens === undefined ? {} : { lens: NATIVE_REVIEWER_LENS[finding.lens as keyof typeof NATIVE_REVIEWER_LENS] }) })),
354
- evidence: [...input.evidence],
355
- };
166
+ return input;
356
167
  }
357
168
 
358
- export function toNativeValidatorDocument(input: CompactValidationProofInput | CompactTargetedValidationInput) {
169
+ export function toNativeValidatorDocument(input: CompactTargetedValidationInput) {
359
170
  return {
360
171
  original_criteria: input.original_criteria,
361
172
  correction_regression: input.correction_regression,
362
- follow_ups: "follow_ups" in input ? input.follow_ups.map((row) => ({ observation: row.summary, proof_refs: [...row.proof_refs] })) : [],
173
+ follow_ups: input.follow_ups.map((row) => ({ observation: row.summary, proof_refs: [...row.proof_refs] })),
363
174
  };
364
175
  }