@akagilnc/pi-workflow-roles 0.1.4239 → 0.1.4259

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.
@@ -51,6 +51,14 @@ import {
51
51
  COLLECTOR_WAIT_TOOL,
52
52
  } from "../collector-ledger.ts";
53
53
  import { ENGINE_DETOUR_TOOL_NAME } from "../engine-detour.ts";
54
+ import {
55
+ projectEngineDetourToolUsageForPublicTerminal,
56
+ readEngineDetourToolUsage,
57
+ readInvocationEngineMounted,
58
+ runDirectoryFromSessionDirectory,
59
+ sessionFileFromSessionDirectory,
60
+ withEngineDetourToolUsageFact,
61
+ } from "../engine-detour-usage.ts";
54
62
  import {
55
63
  JUDGE_OUTPUT_TOOL_NAME,
56
64
  type JudgeVerdict,
@@ -158,6 +166,8 @@ function sealedLedgerHome(admitted: Pick<AdmittedRoleInvocation, "runDirectory">
158
166
  */
159
167
  export type SettlementCourtScope = {
160
168
  readonly courtAttemptId?: string;
169
+ /** Public-invocation scope from the shared Host envelope (#537). */
170
+ readonly invocationScopeId?: string;
161
171
  };
162
172
 
163
173
  function ledgerReadScope(
@@ -300,6 +310,7 @@ export async function attachRecordedSubmissions<T extends TerminalResult>(
300
310
  export async function settleHostEndedNoReceipt(
301
311
  admitted: AdmittedRoleInvocation,
302
312
  authority: DurablePrincipalAuthority,
313
+ scope?: SettlementCourtScope,
303
314
  ): Promise<TerminalResult> {
304
315
  const facts = noReceiptLifecycleFacts({
305
316
  terminalToolCalled: false,
@@ -323,6 +334,7 @@ export async function settleHostEndedNoReceipt(
323
334
  runId: admitted.runId,
324
335
  },
325
336
  coordinates.sessionDirectory,
337
+ detourGateContext(admitted, scope),
326
338
  );
327
339
  }
328
340
 
@@ -2187,11 +2199,86 @@ export async function extractGateFactFromSessionDirectory(
2187
2199
  * or swallowed); callers that already hold a controlled failure still surface the
2188
2200
  * JSONL/session cause rather than pretend the gate was absent.
2189
2201
  */
2202
+ /**
2203
+ * #537: project this-invocation ak_engine_detour usage onto decisiveFacts.
2204
+ * Absent when engine is not mounted; callCount 0 when mounted with zero calls.
2205
+ * Never mutates role payloads (ADR 0003 / 0042 / 0052).
2206
+ * Scope is the public-invocation id bound once per ak-role call — never
2207
+ * courtAttemptId and never Pi session.jsonl toolResult join. Session damage
2208
+ * therefore cannot replace an already-formed roleOutcome (no_receipt / failure).
2209
+ */
2210
+ async function attachEngineDetourToolUsage<
2211
+ T extends { roleOutcome: TerminalRoleOutcome; resume?: TerminalResume },
2212
+ >(
2213
+ base: T,
2214
+ sessionDirectory: string,
2215
+ gateContext: {
2216
+ readonly runDirectory?: string;
2217
+ readonly courtAttemptId?: string;
2218
+ readonly invocationScopeId?: string;
2219
+ } = {},
2220
+ ): Promise<T> {
2221
+ const runDirectory =
2222
+ typeof gateContext.runDirectory === "string" && gateContext.runDirectory.length > 0
2223
+ ? gateContext.runDirectory
2224
+ : runDirectoryFromSessionDirectory(sessionDirectory);
2225
+ const engineMounted = await readInvocationEngineMounted(runDirectory);
2226
+ if (!engineMounted) return base;
2227
+
2228
+ // Public-invocation scope from the shared Host envelope (settlement scope), never
2229
+ // courtAttemptId and never a detour sidecar file.
2230
+ const invocationScopeId =
2231
+ typeof gateContext.invocationScopeId === "string" &&
2232
+ gateContext.invocationScopeId.length > 0
2233
+ ? gateContext.invocationScopeId
2234
+ : undefined;
2235
+
2236
+ const sessionFile = sessionFileFromSessionDirectory(sessionDirectory);
2237
+ const usage = await readEngineDetourToolUsage({
2238
+ sessionParent: sessionFile,
2239
+ engineMounted: true,
2240
+ ...(invocationScopeId === undefined ? {} : { invocationScopeId }),
2241
+ cwd: runDirectory,
2242
+ });
2243
+ const projected =
2244
+ usage === undefined
2245
+ ? undefined
2246
+ : projectEngineDetourToolUsageForPublicTerminal(usage, {
2247
+ // Resumable Terminal: run ID only in resume.command — relative openable path.
2248
+ discloseRecordFile: base.resume === undefined,
2249
+ });
2250
+ return {
2251
+ ...base,
2252
+ roleOutcome: withEngineDetourToolUsageFact(base.roleOutcome, projected),
2253
+ };
2254
+ }
2255
+
2256
+ /** Gate + detour projection context from admitted run + settlement scope. */
2257
+ function detourGateContext(
2258
+ admitted: { readonly runDirectory: string },
2259
+ scope?: SettlementCourtScope,
2260
+ ): {
2261
+ readonly runDirectory: string;
2262
+ readonly courtAttemptId?: string;
2263
+ readonly invocationScopeId?: string;
2264
+ } {
2265
+ return {
2266
+ runDirectory: admitted.runDirectory,
2267
+ ...(scope?.courtAttemptId === undefined || scope.courtAttemptId.length === 0
2268
+ ? {}
2269
+ : { courtAttemptId: scope.courtAttemptId }),
2270
+ ...(scope?.invocationScopeId === undefined || scope.invocationScopeId.length === 0
2271
+ ? {}
2272
+ : { invocationScopeId: scope.invocationScopeId }),
2273
+ };
2274
+ }
2275
+
2190
2276
  async function withOptionalGateProjection<
2191
2277
  T extends {
2192
2278
  roleOutcome: TerminalRoleOutcome;
2193
2279
  navigator: TerminalNavigatorFact;
2194
2280
  artifacts: readonly TerminalArtifactRef[];
2281
+ resume?: TerminalResume;
2195
2282
  },
2196
2283
  >(
2197
2284
  base: T,
@@ -2199,6 +2286,8 @@ async function withOptionalGateProjection<
2199
2286
  gateContext: {
2200
2287
  readonly runDirectory?: string;
2201
2288
  readonly parentSessionFile?: string;
2289
+ readonly courtAttemptId?: string;
2290
+ readonly invocationScopeId?: string;
2202
2291
  } = {},
2203
2292
  ): Promise<T & { gate?: TerminalGateFact }> {
2204
2293
  // A gate transport failure is already represented by typed evidence and has no
@@ -2207,19 +2296,23 @@ async function withOptionalGateProjection<
2207
2296
  const secondaryEvidence = base.roleOutcome.kind === "failure"
2208
2297
  ? base.roleOutcome.decisiveFacts.secondaryEvidence
2209
2298
  : undefined;
2210
- if (
2299
+ const skipGate =
2211
2300
  isRecord(secondaryEvidence)
2212
2301
  && secondaryEvidence.kind === "role_infrastructure_failure"
2213
2302
  && (
2214
2303
  secondaryEvidence.stage === "gatekeeper"
2215
2304
  || secondaryEvidence.stage === "inspector"
2216
2305
  || secondaryEvidence.stage === "notary"
2217
- )
2218
- ) return base;
2306
+ );
2307
+
2308
+ let next: T & { gate?: TerminalGateFact } = base;
2309
+ if (!skipGate) {
2310
+ // Defaults live solely in extractGateFactFromSessionDirectory — do not re-derive.
2311
+ const gate = await extractGateFactFromSessionDirectory(sessionDirectory, gateContext);
2312
+ if (gate !== undefined) next = { ...base, gate };
2313
+ }
2219
2314
 
2220
- // Defaults live solely in extractGateFactFromSessionDirectory — do not re-derive.
2221
- const gate = await extractGateFactFromSessionDirectory(sessionDirectory, gateContext);
2222
- return gate === undefined ? base : { ...base, gate };
2315
+ return attachEngineDetourToolUsage(next, sessionDirectory, gateContext);
2223
2316
  }
2224
2317
 
2225
2318
  export function extractNavigatorFact(
@@ -2508,6 +2601,7 @@ async function settleLawfulJudgeTerminalResult(
2508
2601
  runId: admitted.runId,
2509
2602
  },
2510
2603
  coordinates.sessionDirectory,
2604
+ detourGateContext(admitted, scope),
2511
2605
  );
2512
2606
  }
2513
2607
 
@@ -2575,6 +2669,7 @@ async function settleLawfulCoderTerminalResult(
2575
2669
  runId: admitted.runId,
2576
2670
  },
2577
2671
  coordinates.sessionDirectory,
2672
+ detourGateContext(admitted, scope),
2578
2673
  );
2579
2674
  }
2580
2675
 
@@ -2751,6 +2846,7 @@ async function settleLawfulFixerTerminalResult(
2751
2846
  runId: admitted.runId,
2752
2847
  },
2753
2848
  sessionDirectory,
2849
+ detourGateContext(admitted, scope),
2754
2850
  );
2755
2851
  }
2756
2852
 
@@ -2848,11 +2944,16 @@ async function settleLawfulCollectorTerminalResult(
2848
2944
  if (residual === undefined) continue;
2849
2945
  const candidate = residual.candidate;
2850
2946
  const details = isRecord(candidate) ? candidate : { candidate };
2851
- const failed = await settleFailureTerminalResult(admitted, {
2852
- cause: "output",
2853
- diagnostic: residual.diagnostic,
2854
- details,
2855
- }, authority);
2947
+ const failed = await settleFailureTerminalResult(
2948
+ admitted,
2949
+ {
2950
+ cause: "output",
2951
+ diagnostic: residual.diagnostic,
2952
+ details,
2953
+ },
2954
+ authority,
2955
+ scope ?? {},
2956
+ );
2856
2957
  return attachRecordedSubmissions(admitted, failed, scope);
2857
2958
  }
2858
2959
  return undefined;
@@ -2874,6 +2975,7 @@ async function settleLawfulCollectorTerminalResult(
2874
2975
  runId: admitted.runId,
2875
2976
  },
2876
2977
  sessionDirectory,
2978
+ detourGateContext(admitted, scope),
2877
2979
  ),
2878
2980
  scope,
2879
2981
  );
@@ -3025,6 +3127,7 @@ async function settleLawfulDoctorTerminalResult(
3025
3127
  runId: admitted.runId,
3026
3128
  },
3027
3129
  sessionDirectory,
3130
+ detourGateContext(admitted, scope),
3028
3131
  );
3029
3132
  }
3030
3133
  const roleOutcome = sealed;
@@ -3048,6 +3151,7 @@ async function settleLawfulDoctorTerminalResult(
3048
3151
  runId: admitted.runId,
3049
3152
  },
3050
3153
  sessionDirectory,
3154
+ detourGateContext(admitted, scope),
3051
3155
  );
3052
3156
  }
3053
3157
 
@@ -3137,11 +3241,16 @@ async function settleLawfulSeatAcceptedTerminalResult(
3137
3241
  const details = isRecord(residual.candidate)
3138
3242
  ? residual.candidate
3139
3243
  : { candidate: residual.candidate };
3140
- const failed = await settleFailureTerminalResult(admitted, {
3141
- cause: "output",
3142
- diagnostic: residual.diagnostic,
3143
- details,
3144
- }, authority);
3244
+ const failed = await settleFailureTerminalResult(
3245
+ admitted,
3246
+ {
3247
+ cause: "output",
3248
+ diagnostic: residual.diagnostic,
3249
+ details,
3250
+ },
3251
+ authority,
3252
+ scope ?? {},
3253
+ );
3145
3254
  return withSubmissions(failed, submissions);
3146
3255
  }
3147
3256
  }
@@ -3157,6 +3266,7 @@ async function settleLawfulSeatAcceptedTerminalResult(
3157
3266
  runId: admitted.runId,
3158
3267
  },
3159
3268
  sessionDirectory,
3269
+ detourGateContext(admitted, scope),
3160
3270
  ),
3161
3271
  submissions,
3162
3272
  );
@@ -3172,6 +3282,7 @@ async function settleLawfulSeatAcceptedTerminalResult(
3172
3282
  runId: admitted.runId,
3173
3283
  },
3174
3284
  sessionDirectory,
3285
+ detourGateContext(admitted, scope),
3175
3286
  ),
3176
3287
  submissions,
3177
3288
  );
@@ -3568,6 +3679,7 @@ async function settleLawfulReviewerTerminalResult(
3568
3679
  runId: admitted.runId,
3569
3680
  },
3570
3681
  sessionDirectory,
3682
+ detourGateContext(admitted, scope),
3571
3683
  );
3572
3684
  }
3573
3685
 
@@ -3720,11 +3832,16 @@ async function settleLawfulMergerTerminalResult(
3720
3832
  if (residual === undefined) continue;
3721
3833
  const candidate = residual.candidate;
3722
3834
  const details = isRecord(candidate) ? candidate : { candidate };
3723
- const failed = await settleFailureTerminalResult(admitted, {
3724
- cause: "output",
3725
- diagnostic: residual.diagnostic,
3726
- details,
3727
- }, authority);
3835
+ const failed = await settleFailureTerminalResult(
3836
+ admitted,
3837
+ {
3838
+ cause: "output",
3839
+ diagnostic: residual.diagnostic,
3840
+ details,
3841
+ },
3842
+ authority,
3843
+ scope ?? {},
3844
+ );
3728
3845
  return attachRecordedSubmissions(admitted, failed, scope);
3729
3846
  }
3730
3847
  return undefined;
@@ -3753,6 +3870,7 @@ async function settleLawfulMergerTerminalResult(
3753
3870
  runId: admitted.runId,
3754
3871
  },
3755
3872
  sessionDirectory,
3873
+ detourGateContext(admitted, scope),
3756
3874
  ),
3757
3875
  scope,
3758
3876
  );
@@ -4018,7 +4136,9 @@ export async function settleFailureTerminalResult(
4018
4136
  admitted: AdmittedRoleInvocation,
4019
4137
  failure: ControlledFailure,
4020
4138
  authority: DurablePrincipalAuthority,
4021
- options: { readonly resume?: TerminalResume } = {},
4139
+ options: SettlementCourtScope & {
4140
+ readonly resume?: TerminalResume;
4141
+ } = {},
4022
4142
  ): Promise<TerminalResult> {
4023
4143
  const coordinates = coordinatesFromAdmitted(authority, admitted);
4024
4144
  const { sessionDirectory, sessionFile } = coordinates;
@@ -4061,6 +4181,7 @@ export async function settleFailureTerminalResult(
4061
4181
  runId: admitted.runId,
4062
4182
  },
4063
4183
  sessionDirectory,
4184
+ detourGateContext(admitted, options),
4064
4185
  );
4065
4186
  }
4066
4187
  } catch { /* malformed lifecycle bytes remain the existing nonzero output failure */ }
@@ -4104,6 +4225,7 @@ export async function settleFailureTerminalResult(
4104
4225
  resume: options.resume,
4105
4226
  },
4106
4227
  sessionDirectory,
4228
+ detourGateContext(admitted, options),
4107
4229
  );
4108
4230
  }
4109
4231
  const roleOutcome: TerminalRoleOutcome = {
@@ -4122,6 +4244,7 @@ export async function settleFailureTerminalResult(
4122
4244
  runId: admitted.runId,
4123
4245
  },
4124
4246
  sessionDirectory,
4247
+ detourGateContext(admitted, options),
4125
4248
  );
4126
4249
  }
4127
4250
 
@@ -4130,7 +4253,9 @@ export async function settleJudgeFailureTerminalResult(
4130
4253
  admitted: AdmittedJudgeInvocation,
4131
4254
  failure: ControlledFailure,
4132
4255
  authority: DurablePrincipalAuthority,
4133
- options: { readonly resume?: TerminalResume } = {},
4256
+ options: SettlementCourtScope & {
4257
+ readonly resume?: TerminalResume;
4258
+ } = {},
4134
4259
  ): Promise<TerminalResult> {
4135
4260
  return settleFailureTerminalResult(admitted, failure, authority, options);
4136
4261
  }
@@ -34,6 +34,8 @@ export type RoleTurnRequestProjectionOptions = {
34
34
  continuation: RoleTurnRequest["continuation"];
35
35
  /** #833 resume-with-message court attempt. */
36
36
  courtAttemptId?: string;
37
+ /** #537 public-invocation scope (one ak-role call). */
38
+ invocationScopeId?: string;
37
39
  /** Station child role run (#840): omit automatic navigator attendance. */
38
40
  stationChild?: boolean;
39
41
  };
@@ -79,6 +81,9 @@ export function projectRoleTurnRequest(
79
81
  ...(options.courtAttemptId === undefined || options.courtAttemptId.length === 0
80
82
  ? {}
81
83
  : { courtAttemptId: options.courtAttemptId }),
84
+ ...(options.invocationScopeId === undefined || options.invocationScopeId.length === 0
85
+ ? {}
86
+ : { invocationScopeId: options.invocationScopeId }),
82
87
  ...(options.stationChild === undefined ? {} : { stationChild: options.stationChild }),
83
88
  };
84
89
  }
@@ -175,6 +175,8 @@ export async function prepareRoleEnvelope(options: {
175
175
  model: request.model === undefined ? undefined : { provider: request.model.provider },
176
176
  runDirectory: request.runDirectory,
177
177
  ...(request.courtAttemptId === undefined ? {} : { courtAttemptId: request.courtAttemptId }),
178
+ ...(request.invocationScopeId === undefined ? {} : { invocationScopeId: request.invocationScopeId }),
179
+ ...(request.host === undefined || request.host.trim() === "" ? {} : { host: request.host.trim() }),
178
180
  sessionManager: {
179
181
  getLeafEntry: () => sessionEntries.at(-1) as ReturnType<HostContext["sessionManager"]["getLeafEntry"]>,
180
182
  getLeafId: () => runId,