gentle-pi 2.1.2 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/README.md +108 -79
  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 -3
  7. package/assets/agents/review-reliability.md +4 -3
  8. package/assets/agents/review-resilience.md +4 -3
  9. package/assets/agents/review-risk.md +4 -3
  10. package/assets/agents/sdd-apply.md +11 -4
  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 +8 -1
  17. package/assets/agents/sdd-research.md +54 -0
  18. package/assets/agents/sdd-spec.md +6 -1
  19. package/assets/agents/sdd-status.md +10 -5
  20. package/assets/agents/sdd-sync.md +6 -1
  21. package/assets/agents/sdd-tasks.md +8 -4
  22. package/assets/agents/sdd-verify.md +27 -2
  23. package/assets/chains/4r-review.chain.md +2 -0
  24. package/assets/chains/sdd-full.chain.md +2 -2
  25. package/assets/chains/sdd-plan.chain.md +1 -1
  26. package/assets/chains/sdd-verify.chain.md +2 -2
  27. package/assets/orchestrator-delegation.md +145 -160
  28. package/assets/orchestrator-memory.md +2 -0
  29. package/assets/orchestrator.md +25 -48
  30. package/assets/sdd-orchestrator-workflow.md +163 -25
  31. package/assets/support/sdd-status-contract.md +24 -6
  32. package/contracts/review-integration/v1/fixtures/consent.fixture.json +3 -3
  33. package/contracts/review-integration/v1/fixtures/start-v2.fixture.json +19 -28
  34. package/contracts/review-integration/v1/fixtures/start.fixture.json +1 -10
  35. package/contracts/review-integration/v1/fixtures/status-v2.fixture.json +12 -21
  36. package/contracts/review-integration/v1/schemas/correction-plan-request.schema.json +49 -0
  37. package/contracts/review-integration/v1/schemas/operation.schema.json +76 -0
  38. package/contracts/review-integration/v1/schemas/repair.schema.json +39 -0
  39. package/contracts/review-integration/v1/schemas/status-v2.schema.json +4 -2
  40. package/contracts/review-integration/v1/schemas/status.schema.json +4 -2
  41. package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
  42. package/contracts/review-integration/v2/fixtures/consent.fixture.json +1 -1
  43. package/contracts/review-integration/v2/fixtures/start.fixture.json +1 -10
  44. package/contracts/review-integration/v2/fixtures/status.fixture.json +1 -10
  45. package/contracts/review-integration/v2/schemas/failure.schema.json +5 -1
  46. package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
  47. package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
  48. package/contracts/review-integration/v2/schemas/operation.schema.json +6 -1
  49. package/contracts/review-integration/v2/schemas/repair.schema.json +4 -2
  50. package/contracts/review-integration/v2/schemas/start.schema.json +5 -2
  51. package/contracts/review-integration/v2/schemas/status.schema.json +4 -2
  52. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +30 -0
  53. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/README.md +12 -0
  54. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/manifest.json +65 -0
  55. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/lens.schema.json +16 -0
  56. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/refuter.schema.json +1 -0
  57. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/targeted-validator.schema.json +1 -0
  58. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/lens.json +1 -0
  59. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/refuter.json +1 -0
  60. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/targeted-validator.json +1 -0
  61. package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-capabilities.baseline.json +15 -0
  62. package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-roles.baseline.json +42 -0
  63. package/docs/native-authority-architecture.md +14 -16
  64. package/docs/review-integration.md +27 -353
  65. package/extensions/ask-user-choice.ts +151 -0
  66. package/extensions/gentle-ai.ts +2710 -3211
  67. package/extensions/quiet-tools.ts +515 -32
  68. package/extensions/sdd-init.ts +21 -12
  69. package/extensions/skill-registry.ts +10 -2
  70. package/extensions/startup-banner.ts +10 -4
  71. package/lib/gentle-ai-binary.ts +173 -2
  72. package/lib/gentle-ai-renderer.ts +70 -0
  73. package/lib/model-routing-authority.ts +133 -0
  74. package/lib/native-review-cli.ts +676 -781
  75. package/lib/opaque-pi-reviewer-adapter.ts +284 -0
  76. package/lib/provider-contract-bundle.ts +704 -0
  77. package/lib/review-candidate-view.ts +836 -118
  78. package/lib/review-compact-contract.ts +59 -248
  79. package/lib/review-host-relay.ts +578 -0
  80. package/lib/review-integration-v2.ts +1254 -221
  81. package/lib/review-last-event-controller.ts +35 -0
  82. package/lib/review-relay-contract.ts +16 -0
  83. package/lib/sdd-preflight.ts +230 -67
  84. package/lib/sdd-status.ts +66 -111
  85. package/lib/terminal-theme.ts +1 -1
  86. package/package.json +83 -79
  87. package/runtime/gentle-ai-binary.mjs +174 -3
  88. package/runtime/native-review-cli.mjs +610 -715
  89. package/runtime/review-integration-v2.mjs +1223 -190
  90. package/runtime/review-relay-contract.mjs +17 -0
  91. package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +5 -5
  92. package/scripts/check-provider-contract.mjs +138 -0
  93. package/scripts/gentle-ai-installer.mjs +85 -22
  94. package/scripts/maintainer/provider-relay-matrix.mjs +403 -0
  95. package/scripts/mirror-provider-contract.mjs +143 -0
  96. package/scripts/test-packed-runner.mjs +20 -9
  97. package/scripts/verify-package-files.mjs +119 -43
  98. package/skills/_shared/review-ledger-contract.md +11 -19
  99. package/skills/chained-pr/SKILL.md +3 -0
  100. package/skills/cognitive-doc-design/SKILL.md +1 -1
  101. package/skills/comment-writer/SKILL.md +1 -1
  102. package/skills/gentle-ai/SKILL.md +7 -74
  103. package/skills/issue-creation/SKILL.md +94 -168
  104. package/skills/judgment-day/SKILL.md +9 -5
  105. package/skills/judgment-day/references/prompts-and-formats.md +2 -0
  106. package/skills/rdd-defect-workflow/SKILL.md +54 -0
  107. package/skills/release/SKILL.md +3 -3
  108. package/skills/skill-registry/SKILL.md +1 -1
  109. package/skills/work-unit-commits/SKILL.md +3 -1
  110. package/tests/artifact-language.test.ts +24 -11
  111. package/tests/ask-user-choice.test.ts +264 -0
  112. package/tests/background-subagents.test.ts +771 -0
  113. package/tests/codegraph-tools.test.ts +3 -3
  114. package/tests/crosslane/cross-lane.mjs +16 -0
  115. package/tests/delegated-key-learnings-contract.test.ts +240 -0
  116. package/tests/devbinary/native-review-parity.devtest.ts +167 -250
  117. package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
  118. package/tests/fixtures/devbinary/capabilities-v2.1.derived.json +331 -0
  119. package/tests/fixtures/devbinary/capabilities-v2.2.captured.json +340 -0
  120. package/tests/fixtures/devbinary/consent-v3.captured.json +37 -0
  121. package/tests/fixtures/devbinary/failure-v2-capture-evidence.captured.json +16 -0
  122. package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
  123. package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
  124. package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
  125. package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
  126. package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
  127. package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
  128. package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
  129. package/tests/fixtures/devbinary/result-artifact-v2-path.captured.json +12 -0
  130. package/tests/fixtures/devbinary/result-artifact-v2.captured.json +12 -0
  131. package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
  132. package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
  133. package/tests/fixtures/devbinary/start-v3-consent-declined.captured.json +19 -0
  134. package/tests/fixtures/devbinary/start-v3-consent-granted.captured.json +109 -0
  135. package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
  136. package/tests/fixtures/devbinary/status-v5-capture-result-submission.captured.json +184 -0
  137. package/tests/fixtures/devbinary/status-v5-repository-context.captured.json +138 -0
  138. package/tests/fixtures/devbinary/status-v5.captured.json +88 -0
  139. package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
  140. package/tests/fixtures/provider-contract-bundle/v1.1.0/README.md +12 -0
  141. package/tests/fixtures/provider-contract-bundle/v1.1.0/manifest.json +65 -0
  142. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/lens.schema.json +16 -0
  143. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/refuter.schema.json +1 -0
  144. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/targeted-validator.schema.json +1 -0
  145. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/lens.json +1 -0
  146. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/refuter.json +1 -0
  147. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/targeted-validator.json +1 -0
  148. package/tests/gentle-ai-binary.test.ts +83 -13
  149. package/tests/gentle-ai-dev-binary-surfacing.test.ts +195 -0
  150. package/tests/gentle-ai-dev-binary.test.ts +159 -0
  151. package/tests/gentle-ai-installer.test.ts +138 -48
  152. package/tests/gentle-ai.test.ts +820 -131
  153. package/tests/gentle-theme.test.ts +133 -0
  154. package/tests/maintainer/provider-relay.maintest.ts +601 -0
  155. package/tests/model-routing-authority.test.ts +257 -0
  156. package/tests/native-review-capability-contract.test.ts +75 -2
  157. package/tests/native-review-cli.test.ts +588 -908
  158. package/tests/native-review-consent.test.ts +283 -38
  159. package/tests/native-review-parity-runtime.test.ts +108 -350
  160. package/tests/native-review-parity.test.ts +684 -732
  161. package/tests/native-sdd-attempt-authority.test.ts +235 -0
  162. package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
  163. package/tests/orchestrator-budget.test.ts +158 -89
  164. package/tests/orchestrator-rdd-ownership.test.ts +103 -0
  165. package/tests/package-manifest.test.ts +128 -100
  166. package/tests/provider-contract-bundle.test.ts +385 -0
  167. package/tests/provider-contract-mirror.test.ts +206 -0
  168. package/tests/provider-defect-handoff.test.ts +252 -0
  169. package/tests/quiet-tool-rendering.test.ts +1055 -28
  170. package/tests/review-actor-tool-deny.test.ts +12 -13
  171. package/tests/review-authority-recovery-docs.test.ts +1 -2
  172. package/tests/review-candidate-view.test.ts +898 -12
  173. package/tests/review-compact-contract.test.ts +29 -122
  174. package/tests/review-controller-lock-status.test.ts +2 -2
  175. package/tests/review-controller-native-recovery.test.ts +366 -857
  176. package/tests/review-controller-native-routing.test.ts +1253 -4199
  177. package/tests/review-controller-retired-ops.test.ts +1 -1
  178. package/tests/review-controller-workspace-root.test.ts +236 -70
  179. package/tests/review-controller.test.ts +26 -816
  180. package/tests/review-corrected-finalize-binding.test.ts +134 -0
  181. package/tests/review-dispatch-hydration-gap.test.ts +145 -0
  182. package/tests/review-gate.test.ts +0 -45
  183. package/tests/review-host-relay-restart-parity.test.ts +360 -0
  184. package/tests/review-host-relay-routing.test.ts +352 -0
  185. package/tests/review-host-relay.test.ts +754 -0
  186. package/tests/review-integration-v2-forward.test.ts +654 -0
  187. package/tests/review-integration-v2.test.ts +317 -129
  188. package/tests/review-last-event-closure.test.ts +408 -0
  189. package/tests/review-ledger-contract.test.ts +106 -60
  190. package/tests/review-recovered-lineage-routing.test.ts +199 -0
  191. package/tests/review-relay-transport-agent.test.ts +322 -0
  192. package/tests/review-snapshot.test.ts +3 -2
  193. package/tests/runtime-harness.mjs +573 -180
  194. package/tests/sdd-agent-tools.test.ts +53 -32
  195. package/tests/sdd-preflight.test.ts +81 -15
  196. package/tests/sdd-status.test.ts +109 -110
  197. package/tests/skill-collision-prefixes.test.ts +6 -8
  198. package/tests/skill-registry.test.ts +50 -1
  199. package/tests/verify-package-files.test.ts +62 -0
  200. package/tests/writer-edit-surface-scope.test.ts +230 -0
  201. package/themes/Gentleman-Cute.json +94 -0
  202. package/themes/Gentleman-Sexy.json +92 -0
  203. package/assets/agents/review-refuter.md +0 -40
  204. package/assets/agents/review-validator.md +0 -23
  205. package/lib/git-commit-transaction.ts +0 -801
  206. package/lib/native-review-remediation.ts +0 -49
  207. package/lib/review-compact.ts +0 -947
  208. package/lib/review-refuter-adapter.ts +0 -129
  209. package/lib/review-runtime-contract.ts +0 -68
  210. package/prompts/gcl.md +0 -54
  211. package/prompts/gis.md +0 -25
  212. package/prompts/gpr.md +0 -41
  213. package/prompts/gwr.md +0 -31
  214. package/runtime/git-commit-transaction.mjs +0 -802
  215. package/scripts/run-git-commit-transaction.mjs +0 -35
  216. package/tests/fixtures/native-review-cli/v2.1.2/bind-sdd.json +0 -25
  217. package/tests/fixtures/native-review-cli/v2.1.2/finalize.json +0 -8
  218. package/tests/fixtures/native-review-cli/v2.1.2/sdd-status-engram.json +0 -139
  219. package/tests/fixtures/native-review-cli/v2.1.2/sdd-status.json +0 -200
  220. package/tests/fixtures/native-review-cli/v2.1.2/start.json +0 -12
  221. package/tests/fixtures/native-review-cli/v2.1.2/validate-allow.json +0 -24
  222. package/tests/fixtures/native-review-cli/v2.1.2/validate-deny-empty-context.json +0 -20
  223. package/tests/fixtures/native-review-cli/v2.1.2/validate-deny.json +0 -28
  224. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
  225. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
  226. package/tests/git-commit-transaction.test.ts +0 -302
  227. package/tests/review-compact.test.ts +0 -243
  228. package/tests/review-refuter-adapter.test.ts +0 -89
@@ -0,0 +1,408 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import test from "node:test";
5
+ import { __testing } from "../extensions/gentle-ai.ts";
6
+ import { reconcileUnknownReviewLastEventCapture } from "../lib/review-last-event-controller.ts";
7
+ import * as nativeReviewCliModule from "../lib/native-review-cli.ts";
8
+ import type { NativeReviewCli } from "../lib/native-review-cli.ts";
9
+ import {
10
+ decodeReviewLastEventClosureV1,
11
+ decodeReviewStartV3,
12
+ type ReviewCollectInputV3,
13
+ type ReviewStatusV3,
14
+ } from "../lib/review-integration-v2.ts";
15
+
16
+ const SHA = `sha256:${"a".repeat(64)}`;
17
+ const TREE = "b".repeat(40);
18
+ const CAPTURED_FIXTURES = join(process.cwd(), "tests", "fixtures", "devbinary");
19
+
20
+ function captured(name: string): unknown {
21
+ return JSON.parse(readFileSync(join(CAPTURED_FIXTURES, name), "utf8"));
22
+ }
23
+
24
+ function closure(operation: string, lineageId: string): Record<string, unknown> {
25
+ return {
26
+ schema: "gentle-ai.review-last-event-closure/v1",
27
+ operation,
28
+ lineage_id: lineageId,
29
+ state: operation === "review.capture-correction-plan" ? "correction_required" : "approved",
30
+ ...(operation === "review.capture-correction-plan"
31
+ ? { target_identity: SHA, request_hash: SHA, correction_lines: 2 }
32
+ : { action: "native last event closed the review" }),
33
+ store_revision: SHA,
34
+ };
35
+ }
36
+
37
+ function bindingArguments(lineageId: string, suffix = "0"): ReviewCollectInputV3["arguments"] {
38
+ return [
39
+ { name: "lineage", value: lineageId, token: `--lineage=${lineageId}` },
40
+ { name: "expected-revision", value: SHA, token: `--expected-revision=${SHA}` },
41
+ { name: "target", value: SHA, token: `--target=${SHA}` },
42
+ { name: "repository-context", value: `rctx1_${"c".repeat(64)}`, token: `--repository-context=rctx1_${"c".repeat(64)}` },
43
+ { name: "lens", value: `review-risk-${suffix}`, token: `--lens=review-risk-${suffix}` },
44
+ { name: "order", value: suffix, token: `--order=${suffix}` },
45
+ { name: "subject-hash", value: SHA, token: `--subject-hash=${SHA}` },
46
+ ];
47
+ }
48
+
49
+ function materializeInput(lineageId: string, suffix = "0"): ReviewCollectInputV3 {
50
+ const arguments_ = [
51
+ ...bindingArguments(lineageId, suffix),
52
+ { name: "agent", value: "pi", token: "--agent=pi" },
53
+ { name: "materialize", value: "true", token: "--materialize=true" },
54
+ ];
55
+ return {
56
+ name: "reviewer_result",
57
+ schema: "https://gentle-ai.dev/schema/review/reviewer/v1",
58
+ captureOperation: "review.capture-result",
59
+ arguments: arguments_,
60
+ artifactSubject: {
61
+ schema: "gentle-ai.review-artifact-subject/v2",
62
+ subjectHash: SHA,
63
+ lineageId,
64
+ authorityRevision: SHA,
65
+ targetIdentity: SHA,
66
+ baseTree: TREE,
67
+ candidateTree: TREE,
68
+ changedPathManifestSha256: SHA,
69
+ lens: `review-risk-${suffix}`,
70
+ selectedOrder: Number(suffix),
71
+ },
72
+ submission: {
73
+ operationToken: "capture-result",
74
+ argumentTokens: [...bindingArguments(lineageId, suffix).map((argument) => argument.token!), "--input={{value}}"],
75
+ values: [{ slot: "reviewer_result", domain: "artifact_path_or_stdin", substitutionLocation: 7 }],
76
+ },
77
+ };
78
+ }
79
+
80
+ function correctionPlanInput(lineageId: string): ReviewCollectInputV3 {
81
+ return {
82
+ name: "correction_plan",
83
+ schema: "gentle-ai.review-correction-plan/v1",
84
+ captureOperation: "review.capture-correction-plan",
85
+ arguments: bindingArguments(lineageId),
86
+ submission: {
87
+ operationToken: "capture-correction-plan",
88
+ argumentTokens: [...bindingArguments(lineageId).map((argument) => argument.token!), "--correction-lines={{value}}"],
89
+ values: [{ slot: "correction_lines", domain: "positive_integer", substitutionLocation: 7, minimum: 2, maximum: 3 }],
90
+ },
91
+ };
92
+ }
93
+
94
+ function roleInput(lineageId: string, operation: "review.capture-refuter" | "review.capture-validation"): ReviewCollectInputV3 {
95
+ return {
96
+ name: operation === "review.capture-refuter" ? "provider_refuter" : "targeted_validator",
97
+ schema: "gentle-ai.review-provider-role/v1",
98
+ captureOperation: operation,
99
+ arguments: [
100
+ ...bindingArguments(lineageId),
101
+ { name: "agent", value: "pi", token: "--agent=pi" },
102
+ { name: "execute", value: "true", token: "--execute=true" },
103
+ ],
104
+ };
105
+ }
106
+
107
+ function status(lineageId: string, inputs: readonly ReviewCollectInputV3[]): ReviewStatusV3 {
108
+ return {
109
+ contract: "gentle-ai.review-integration/v2",
110
+ applicability: "current_target",
111
+ authority: { version: "compact-v2", lineageId, state: "reviewing", generation: 1, revision: SHA },
112
+ action: "stop",
113
+ replayability: "not_replayable",
114
+ targetIdentity: SHA,
115
+ projection: {
116
+ schema: "gentle-ai.review-candidate-projection/v1",
117
+ kind: "current-changes",
118
+ projection: "workspace",
119
+ baseTree: TREE,
120
+ initialReviewTree: TREE,
121
+ currentCandidateTree: TREE,
122
+ pathsDigest: SHA,
123
+ paths: ["app.ts"],
124
+ intendedUntracked: [],
125
+ intendedUntrackedProof: SHA,
126
+ initialSnapshotIdentity: SHA,
127
+ currentSnapshotIdentity: SHA,
128
+ },
129
+ candidates: [],
130
+ nextTransition: { kind: "collect", reasonCode: "capture_required", collect: { inputs: [...inputs] } },
131
+ raw: { schema: "gentle-ai.review-integration.status/v5" },
132
+ } as unknown as ReviewStatusV3;
133
+ }
134
+
135
+ function capture(lineageId: string, input: ReviewCollectInputV3, native: NativeReviewCli, extras: Record<string, unknown> = {}): Promise<Record<string, unknown>> {
136
+ return __testing.executeReviewCaptureOperation({ lineageId, collectBinding: JSON.stringify(input), ...extras }, process.cwd(), native);
137
+ }
138
+
139
+ test("strictly decodes the real zero-lens closed START and every last-event closure", () => {
140
+ const start = decodeReviewStartV3(captured("start-v3-zero-lens-closed.captured.json"));
141
+ assert.equal(start.action, "closed");
142
+ assert.equal(start.state, "approved");
143
+ assert.equal(start.lensesRequired, false);
144
+ assert.deepEqual(start.selectedLenses, []);
145
+ for (const [name, operation, state] of [
146
+ ["last-event-capture-result-approved.captured.json", "review/capture-result", "approved"],
147
+ ["last-event-capture-result-correction-required.captured.json", "review/capture-result", "correction_required"],
148
+ ["last-event-capture-correction-plan.captured.json", "review.capture-correction-plan", "correction_required"],
149
+ ["last-event-capture-refuter-correction-required.captured.json", "review.capture-refuter", "correction_required"],
150
+ ["last-event-capture-refuter-approved.captured.json", "review.capture-refuter", "approved"],
151
+ ["last-event-capture-validation-approved.captured.json", "review/capture-validation", "approved"],
152
+ ] as const) {
153
+ const decoded = decodeReviewLastEventClosureV1(captured(name));
154
+ assert.equal(decoded.operation, operation, name);
155
+ assert.equal(decoded.state, state, name);
156
+ }
157
+ });
158
+
159
+ test("retired legacy client no longer exposes a FINALIZE route", () => {
160
+ assert.equal("NativeReviewCliV214" in nativeReviewCliModule, false);
161
+ assert.equal("NativeReviewCliV213" in nativeReviewCliModule, false);
162
+ });
163
+
164
+ test("one exact offered materialize binding executes only its selected slot and never follows STATUS", async (t) => {
165
+ t.after(() => __testing.setReviewHostRelayRunnerForTesting());
166
+ const lineageId = "one-slot-lineage";
167
+ const selected = materializeInput(lineageId, "0");
168
+ const ignored = materializeInput(lineageId, "1");
169
+ let statusCalls = 0;
170
+ const native = {
171
+ targetStatus: async () => {
172
+ statusCalls += 1;
173
+ return status(lineageId, [selected, ignored]);
174
+ },
175
+ } as unknown as NativeReviewCli;
176
+ const relayed: unknown[] = [];
177
+ __testing.setReviewHostRelayRunnerForTesting(async (request) => {
178
+ relayed.push(request);
179
+ return { promptByteLength: 12, resultByteLength: 8, submission: "{\"admission_decision\":\"completed\"}" };
180
+ });
181
+
182
+ const result = await capture(lineageId, selected, native, { reviewerRunAcknowledged: true });
183
+ assert.equal(statusCalls, 1);
184
+ assert.equal(relayed.length, 1);
185
+ assert.equal(result.status, "captured");
186
+ assert.equal(result.outcome, "native-reviewer-result-captured");
187
+ assert.deepEqual((relayed[0] as { captureArgumentTokens: readonly string[] }).captureArgumentTokens, selected.arguments.map((argument) => argument.token));
188
+ });
189
+
190
+ test("malformed, stale, duplicate, and incomplete collect bindings reject before capture mutation", async (t) => {
191
+ t.after(() => __testing.setReviewHostRelayRunnerForTesting());
192
+ const lineageId = "rejected-binding";
193
+ const input = materializeInput(lineageId);
194
+ let statusCalls = 0;
195
+ let launches = 0;
196
+ const native = { targetStatus: async () => { statusCalls += 1; return status(lineageId, [input]); } } as unknown as NativeReviewCli;
197
+ __testing.setReviewHostRelayRunnerForTesting(async () => { launches += 1; return { promptByteLength: 1, resultByteLength: 1, submission: "{}" }; });
198
+
199
+ await assert.rejects(() => __testing.executeReviewCaptureOperation({ lineageId, collectBinding: "{" }, process.cwd(), native), /collectBinding is not valid JSON/);
200
+ const stale = await __testing.executeReviewCaptureOperation({ lineageId, collectBinding: JSON.stringify(materializeInput("other")) }, process.cwd(), native);
201
+ const duplicate = await __testing.executeReviewCaptureOperation({ lineageId, collectBinding: JSON.stringify(input) }, process.cwd(), {
202
+ targetStatus: async () => { statusCalls += 1; return status(lineageId, [input, input]); },
203
+ } as unknown as NativeReviewCli);
204
+ assert.equal(stale.outcome, "capture-binding-rejected");
205
+ assert.equal(duplicate.outcome, "capture-binding-rejected");
206
+
207
+ const missingLineageToken: ReviewCollectInputV3 = { ...input, arguments: input.arguments.filter((argument) => argument.name !== "lineage") };
208
+ const missingTargetToken: ReviewCollectInputV3 = { ...input, arguments: input.arguments.filter((argument) => argument.name !== "target") };
209
+ const mismatchedLineageToken: ReviewCollectInputV3 = {
210
+ ...input,
211
+ arguments: input.arguments.map((argument) => argument.name === "lineage" ? { ...argument, value: "other-lineage" } : argument),
212
+ };
213
+ const mismatchedTargetToken: ReviewCollectInputV3 = {
214
+ ...input,
215
+ arguments: input.arguments.map((argument) => argument.name === "target" ? { ...argument, value: `sha256:${"d".repeat(64)}` } : argument),
216
+ };
217
+ const missingAuthorityLineage = { ...status(lineageId, [input]), authority: undefined } as ReviewStatusV3;
218
+ const emptyAuthorityLineage = {
219
+ ...status(lineageId, [input]),
220
+ authority: { ...status(lineageId, [input]).authority!, lineageId: "" },
221
+ } as unknown as ReviewStatusV3;
222
+ const missingStatusTarget = { ...status(lineageId, [missingTargetToken]), targetIdentity: undefined } as unknown as ReviewStatusV3;
223
+ const emptyStatusTarget = { ...status(lineageId, [input]), targetIdentity: "" } as unknown as ReviewStatusV3;
224
+ for (const [offeredInput, currentStatus] of [
225
+ [missingLineageToken, status(lineageId, [missingLineageToken])],
226
+ [missingTargetToken, status(lineageId, [missingTargetToken])],
227
+ [mismatchedLineageToken, status(lineageId, [mismatchedLineageToken])],
228
+ [mismatchedTargetToken, status(lineageId, [mismatchedTargetToken])],
229
+ [input, missingAuthorityLineage],
230
+ [input, emptyAuthorityLineage],
231
+ [missingTargetToken, missingStatusTarget],
232
+ [input, emptyStatusTarget],
233
+ ] as const) {
234
+ const rejected = await __testing.executeReviewCaptureOperation(
235
+ { lineageId, collectBinding: JSON.stringify(offeredInput), reviewerRunAcknowledged: true },
236
+ process.cwd(),
237
+ { targetStatus: async () => currentStatus } as unknown as NativeReviewCli,
238
+ );
239
+ assert.equal(rejected.outcome, "capture-binding-rejected");
240
+ }
241
+ assert.equal(launches, 0);
242
+ assert.equal(statusCalls, 2);
243
+ });
244
+
245
+ test("forecast spends zero and acknowledgement runs exactly one materialize reviewer", async (t) => {
246
+ t.after(() => __testing.setReviewHostRelayRunnerForTesting());
247
+ const lineageId = "forecast-lineage";
248
+ const input = materializeInput(lineageId);
249
+ let statusCalls = 0;
250
+ let launches = 0;
251
+ const native = { targetStatus: async () => { statusCalls += 1; return status(lineageId, [input]); } } as unknown as NativeReviewCli;
252
+ __testing.setReviewHostRelayRunnerForTesting(async () => { launches += 1; return { promptByteLength: 1, resultByteLength: 1, submission: "{}" }; });
253
+
254
+ const forecast = await capture(lineageId, input, native);
255
+ const acknowledged = await capture(lineageId, input, native, { reviewerRunAcknowledged: true });
256
+ assert.equal(forecast.outcome, "reviewer-model-run-forecast");
257
+ assert.equal((forecast.cost_forecast as { model_runs: number }).model_runs, 1);
258
+ assert.equal(acknowledged.status, "captured");
259
+ assert.equal(launches, 1);
260
+ assert.equal(statusCalls, 2);
261
+ });
262
+
263
+ test("correction-plan preserves exact provider tokens and bounds without a follow-up lifecycle call", async () => {
264
+ const lineageId = "correction-plan";
265
+ const input = correctionPlanInput(lineageId);
266
+ let statusCalls = 0;
267
+ const requests: Array<{ argumentTokens: readonly string[]; correctionLines: number }> = [];
268
+ const native = {
269
+ targetStatus: async () => { statusCalls += 1; return status(lineageId, [input]); },
270
+ captureCorrectionPlan: async (request: { argumentTokens: readonly string[]; correctionLines: number }) => {
271
+ requests.push(request);
272
+ return decodeReviewLastEventClosureV1(closure("review.capture-correction-plan", lineageId));
273
+ },
274
+ } as unknown as NativeReviewCli;
275
+ const missing = await capture(lineageId, input, native);
276
+ const outOfBounds = await capture(lineageId, input, native, { correctionLines: 4 });
277
+ const completed = await capture(lineageId, input, native, { correctionLines: 2 });
278
+ assert.equal(missing.outcome, "correction-lines-required");
279
+ assert.equal(outOfBounds.outcome, "capture-binding-rejected");
280
+ assert.equal(completed.status, "closed");
281
+ assert.equal(requests.length, 1);
282
+ assert.deepEqual(requests[0]!.argumentTokens, input.submission!.argumentTokens);
283
+ assert.equal(requests[0]!.correctionLines, 2);
284
+ assert.equal(statusCalls, 3);
285
+ });
286
+
287
+ test("refuter and validation each execute one self-contained provider vector", async () => {
288
+ for (const operation of ["review.capture-refuter", "review.capture-validation"] as const) {
289
+ const lineageId = `role-${operation}`;
290
+ const input = roleInput(lineageId, operation);
291
+ const requests: Array<{ captureOperation: string; argumentTokens: readonly string[] }> = [];
292
+ const native = {
293
+ targetStatus: async () => status(lineageId, [input]),
294
+ captureProviderRole: async (request: { captureOperation: string; argumentTokens: readonly string[] }) => {
295
+ requests.push(request);
296
+ return { schema: "gentle-ai.review-provider-role-capture/v1", lineageId, targetIdentity: SHA, role: operation, captured: true };
297
+ },
298
+ } as unknown as NativeReviewCli;
299
+ const result = await capture(lineageId, input, native);
300
+ assert.equal(result.status, "captured");
301
+ assert.equal(requests.length, 1);
302
+ assert.equal(requests[0]!.captureOperation, operation);
303
+ assert.deepEqual(requests[0]!.argumentTokens, input.arguments.map((argument) => argument.token!));
304
+ }
305
+ });
306
+
307
+ test("terminal capture closes directly, nonterminal capture does not auto-follow, and unknown mutation reconciles once", async (t) => {
308
+ t.after(() => __testing.setReviewHostRelayRunnerForTesting());
309
+ const lineageId = "closure-behavior";
310
+ const input = materializeInput(lineageId);
311
+ let statusCalls = 0;
312
+ const native = { targetStatus: async () => { statusCalls += 1; return status(lineageId, [input]); } } as unknown as NativeReviewCli;
313
+ __testing.setReviewHostRelayRunnerForTesting(async () => ({ promptByteLength: 1, resultByteLength: 1, submission: JSON.stringify(closure("review/capture-result", lineageId)) }));
314
+ const terminal = await capture(lineageId, input, native, { reviewerRunAcknowledged: true });
315
+ assert.equal(terminal.status, "closed");
316
+ assert.equal(statusCalls, 1, "terminal closure performs no post-success STATUS");
317
+
318
+ statusCalls = 0;
319
+ let captureCalls = 0;
320
+ const correction = correctionPlanInput(lineageId);
321
+ const ambiguousNative = {
322
+ targetStatus: async () => { statusCalls += 1; return status(lineageId, [correction]); },
323
+ captureCorrectionPlan: async () => {
324
+ captureCalls += 1;
325
+ throw Object.assign(new Error("response lost"), { mutationOutcome: "unknown", nextAction: "review.status" });
326
+ },
327
+ } as unknown as NativeReviewCli;
328
+ const reconciled = await capture(lineageId, correction, ambiguousNative, { correctionLines: 2 });
329
+ assert.equal(reconciled.status, "reconciled");
330
+ assert.equal(captureCalls, 1);
331
+ assert.equal(statusCalls, 2, "only initial STATUS plus one ambiguity reconciliation");
332
+ });
333
+
334
+ test("unknown-capture reconciliation validates the target-scoped lineage without replay", async () => {
335
+ const calls: Array<Record<string, unknown>> = [];
336
+ const native = {
337
+ targetStatus: async (request: Record<string, unknown>) => {
338
+ calls.push(request);
339
+ assert.equal(request.lineageId, "reconcile-lineage");
340
+ return { targetIdentity: SHA, authority: { lineageId: "reconcile-lineage" } } as ReviewStatusV3;
341
+ },
342
+ } as unknown as NativeReviewCli;
343
+ const result = await reconcileUnknownReviewLastEventCapture(native, "/repo", { lineageId: "reconcile-lineage", targetIdentity: SHA });
344
+ await reconcileUnknownReviewLastEventCapture(native, "/repo", { lineageId: "reconcile-lineage", targetIdentity: SHA }, { baseRef: "refs/heads/main", committedOnly: true });
345
+ assert.deepEqual(calls, [{ cwd: "/repo", lineageId: "reconcile-lineage" }, { cwd: "/repo", lineageId: "reconcile-lineage", baseRef: "refs/heads/main", committedOnly: true }]);
346
+ assert.equal(result.targetIdentity, SHA);
347
+ let launches = 0;
348
+ const strictNative = new nativeReviewCliModule.NativeReviewCliV216(async () => { launches += 1; throw new Error("must not launch"); }, "/package/.gentle-ai/gentle-ai");
349
+ for (const selector of [{ committedOnly: true }, { baseRef: "refs/heads/main" }, { baseRef: "refs/heads/main", committedOnly: false }] as unknown as readonly Parameters<typeof reconcileUnknownReviewLastEventCapture>[3][]) await assert.rejects(() => reconcileUnknownReviewLastEventCapture(strictNative, "/repo", { lineageId: "reconcile-lineage", targetIdentity: SHA }, selector));
350
+ assert.equal(launches, 0);
351
+ });
352
+
353
+ // One approved terminal closure carrying the exact continuation the provider
354
+ // renders: closed positional arguments, the single approved precondition, and a
355
+ // binding that names the candidate being burned.
356
+ function approvedClosureWithAcknowledgement(): Record<string, unknown> {
357
+ const lineageId = "review-acknowledgement-fixture";
358
+ const body = closure("review/capture-result", lineageId);
359
+ body.acknowledgement = {
360
+ operation: "review.acknowledge-approved",
361
+ command: "gentle-ai review acknowledge-approved",
362
+ arguments: [
363
+ { name: "cwd", value: "/repo", token: "--cwd=/repo" },
364
+ { name: "lineage", value: lineageId, token: `--lineage=${lineageId}` },
365
+ { name: "target", value: SHA, token: `--target=${SHA}` },
366
+ { name: "expected-revision", value: SHA, token: `--expected-revision=${SHA}` },
367
+ { name: "token", value: "a".repeat(64), token: `--token=${"a".repeat(64)}` },
368
+ ],
369
+ preconditions: [{ name: "state", value: "approved" }],
370
+ binding: { lineage_id: lineageId, revision: SHA, target_identity: SHA },
371
+ };
372
+ return body;
373
+ }
374
+
375
+ test("the approved acknowledgement fixture decodes as a runnable continuation", () => {
376
+ const decoded = decodeReviewLastEventClosureV1(approvedClosureWithAcknowledgement());
377
+ assert.equal(decoded.acknowledgement?.operation, "review.acknowledge-approved");
378
+ assert.equal(decoded.acknowledgementUndecodable, undefined);
379
+ });
380
+
381
+ test("an approved acknowledgement must prove the target its relayed token burns", () => {
382
+ const closure = approvedClosureWithAcknowledgement();
383
+ const acknowledgement = closure.acknowledgement as Record<string, unknown>;
384
+ const args = (acknowledgement.arguments as Record<string, unknown>[]).map((argument) => ({ ...argument }));
385
+ // The relayed token now names a different candidate than the binding does.
386
+ const decoyTarget = `sha256:${"b".repeat(64)}`;
387
+ args[2] = { name: "target", value: decoyTarget, token: `--target=${decoyTarget}` };
388
+ acknowledgement.arguments = args;
389
+ assert.throws(
390
+ () => decodeReviewLastEventClosureV1(closure),
391
+ /acknowledgement target argument does not match its binding/,
392
+ "a relayed target the binding does not commit to must be refused",
393
+ );
394
+ });
395
+
396
+ test("an unreadable acknowledgement degrades the continuation, never the approval", () => {
397
+ const closure = approvedClosureWithAcknowledgement();
398
+ // One extra provider-side argument is enough to miss the closed shape.
399
+ const acknowledgement = closure.acknowledgement as Record<string, unknown>;
400
+ acknowledgement.arguments = [
401
+ ...(acknowledgement.arguments as unknown[]),
402
+ { name: "future", value: "1", token: "--future=1" },
403
+ ];
404
+ const decoded = decodeReviewLastEventClosureV1(closure);
405
+ assert.equal(decoded.state, "approved", "the approval outcome must survive an unreadable continuation");
406
+ assert.equal(decoded.acknowledgement, undefined, "an unreadable continuation must not be offered as runnable");
407
+ assert.equal(decoded.acknowledgementUndecodable, true, "the host must be told the continuation exists and could not be read");
408
+ });
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
- import { readFileSync } from "node:fs";
3
- import { join } from "node:path";
2
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
+ import { dirname, join, relative, resolve, sep } from "node:path";
4
4
  import test from "node:test";
5
5
  import { REVIEW_LENS_PARITY_PATTERNS } from "./support/review-lens-parity.ts";
6
6
 
@@ -14,8 +14,6 @@ const REVIEW_LENSES = [
14
14
  "assets/agents/review-reliability.md",
15
15
  ] as const;
16
16
  const JUDGES = ["assets/agents/jd-judge-a.md", "assets/agents/jd-judge-b.md"] as const;
17
- const REFUTER = "assets/agents/review-refuter.md";
18
- const VALIDATOR = "assets/agents/review-validator.md";
19
17
  const FIX_AGENT = "assets/agents/jd-fix-agent.md";
20
18
  const JD_SKILL = "skills/judgment-day/SKILL.md";
21
19
  const JD_PROMPTS = "skills/judgment-day/references/prompts-and-formats.md";
@@ -87,7 +85,7 @@ const FIX_PATTERNS = [
87
85
  /Do not add findings, alter frozen claims, authorize transitions, deliver, publish, or start another actor\./,
88
86
  ] as const;
89
87
 
90
- test("canonical contract defines compact risk, causal admission, correction, CAS, compatibility, and gates", () => {
88
+ test("canonical contract defines compact risk, causal admission, correction, CAS, compatibility, and the delivery boundary", () => {
91
89
  const content = read(CANONICAL);
92
90
  assertMatches(CANONICAL, content, [
93
91
  /start -> finalize -> validate/,
@@ -103,29 +101,25 @@ test("canonical contract defines compact risk, causal admission, correction, CAS
103
101
  /original budget/i,
104
102
  /frozen findings and genesis scope/i,
105
103
  /content-derived revisions, compare-and-swap replacement, exact retry idempotency/i,
106
- /graph-v1 ordinary lineages remain readable and gate-validatable but reject new mutation/i,
104
+ /graph-v1 ordinary lineages remain readable for compatibility but reject new mutation/i,
107
105
  /Legacy graph bundle export\/import is retired/i,
108
106
  /Judgment Day remains mutable on graph-v1/i,
109
- /reloads authority and re-derives target\/publication evidence before allow/i,
110
- /one one-shot authorization for the exact subsequent command/i,
111
- /gentle-ai\.review-integration\/v1/i,
112
- /durable hook\/native-validation transaction/i,
113
- /Pi-owned `review-publication-gate` module isolates command projection and publication revalidation/i,
107
+ /--agent=pi --materialize=true/,
108
+ /provider-owned submission form/i,
109
+ /self-contained authority-advancing vectors/i,
110
+ /Commit, push, pull-request creation, and release creation are not RDD gates/i,
111
+ /Review outcomes and receipt state are informational and never authorize, consume, rewrite, or block a Bash delivery command/i,
112
+ /Pi does not inspect RDD mode or native authority for those commands/i,
113
+ /Review transactions, validation, and SDD never perform delivery commands themselves/i,
114
114
  /local orchestrator and same-user process are trusted/i,
115
115
  /reviewer and validator outputs remain semantically untrusted/i,
116
116
  /do not report.*trusted local orchestrator.*security finding/i,
117
117
  /untrusted repository content.*malformed inputs.*stale authority.*path drift.*external callers/i,
118
118
  ...JUDGMENT_DAY_PATTERNS,
119
119
  ]);
120
- assert.match(read(README), /Trust boundary:[\s\S]*separately privileged signer\/service/);
121
- assert.doesNotMatch(read(README), /Known limitation:[\s\S]*runtime-owned child-agent identity\/attestation/);
122
- assert.match(read(README), /split fetch\/push[\s\S]*unsupported[\s\S]*upstream[\s\S]*base-ref/i);
123
- assert.match(read(README), /Residual gap \(separate follow-up\): native first-push authorization remains unsupported until Pi has a persisted explicit advertised-base source\./);
124
- const lifecycleSpec = read("openspec/specs/review-transaction/spec.md");
125
- assert.match(lifecycleSpec, /split fetch\/push[\s\S]*upstream contract limitation/i);
126
- assert.match(lifecycleSpec, /allow response MUST return the exact requested gate/i);
127
- assert.match(lifecycleSpec, /non-authorizing denial MAY return an empty gate[\s\S]*pre_pr_boundary/i);
128
- assert.match(lifecycleSpec, /one aggregate bash-time deadline/i);
120
+ assert.match(read(README), /Review outcomes and receipt state are informational; commit, push, pull-request, and release delivery follow ordinary repository policy\./);
121
+ assert.doesNotMatch(read(README), /one one-shot authorization for the exact command/i);
122
+ assert.doesNotMatch(read(README), /review-publication-gate/i);
129
123
  });
130
124
 
131
125
  for (const path of REVIEW_LENSES) {
@@ -135,6 +129,69 @@ for (const path of REVIEW_LENSES) {
135
129
  });
136
130
  }
137
131
 
132
+ function packagePaths(): string[] {
133
+ const manifest = JSON.parse(read("package.json")) as { files?: unknown };
134
+ if (!Array.isArray(manifest.files)) throw new Error("package.json must declare package files");
135
+ return manifest.files.map((entry) => {
136
+ if (typeof entry !== "string") throw new Error("package.json files entries must be strings");
137
+ return entry.replace(/\/+$/, "");
138
+ });
139
+ }
140
+
141
+ function projectPath(absolutePath: string): string | undefined {
142
+ const path = relative(ROOT, absolutePath);
143
+ if (path === "" || path === ".." || path.startsWith(`..${sep}`)) return undefined;
144
+ return path.split(sep).join("/");
145
+ }
146
+
147
+ function isPackagedFile(path: string, packageRoots: readonly string[]): boolean {
148
+ const absolutePath = resolve(ROOT, path);
149
+ const projectRelativePath = projectPath(absolutePath);
150
+ if (!projectRelativePath || !existsSync(absolutePath) || !statSync(absolutePath).isFile()) return false;
151
+ return packageRoots.some((root) => projectRelativePath === root || projectRelativePath.startsWith(`${root}/`));
152
+ }
153
+
154
+ function packagedOrdinaryReviewLenses(): string[] {
155
+ const packageRoots = packagePaths();
156
+ return readdirSync(join(ROOT, "assets", "agents"), { withFileTypes: true })
157
+ .filter((entry) => entry.isFile() && entry.name.startsWith("review-") && entry.name.endsWith(".md"))
158
+ .map((entry) => `assets/agents/${entry.name}`)
159
+ .filter((path) => isPackagedFile(path, packageRoots))
160
+ .sort();
161
+ }
162
+
163
+ const MARKDOWN_FILE_REFERENCE = /(?:https?:\/\/[^\s)`\]]+|(?:\.{1,2}\/)?[A-Za-z0-9][A-Za-z0-9_./-]*)\.md\b/g;
164
+
165
+ function assertMarkdownFileDependenciesResolve(path: string, content: string, packageRoots: readonly string[]): void {
166
+ const sourceDirectory = dirname(join(ROOT, path));
167
+ const unresolved = [...content.matchAll(MARKDOWN_FILE_REFERENCE)]
168
+ .map((match) => match[0])
169
+ .filter((reference) => {
170
+ if (reference.startsWith("http://") || reference.startsWith("https://")) return true;
171
+ return ![resolve(ROOT, reference), resolve(sourceDirectory, reference)].some((candidate) => {
172
+ const candidatePath = projectPath(candidate);
173
+ return candidatePath !== undefined && isPackagedFile(candidatePath, packageRoots);
174
+ });
175
+ });
176
+ assert.deepEqual(unresolved, [], `${path} names unavailable Markdown dependencies: ${unresolved.join(", ")}`);
177
+ }
178
+
179
+ test("packaged ordinary review lenses resolve their Markdown file dependencies", () => {
180
+ const packageRoots = packagePaths();
181
+ const lenses = packagedOrdinaryReviewLenses();
182
+ assert.ok(lenses.length > 0, "package must ship at least one ordinary review lens");
183
+ for (const path of lenses) assertMarkdownFileDependenciesResolve(path, read(path), packageRoots);
184
+ });
185
+
186
+ test("ordinary review dependencies allow packaged files and reject unresolved paths", () => {
187
+ const packageRoots = packagePaths();
188
+ assertMarkdownFileDependenciesResolve("assets/agents/review-risk.md", "Sources: docs/native-authority-architecture.md", packageRoots);
189
+ assert.throws(
190
+ () => assertMarkdownFileDependenciesResolve("assets/agents/review-risk.md", "Sources: docs/nonexistent/security.md", packageRoots),
191
+ /unavailable Markdown dependencies: docs\/nonexistent\/security\.md/,
192
+ );
193
+ });
194
+
138
195
  test("ordinary lens prompts contain the literal compact-v2 native result envelope", () => {
139
196
  const expectedLenses = ["review-risk", "review-resilience", "review-readability", "review-reliability"];
140
197
  for (const [index, path] of REVIEW_LENSES.entries()) {
@@ -196,29 +253,15 @@ test("risk lens distinguishes trusted orchestration from concrete boundary bypas
196
253
  assert.match(content, /untrusted repository content.*malformed inputs.*stale authority.*path drift.*external callers/i);
197
254
  });
198
255
 
199
- test("ordinary refuter is one complete read-only inferential batch with concrete proof", () => {
200
- const content = read(REFUTER);
201
- assertMatches(REFUTER, content, [
202
- /Receive the complete inferential-severe frozen-row list once/,
203
- /`gentle-ai\.refuter-result-batch\/v1`/,
204
- /`request_hash`[\s\S]*`finding_id`/,
205
- /`proof_refs`[\s\S]*`changed-hunk:`[\s\S]*`candidate-created-path:`[\s\S]*`differential-test:`[\s\S]*`before-after:`/,
206
- /independent concrete refuter proof is valid and need not repeat reviewer `proof_refs`/,
207
- /Use `inconclusive` when the supplied evidence supports neither `refuted` nor `corroborated`/,
208
- /Do not create findings, alter frozen claims, request fixes, launch actors, persist authority, or repeat/,
209
- ]);
210
- });
211
-
212
- test("targeted validator checks only original criteria and correction regression without expanding scope", () => {
213
- const content = read(VALIDATOR);
214
- assertMatches(VALIDATOR, content, [
215
- /original-criteria proof/,
216
- /correction-regression proof/,
217
- /Validate the original criteria and correction regression only/,
218
- /Never expand paths, IDs, untracked scope, acceptance criteria, or correction purpose/,
219
- /empty `fix_caused_findings` array/,
220
- /Do not request another fix or attempt, launch actors, persist authority, or repeat yourself/,
221
- ]);
256
+ test("the Pi-owned adversarial role agents are retired: roles execute through Go-owned pi processes", () => {
257
+ // gentle-pi#311 P5: the refuter and targeted validator are no longer
258
+ // Pi-authored actors. The provider renders self-contained
259
+ // review.capture-refuter / review.capture-validation vectors; executing
260
+ // them makes Go materialize the role prompt, spawn its own locked-down pi
261
+ // subprocess, and admit the raw verdict.
262
+ for (const retired of ["assets/agents/review-refuter.md", "assets/agents/review-validator.md"]) {
263
+ assert.throws(() => read(retired), `${retired} must be deleted`);
264
+ }
222
265
  });
223
266
 
224
267
  for (const path of JUDGES) {
@@ -267,33 +310,36 @@ test("Judgment Day skill and prompts preserve bounded fix and re-judgment author
267
310
  assertMatches(FIX_AGENT, read(FIX_AGENT), FIX_PATTERNS);
268
311
  });
269
312
 
270
- test("orchestrator, skill, and README agree on compact facade and compatibility", () => {
313
+ test("orchestrator, injected skill, and README defer RDD lifecycle ownership to Gentle AI", () => {
314
+ const boundary = "Gentle AI dynamically supplies runtime-specific RDD instructions via generated Pi APPEND_SYSTEM composition. Follow only those exact native instructions; if absent or unsupported, this package does not invent or fall back.";
315
+ const orchestrator = union(ORCHESTRATOR);
316
+ assert.ok(orchestrator.includes(boundary), "orchestrator must carry the sole static ownership boundary");
317
+
271
318
  for (const [label, content] of [
272
- ["orchestrator", union(ORCHESTRATOR)],
273
319
  [GENTLE_SKILL, read(GENTLE_SKILL)],
274
320
  [README, read(README)],
275
321
  ] as const) {
276
322
  assertMatches(label, content, [
277
- /start -> finalize -> validate/,
278
- /`evidence_class`[\s\S]*`causal_disposition`/,
279
- /one correction transaction/i,
280
- /(?:graph-v1|legacy)[\s\S]*(?:read-only|reject mutation)/i,
281
- /Judgment Day[\s\S]*(?:explicit|separate)/i,
282
- /(?:one-shot|one exact one-shot)[\s\S]*(?:bash time|bash-time)/i,
323
+ /Gentle AI dynamically supplies runtime-specific RDD instructions/i,
324
+ /(?:sole lifecycle authority|does not define an RDD lifecycle)/i,
283
325
  ]);
284
326
  }
327
+
328
+ for (const [label, content] of [
329
+ ["orchestrator", orchestrator],
330
+ [GENTLE_SKILL, read(GENTLE_SKILL)],
331
+ ] as const) {
332
+ assert.doesNotMatch(content, /start -> finalize -> validate|INSPECT before START|next_transition|review\.capture-result/i, label);
333
+ }
285
334
  });
286
335
 
287
- test("README documents the exact native pairing and authority-preserving rollback boundary", () => {
336
+ test("README documents the dynamic runtime authority boundary without an old package route", () => {
288
337
  const content = read(README);
289
- assert.match(content, /package-local Gentle AI v2\.1\.11 executable/i);
290
- assert.match(content, /independently hashes it[\s\S]*negotiates `gentle-ai\.review-integration\/v1`/i);
291
- assert.match(content, /Capabilities are cached by that executable digest/i);
292
- assert.match(content, /Every START, target status, FINALIZE, validate, and BIND-SDD request passes the same contract identifier/i);
293
- assert.match(content, /rollback MUST preserve every native store and receipt/);
294
- assert.match(content, /MUST NOT run a downgraded binary/i);
295
- assert.match(content, /existing branch.*advertised commit equals.*old object/is);
296
- assert.match(content, /never guesses? a base.*upstream.*default branch.*nearest ancestor/is);
338
+ assert.match(content, /Gentle AI dynamically supplies runtime-specific RDD instructions/i);
339
+ assert.match(content, /does not define an RDD lifecycle/i);
340
+ assert.doesNotMatch(content, /New ordinary review uses compact `gentle_review` `start -> finalize -> validate`\./);
341
+ assert.match(content, /Dangerous-command safety remains independent and authoritative/);
342
+ assert.match(content, /Project and user overrides may shadow a package asset/);
297
343
  });
298
344
 
299
345
  test("managed contracts retain no fresh lifecycle review directive", () => {