gentle-pi 2.2.0 → 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 (130) hide show
  1. package/README.md +51 -80
  2. package/assets/agents/review-readability.md +0 -2
  3. package/assets/agents/review-reliability.md +0 -2
  4. package/assets/agents/review-resilience.md +0 -2
  5. package/assets/agents/review-risk.md +0 -2
  6. package/assets/agents/sdd-apply.md +5 -3
  7. package/assets/agents/sdd-proposal.md +2 -0
  8. package/assets/agents/sdd-research.md +54 -0
  9. package/assets/agents/sdd-status.md +4 -4
  10. package/assets/agents/sdd-tasks.md +2 -3
  11. package/assets/agents/sdd-verify.md +21 -1
  12. package/assets/chains/sdd-full.chain.md +1 -1
  13. package/assets/chains/sdd-verify.chain.md +1 -1
  14. package/assets/orchestrator-delegation.md +54 -248
  15. package/assets/orchestrator-memory.md +2 -0
  16. package/assets/orchestrator.md +22 -38
  17. package/assets/sdd-orchestrator-workflow.md +30 -37
  18. package/assets/support/sdd-status-contract.md +7 -7
  19. package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
  20. package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
  21. package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
  22. package/docs/native-authority-architecture.md +9 -11
  23. package/docs/review-integration.md +27 -373
  24. package/extensions/ask-user-choice.ts +151 -0
  25. package/extensions/gentle-ai.ts +1778 -3673
  26. package/extensions/quiet-tools.ts +515 -32
  27. package/extensions/sdd-init.ts +4 -8
  28. package/lib/gentle-ai-renderer.ts +70 -0
  29. package/lib/model-routing-authority.ts +133 -0
  30. package/lib/native-review-cli.ts +372 -940
  31. package/lib/opaque-pi-reviewer-adapter.ts +284 -0
  32. package/lib/review-candidate-view.ts +341 -132
  33. package/lib/review-host-relay.ts +210 -68
  34. package/lib/review-integration-v2.ts +839 -307
  35. package/lib/review-last-event-controller.ts +35 -0
  36. package/lib/sdd-preflight.ts +177 -66
  37. package/lib/sdd-status.ts +66 -111
  38. package/lib/terminal-theme.ts +1 -1
  39. package/package.json +83 -82
  40. package/runtime/gentle-ai-binary.mjs +1 -1
  41. package/runtime/native-review-cli.mjs +322 -890
  42. package/runtime/review-integration-v2.mjs +789 -257
  43. package/runtime/review-relay-contract.mjs +1 -1
  44. package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +4 -5
  45. package/scripts/gentle-ai-installer.mjs +75 -22
  46. package/scripts/maintainer/provider-relay-matrix.mjs +195 -11
  47. package/scripts/test-packed-runner.mjs +4 -7
  48. package/scripts/verify-package-files.mjs +12 -13
  49. package/skills/_shared/review-ledger-contract.md +8 -14
  50. package/skills/chained-pr/SKILL.md +3 -0
  51. package/skills/cognitive-doc-design/SKILL.md +1 -1
  52. package/skills/comment-writer/SKILL.md +1 -1
  53. package/skills/gentle-ai/SKILL.md +7 -74
  54. package/skills/judgment-day/SKILL.md +5 -7
  55. package/skills/rdd-defect-workflow/SKILL.md +3 -3
  56. package/skills/release/SKILL.md +3 -3
  57. package/skills/skill-registry/SKILL.md +1 -1
  58. package/skills/work-unit-commits/SKILL.md +3 -1
  59. package/tests/artifact-language.test.ts +24 -11
  60. package/tests/ask-user-choice.test.ts +264 -0
  61. package/tests/codegraph-tools.test.ts +3 -3
  62. package/tests/crosslane/cross-lane.mjs +15 -1168
  63. package/tests/delegated-key-learnings-contract.test.ts +8 -6
  64. package/tests/devbinary/native-review-parity.devtest.ts +167 -250
  65. package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
  66. package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
  67. package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
  68. package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
  69. package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
  70. package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
  71. package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
  72. package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
  73. package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
  74. package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
  75. package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
  76. package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
  77. package/tests/gentle-ai-binary.test.ts +83 -13
  78. package/tests/gentle-ai-dev-binary.test.ts +51 -228
  79. package/tests/gentle-ai-installer.test.ts +137 -47
  80. package/tests/gentle-ai.test.ts +820 -131
  81. package/tests/gentle-theme.test.ts +133 -0
  82. package/tests/maintainer/provider-relay.maintest.ts +340 -4
  83. package/tests/model-routing-authority.test.ts +257 -0
  84. package/tests/native-review-capability-contract.test.ts +28 -1
  85. package/tests/native-review-cli.test.ts +586 -962
  86. package/tests/native-review-consent.test.ts +150 -66
  87. package/tests/native-review-parity-runtime.test.ts +80 -359
  88. package/tests/native-review-parity.test.ts +674 -925
  89. package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
  90. package/tests/orchestrator-budget.test.ts +132 -88
  91. package/tests/orchestrator-rdd-ownership.test.ts +103 -0
  92. package/tests/package-manifest.test.ts +37 -35
  93. package/tests/provider-defect-handoff.test.ts +15 -118
  94. package/tests/quiet-tool-rendering.test.ts +1055 -28
  95. package/tests/review-authority-recovery-docs.test.ts +1 -2
  96. package/tests/review-candidate-view.test.ts +409 -3
  97. package/tests/review-compact-contract.test.ts +4 -30
  98. package/tests/review-controller-lock-status.test.ts +2 -2
  99. package/tests/review-controller-native-recovery.test.ts +363 -1450
  100. package/tests/review-controller-native-routing.test.ts +1251 -5642
  101. package/tests/review-controller-retired-ops.test.ts +1 -1
  102. package/tests/review-controller-workspace-root.test.ts +222 -71
  103. package/tests/review-controller.test.ts +26 -816
  104. package/tests/review-corrected-finalize-binding.test.ts +112 -153
  105. package/tests/review-dispatch-hydration-gap.test.ts +1 -53
  106. package/tests/review-gate.test.ts +0 -45
  107. package/tests/review-host-relay-restart-parity.test.ts +360 -0
  108. package/tests/review-host-relay-routing.test.ts +117 -82
  109. package/tests/review-host-relay.test.ts +242 -8
  110. package/tests/review-integration-v2-forward.test.ts +576 -553
  111. package/tests/review-integration-v2.test.ts +239 -165
  112. package/tests/review-last-event-closure.test.ts +408 -0
  113. package/tests/review-ledger-contract.test.ts +97 -35
  114. package/tests/review-recovered-lineage-routing.test.ts +0 -47
  115. package/tests/review-relay-transport-agent.test.ts +119 -46
  116. package/tests/review-snapshot.test.ts +3 -2
  117. package/tests/runtime-harness.mjs +361 -196
  118. package/tests/sdd-agent-tools.test.ts +36 -0
  119. package/tests/sdd-preflight.test.ts +81 -15
  120. package/tests/sdd-status.test.ts +109 -110
  121. package/tests/skill-collision-prefixes.test.ts +5 -8
  122. package/tests/writer-edit-surface-scope.test.ts +230 -0
  123. package/themes/Gentleman-Cute.json +94 -0
  124. package/themes/Gentleman-Sexy.json +92 -0
  125. package/lib/git-commit-transaction.ts +0 -861
  126. package/runtime/git-commit-transaction.mjs +0 -862
  127. package/scripts/run-git-commit-transaction.mjs +0 -35
  128. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
  129. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
  130. package/tests/git-commit-transaction.test.ts +0 -530
@@ -91,8 +91,7 @@ function recoveredStatus(lineageId: string, materialize: boolean): ReviewStatusV
91
91
  contract: "gentle-ai.review-integration/v2",
92
92
  applicability: "current_target",
93
93
  authority: { version: "compact-v2", lineageId, state: "reviewer_results_required", generation: 2, revision: SHA },
94
- receipt: { status: "expected_missing" },
95
- action: "finalize",
94
+ action: "stop",
96
95
  replayability: "not_replayable",
97
96
  targetIdentity: SHA,
98
97
  projection: {
@@ -111,7 +110,7 @@ function recoveredStatus(lineageId: string, materialize: boolean): ReviewStatusV
111
110
  },
112
111
  candidates: [],
113
112
  nextTransition: { kind: "collect", reasonCode: "reviewer_results_required", collect: { inputs: [collectInput(lineageId, materialize)] } },
114
- raw: { schema: "gentle-ai.review-integration.status/v5", action: "finalize", lineage_id: lineageId },
113
+ raw: { schema: "gentle-ai.review-integration.status/v5", action: "stop", lineage_id: lineageId },
115
114
  } as unknown as ReviewStatusV3;
116
115
  }
117
116
 
@@ -119,18 +118,21 @@ function recoveredStatus(lineageId: string, materialize: boolean): ReviewStatusV
119
118
  * Mirrors the MEASURED live provider: the materialize-marked relay slot is
120
119
  * offered only when the caller asks for the pi agent.
121
120
  */
122
- function transportAwareNative(options: { refusePiAgent?: boolean } = {}): { native: NativeReviewCli; agents: Array<string | undefined> } {
121
+ function transportAwareNative(options: { refusalCode?: string } = {}): {
122
+ native: NativeReviewCli;
123
+ agents: Array<string | undefined>;
124
+ } {
123
125
  const agents: Array<string | undefined> = [];
124
126
  const native = {
125
127
  targetStatus: async (request: { lineageId?: string; agent?: string }) => {
126
128
  agents.push(request.agent);
127
- if (request.agent === "pi" && options.refusePiAgent === true) {
129
+ if (request.agent === "pi" && options.refusalCode !== undefined) {
128
130
  throw new NativeReviewIntegrationError({
129
131
  schema: "gentle-ai.review-integration.failure/v2",
130
132
  contract: "gentle-ai.review-integration/v2",
131
133
  operation: "review.status",
132
134
  phase: "pre_native",
133
- code: TRANSPORT_REFUSAL_CODE,
135
+ code: options.refusalCode,
134
136
  message: "supported immutable review runtimes: claude-code, opencode, codex",
135
137
  mutationOutcome: "none",
136
138
  authorityApplicability: "current_target",
@@ -142,15 +144,22 @@ function transportAwareNative(options: { refusePiAgent?: boolean } = {}): { nati
142
144
  }
143
145
  return recoveredStatus(request.lineageId ?? "relay-lineage", request.agent === "pi");
144
146
  },
145
- finalize: async () => { throw new Error("native finalize must not run while reviewer results are outstanding"); },
146
147
  } as unknown as NativeReviewCli;
147
148
  return { native, agents };
148
149
  }
149
150
 
150
- async function runFinalize(cwd: string, native: NativeReviewCli, lineageId: string, input: Record<string, unknown> = { reviewer_run_acknowledged: true }): Promise<Record<string, unknown>> {
151
+ async function runCapture(cwd: string, native: NativeReviewCli, lineageId: string, input: Record<string, unknown> = { reviewerRunAcknowledged: true }): Promise<Record<string, unknown>> {
152
+ return await __testing.executeReviewCaptureOperation(
153
+ { lineageId, collectBinding: JSON.stringify(collectInput(lineageId, true)), ...input },
154
+ cwd, native, undefined, new CandidateViewRegistry(),
155
+ ) as Record<string, unknown>;
156
+ }
157
+
158
+
159
+ async function runStatus(cwd: string, native: NativeReviewCli, lineageId: string): Promise<Record<string, unknown>> {
151
160
  return await __testing.executeReviewControllerOperation(
152
- { operation: "finalize", lineageId, input: JSON.stringify(input) },
153
- cwd, new Map(), native, undefined, undefined, undefined, new CandidateViewRegistry(),
161
+ { operation: "status", lineageId },
162
+ cwd, native, undefined, new CandidateViewRegistry(),
154
163
  ) as Record<string, unknown>;
155
164
  }
156
165
 
@@ -165,19 +174,19 @@ test("the negotiated status asks for the pi agent so the provider offers its mat
165
174
  return { promptByteLength: 128, resultByteLength: 64, submission: '{"admission_decision":"completed"}' };
166
175
  });
167
176
 
168
- const result = await runFinalize(cwd, native, lineageId);
177
+ const result = await runCapture(cwd, native, lineageId);
169
178
 
170
- assert.ok(agents.includes("pi"), "the controller must negotiate STATUS for the pi reviewer transport");
179
+ assert.equal(agents.at(0), "pi", "the successful Pi route starts with pi-bound STATUS negotiation");
180
+ assert.ok(agents.every((agent) => agent === "pi"), "the selected capture performs no post-success agent-less STATUS re-query");
171
181
  assert.equal(relayed.length, 1, "the materialize-marked slot must reach the host relay");
172
182
  assert.ok(relayed[0]!.captureArgumentTokens.includes("--materialize=true"));
173
183
  assert.ok(relayed[0]!.submission !== undefined, "the provider submission drives the completing form");
174
- const hostRelay = result.host_relay as { transport: string; captured_slots: readonly unknown[] } | undefined;
175
- assert.ok(hostRelay !== undefined, "the envelope reports the relay capture");
184
+ const hostRelay = result.host_relay as { transport: string } | undefined;
185
+ assert.ok(hostRelay !== undefined, "the envelope reports the one relay capture");
176
186
  assert.equal(hostRelay.transport, "pi_host_relay");
177
- assert.equal(hostRelay.captured_slots.length, 1);
178
187
  });
179
188
 
180
- test("finalize forecasts the reviewer model run once and spends nothing until it is acknowledged", async (t) => {
189
+ test("capture forecasts the reviewer model run once and spends nothing until it is acknowledged", async (t) => {
181
190
  t.after(() => __testing.setReviewHostRelayRunnerForTesting());
182
191
  const cwd = repository(t);
183
192
  const lineageId = "forecast-lineage";
@@ -188,62 +197,126 @@ test("finalize forecasts the reviewer model run once and spends nothing until it
188
197
  return { promptByteLength: 128, resultByteLength: 64, submission: '{"admission_decision":"completed"}' };
189
198
  });
190
199
 
191
- // An unacknowledged finalize must forecast, never spend.
192
- const forecast = await runFinalize(cwd, native, lineageId, {});
200
+ // An unacknowledged capture must forecast, never spend.
201
+ const forecast = await runCapture(cwd, native, lineageId, {});
193
202
  assert.equal(launches, 0, "no reviewer model run may start before the forecast is acknowledged");
194
203
  assert.equal(forecast.status, "blocked");
195
204
  assert.equal(forecast.outcome, "reviewer-model-run-forecast");
196
205
  assert.equal(forecast.mutation_performed, false);
197
206
  assert.equal(forecast.mutation_outcome, "none");
198
- const cost = forecast.cost_forecast as { transport: string; model_runs: number; lenses: readonly string[]; side_effects: readonly string[] };
207
+ const cost = forecast.cost_forecast as { transport: string; model_runs: number; lenses: readonly string[] };
199
208
  assert.equal(cost.transport, "pi_host_relay");
200
209
  assert.equal(cost.model_runs, 1, "one model run per outstanding lens, forecast once before launch");
201
210
  assert.deepEqual(cost.lenses, ["review-reliability"]);
202
- assert.ok(cost.side_effects.length > 0);
203
- assert.match(String(forecast.reason), /model run/i);
204
- assert.match(String(forecast.next_action), /reviewer_run_acknowledged/);
205
-
206
- // The acknowledged finalize runs exactly the forecast work.
207
- const acknowledged = await runFinalize(cwd, native, lineageId, { reviewer_run_acknowledged: true });
208
- assert.equal(launches, 1, "the acknowledged finalize runs the forecast model run");
209
- assert.equal((acknowledged.host_relay as { captured_slots: readonly unknown[] }).captured_slots.length, 1);
211
+
212
+ // The acknowledged capture runs exactly the forecast work.
213
+ const acknowledged = await runCapture(cwd, native, lineageId, { reviewerRunAcknowledged: true });
214
+ assert.equal(launches, 1, "the acknowledged capture runs the forecast model run");
215
+ assert.equal(acknowledged.status, "captured");
210
216
  });
211
217
 
212
- test("a provider that refuses the pi transport surfaces the typed cause and still returns status", async (t) => {
218
+ test("a provider that refuses the pi transport blocks immediately without an agent-less lifecycle fallback", async (t) => {
213
219
  t.after(() => __testing.setReviewHostRelayRunnerForTesting());
214
220
  const cwd = repository(t);
215
221
  const lineageId = "legacy-transport-lineage";
216
- const { native, agents } = transportAwareNative({ refusePiAgent: true });
222
+ const { native, agents } = transportAwareNative({ refusalCode: TRANSPORT_REFUSAL_CODE });
217
223
  let relayCalls = 0;
218
224
  __testing.setReviewHostRelayRunnerForTesting(async () => {
219
225
  relayCalls += 1;
220
226
  return { promptByteLength: 1, resultByteLength: 1, submission: "{}" };
221
227
  });
222
228
 
223
- const result = await runFinalize(cwd, native, lineageId);
229
+ const result = await runCapture(cwd, native, lineageId);
224
230
 
225
- assert.ok(agents.includes("pi"), "the transport is probed once");
226
- assert.ok(agents.includes(undefined), "a refusal falls back to the agent-less status instead of failing the operation");
227
- assert.equal(relayCalls, 0, "no relay slot exists on a provider without the pi transport");
228
- // Deliverable: the user sees the real typed cause, never a generic
229
- // candidate-view message.
231
+ assert.deepEqual(agents, ["pi"], "the refusal path issues one pi-bound STATUS and never falls back to an agent-less STATUS");
232
+ assert.equal(relayCalls, 0, "a refused transport must not launch a relay capture");
230
233
  const transport = result.relay_transport as { supported: boolean; code: string; message: string } | undefined;
231
- assert.ok(transport !== undefined, "the envelope must report the transport refusal");
234
+ assert.ok(transport !== undefined, "the blocked envelope must report the typed transport refusal");
232
235
  assert.equal(transport.supported, false);
233
236
  assert.equal(transport.code, TRANSPORT_REFUSAL_CODE);
234
237
  assert.match(transport.message, /immutable review runtimes/i);
238
+ assert.equal(result.status, "blocked");
239
+ assert.equal(result.outcome, "pi-host-relay-transport-unavailable");
240
+ assert.equal(result.mutation_performed, false);
241
+ assert.equal(result.mutation_outcome, "none");
242
+ assert.match(String(result.next_action), /support.*--agent pi/i);
235
243
  assert.doesNotMatch(JSON.stringify(result), /no current controller-owned candidate view/);
236
- // The lifecycle still routes: reviewer results remain outstanding.
237
- assert.equal(result.outcome, "reviewer-results-required");
244
+ assert.doesNotMatch(JSON.stringify(result), /"status":"(?:approved|allowed)"/);
245
+ });
246
+
247
+ test("a transport refusal names the runnable wrapper continuation, never a raw CLI command as the only exit", async (t) => {
248
+ // #535: a provider-printed raw `gentle-ai review ...` continuation is a dead
249
+ // end in this runtime — Pi is not in the provider's immutable review runtime
250
+ // list — so the refusal envelope itself must name the exit that runs in this
251
+ // surface: the gentle_review / gentle_review_capture wrapper tools. The
252
+ // provider's diagnostic stays intact as evidence alongside the runnable exit.
253
+ const captureCwd = repository(t);
254
+ const { native: captureNative } = transportAwareNative({ refusalCode: TRANSPORT_REFUSAL_CODE });
255
+ const capture = await runCapture(captureCwd, captureNative, "continuation-capture-lineage");
256
+ __testing.clearReviewTransportProbeForTesting(captureNative);
257
+ assert.equal(capture.outcome, "pi-host-relay-transport-unavailable");
258
+ const captureNextAction = String(capture.next_action);
259
+ assert.match(captureNextAction, /gentle_review \{"operation":"inspect"\}/, "the capture refusal re-enters negotiated STATUS through the wrapper controller");
260
+ assert.match(captureNextAction, /gentle_review_capture/, "the capture refusal names the wrapper resubmission, not a CLI command");
261
+ assert.doesNotMatch(captureNextAction, /gentle-ai review/, "a raw `gentle-ai review ...` invocation is not a runnable exit in this runtime");
262
+ assert.deepEqual(capture.wrapper_continuation, { tool: "gentle_review", operation: "inspect", then: "gentle_review_capture" });
263
+ assert.match(String((capture.relay_transport as { message: string }).message), /immutable review runtimes/i, "the provider diagnostic remains intact in the envelope");
264
+
265
+ const statusCwd = repository(t);
266
+ const { native: statusNative } = transportAwareNative({ refusalCode: TRANSPORT_REFUSAL_CODE });
267
+ const status = await runStatus(statusCwd, statusNative, "continuation-status-lineage");
268
+ __testing.clearReviewTransportProbeForTesting(statusNative);
269
+ assert.equal(status.outcome, "pi-host-relay-transport-unavailable");
270
+ const statusNextAction = String(status.next_action);
271
+ assert.match(statusNextAction, /gentle_review \{"operation":"inspect"\}/, "the controller refusal re-enters negotiated STATUS through the wrapper controller");
272
+ assert.doesNotMatch(statusNextAction, /gentle-ai review/, "a raw `gentle-ai review ...` invocation is not a runnable exit in this runtime");
273
+ assert.deepEqual(status.wrapper_continuation, { tool: "gentle_review", operation: "inspect" });
274
+ });
275
+
276
+ test("a remembered pi transport refusal remains blocked without re-probing or lifecycle continuation", async (t) => {
277
+ const cwd = repository(t);
278
+ const { native, agents } = transportAwareNative({ refusalCode: TRANSPORT_REFUSAL_CODE });
279
+ const first = await runCapture(cwd, native, "probe-lineage");
280
+ const second = await runCapture(cwd, native, "probe-lineage");
281
+ assert.equal(first.outcome, "pi-host-relay-transport-unavailable");
282
+ assert.equal(second.outcome, "pi-host-relay-transport-unavailable");
283
+ assert.deepEqual(agents, ["pi"], "a cached refusal must not re-probe or issue an agent-less STATUS");
284
+ __testing.clearReviewTransportProbeForTesting(native);
285
+ });
286
+
287
+ test("every typed Pi transport refusal code fails closed", async (t) => {
288
+ const refusalCodes = [
289
+ "immutable_review_transport_unsupported",
290
+ "unsupported_agent",
291
+ "unknown_flag",
292
+ ];
293
+ for (const refusalCode of refusalCodes) {
294
+ const cwd = repository(t);
295
+ const { native, agents } = transportAwareNative({ refusalCode });
296
+ const result = await runCapture(cwd, native, `refusal-${refusalCode}`);
297
+ assert.equal(result.status, "blocked", `${refusalCode} must be blocked`);
298
+ assert.equal(result.outcome, "pi-host-relay-transport-unavailable", `${refusalCode} must use the unavailable envelope`);
299
+ assert.equal((result.relay_transport as { code: string }).code, refusalCode);
300
+ assert.deepEqual(agents, ["pi"], `${refusalCode} must not fall back to agent-less STATUS`);
301
+ __testing.clearReviewTransportProbeForTesting(native);
302
+ }
238
303
  });
239
304
 
240
- test("the pi transport is probed once per provider and the refusal is remembered", async (t) => {
305
+ test("typed status errors outside the Pi transport refusal set remain native errors", async (t) => {
241
306
  const cwd = repository(t);
242
- const { native, agents } = transportAwareNative({ refusePiAgent: true });
243
- await runFinalize(cwd, native, "probe-lineage");
244
- const afterFirst = agents.filter((agent) => agent === "pi").length;
245
- await runFinalize(cwd, native, "probe-lineage");
246
- const afterSecond = agents.filter((agent) => agent === "pi").length;
247
- assert.equal(afterFirst, 1, "the first flow probes the pi transport exactly once");
248
- assert.equal(afterSecond, 1, "a remembered refusal is never re-probed for the same provider");
307
+ const { native, agents } = transportAwareNative({ refusalCode: "provider_unavailable" });
308
+ const result = await runCapture(cwd, native, "non-transport-status-error");
309
+ assert.deepEqual(agents, ["pi"], "a non-transport error must not be retried as an agent-less lifecycle STATUS");
310
+ assert.equal(result.status, "blocked");
311
+ assert.equal(result.outcome, undefined, "a non-transport error must not be coerced into a transport refusal envelope");
312
+ assert.ok("native_failure" in result, "the native error envelope must remain available to the caller");
313
+ assert.equal(result.relay_transport, undefined);
314
+ });
315
+
316
+ test("ordinary STATUS inspection uses the Pi public collect-binding route", async (t) => {
317
+ const cwd = repository(t);
318
+ const { native, agents } = transportAwareNative();
319
+ const result = await runStatus(cwd, native, "ordinary-status-lineage");
320
+ assert.deepEqual(agents, ["pi"], "ordinary STATUS must request the Pi public collect-binding route");
321
+ assert.equal(result.operation, "status");
249
322
  });
@@ -2,9 +2,10 @@ import assert from "node:assert/strict";
2
2
  import { execFileSync } from "node:child_process";
3
3
  import {
4
4
  existsSync,
5
- mkdtempSync,
6
5
  mkdirSync,
6
+ mkdtempSync,
7
7
  readFileSync,
8
+ realpathSync,
8
9
  rmSync,
9
10
  writeFileSync,
10
11
  } from "node:fs";
@@ -60,7 +61,7 @@ function createRepository(t: test.TestContext): {
60
61
  repository: string;
61
62
  git: (...args: string[]) => string;
62
63
  } {
63
- const parent = mkdtempSync(join(tmpdir(), "gentle-pi-snapshot-"));
64
+ const parent = realpathSync(mkdtempSync(join(tmpdir(), "gentle-pi-snapshot-")));
64
65
  const repository = join(parent, "repo");
65
66
  mkdirSync(repository);
66
67
  t.after(() => rmSync(parent, { recursive: true, force: true }));