@akagilnc/pi-workflow-roles 0.1.3452 → 0.1.3471

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 (36) hide show
  1. package/CLAUDE.md +2 -0
  2. package/dist/evidence-child-executor.js +2 -11
  3. package/dist/grok/production-host.js +196 -205
  4. package/dist/pi/in-process-session.js +4 -0
  5. package/dist/public-cli/main.js +998 -920
  6. package/package.json +1 -1
  7. package/scripts/build-package.mjs +1 -3
  8. package/src/analyst-gate-cycles-read.ts +3 -21
  9. package/src/analyst-ledger.ts +11 -42
  10. package/src/archivist-record-entry.ts +5 -6
  11. package/src/evidence-child-executor.ts +5 -15
  12. package/src/grok/production-host.ts +3 -32
  13. package/src/grok/role-envelope.ts +10 -4
  14. package/src/host-contracts.ts +4 -11
  15. package/src/host-transition-prior-native.ts +3 -7
  16. package/src/ledger-session-read.ts +1 -17
  17. package/src/pi/in-process-session.ts +6 -2
  18. package/src/pi/role-turn-host.ts +9 -0
  19. package/src/public-cli/collector-run.ts +4 -4
  20. package/src/public-cli/countersign-run.ts +90 -20
  21. package/src/public-cli/doctor-run.ts +4 -4
  22. package/src/public-cli/gleaner-left-run.ts +9 -6
  23. package/src/public-cli/inspector-run.ts +95 -32
  24. package/src/public-cli/instruction-seat-run.ts +7 -6
  25. package/src/public-cli/invocation.ts +14 -8
  26. package/src/public-cli/judge-run.ts +4 -4
  27. package/src/public-cli/notary-run.ts +91 -30
  28. package/src/public-cli/post-admission.ts +424 -360
  29. package/src/public-cli/run-lifecycle.ts +205 -0
  30. package/src/public-cli/seat-ticket-binding.ts +136 -16
  31. package/src/public-cli/settlement.ts +74 -52
  32. package/src/run-ticket-number.ts +53 -0
  33. package/src/submission-ledger.ts +86 -18
  34. package/dist/ledger-session-read.js +0 -241
  35. package/dist/ticket-seat-memory.js +0 -244
  36. package/src/ticket-seat-memory.ts +0 -454
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.3452",
3
+ "version": "0.1.3471",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -23,9 +23,7 @@ const entries = [
23
23
  "archivist-record-entry",
24
24
  // Pure subject nest topology — cold discovery without SessionManager (#636).
25
25
  "archivist-record-topology",
26
- // Officer ticket-seat pure read + deferred open (#636 public-bin cold graph).
27
- "ticket-seat-memory",
28
- // ticket-seat-memory → session-opening-materials (#636 non-bundle graph closure).
26
+ // Session material loaders used by published non-bundle roots.
29
27
  "session-opening-materials",
30
28
  // Value-import closure of published non-bundle roots (build-package-only loadable).
31
29
  "auditor-dossier-tool",
@@ -34,11 +34,10 @@ import {
34
34
  import {
35
35
  extractSessionTimestampSpan,
36
36
  intervalRowsAroundAnchor,
37
- LedgerSessionJsonlError,
38
37
  readLedgerSessionJsonl,
39
38
  type LedgerSessionRow,
40
39
  } from "./ledger-session-read.ts";
41
- import { isTicketSeatRunBindingRow } from "./ticket-seat-memory.ts";
40
+
42
41
 
43
42
  /** One completed gate round: direct officer receipt or historical province/officer pair. */
44
43
  /** Honest origin discriminant: direct summons vs historical province dispatch. */
@@ -349,22 +348,7 @@ async function classifyAuditorVolume(
349
348
  filePath: string,
350
349
  ): Promise<readonly ClassifiedVolume[]> {
351
350
  // Canonical JSONL errors propagate — failure honesty (never wash to fewer rounds).
352
- // Shared ticket-seat main volumes are the exception: later-run damage after a
353
- // closed binding must not erase earlier gate rounds (same ownership rule as
354
- // timeline attribution). Pure nested gate volumes without run bindings stay loud.
355
- let rows: readonly LedgerSessionRow[];
356
- try {
357
- rows = await readLedgerSessionJsonl(filePath);
358
- } catch (error) {
359
- if (
360
- error instanceof LedgerSessionJsonlError
361
- && error.prefixRows.some((row) => isTicketSeatRunBindingRow(row))
362
- ) {
363
- rows = error.prefixRows;
364
- } else {
365
- throw error;
366
- }
367
- }
351
+ const rows = await readLedgerSessionJsonl(filePath);
368
352
  const accepted = extractAllAcceptedGateToolCalls(rows);
369
353
  // Rejected-only / non-gate volumes omit (empty accepted set).
370
354
  if (accepted.length === 0) return [];
@@ -455,9 +439,7 @@ function pairGateRounds(
455
439
  * ENOENT (directory truly absent) → []. ENOTDIR and other errors propagate
456
440
  * (failure honesty — damaged topology must not wash to zero rounds).
457
441
  *
458
- * #636 ticket-seat memory may place officer volumes under book-level subject
459
- * digests shared across parent runs; pass `parentSessionFile` to keep only
460
- * rounds whose attempt binding names that parent.
442
+ * Pass `parentSessionFile` to keep only rounds whose attempt binding names that parent.
461
443
  */
462
444
  export async function readAnalystGateCyclesFromAuditorRoles(
463
445
  auditorRolesDirectory: string | readonly string[],
@@ -25,7 +25,6 @@ import {
25
25
  extractSessionModelSequence,
26
26
  extractSessionTimestampSpan,
27
27
  extractSessionToolIntervals,
28
- intervalRowsForMatchingBinding,
29
28
  LedgerSessionJsonlError,
30
29
  readLedgerSessionJsonl,
31
30
  type LedgerSessionRow,
@@ -46,11 +45,6 @@ import {
46
45
  readAnalystGateCyclesFromAuditorRoles,
47
46
  type AnalystGateCycleRound,
48
47
  } from "./analyst-gate-cycles-read.ts";
49
- import {
50
- isTicketSeatRunBindingRow,
51
- resolveTicketSeatMemoryNestDirectories,
52
- ticketSeatRunBindingRunId,
53
- } from "./ticket-seat-memory.ts";
54
48
 
55
49
  export type { AnalystGateCycleRound } from "./analyst-gate-cycles-read.ts";
56
50
 
@@ -210,16 +204,9 @@ async function resolveSessionFile(
210
204
  }
211
205
 
212
206
  /**
213
- * Attribute session rows to one run (#636 D).
207
+ * Attribute session rows to one run.
214
208
  * Private run volumes (session under runDirectory) keep the whole file.
215
- * Shared ticket-seat main volumes must carry this run's binding interval;
216
- * whole-volume stats would let later continuations pollute earlier runs.
217
- * Unbounded external volumes stay honest missing — never silent full ownership.
218
- *
219
- * `closed` on shared intervals: a later binding ended ownership before EOF of
220
- * the provided rows. On damaged-prefix recovery, only closed intervals keep
221
- * full frame/model/tool facts — open intervals may have lost rows to the bad line.
222
- * Private volumes are never closed against a prefix (the whole file is this run).
209
+ * External volumes stay honest missing — never silent full ownership.
223
210
  */
224
211
  function attributeSessionRowsForRun(input: {
225
212
  readonly rows: readonly LedgerSessionRow[];
@@ -236,19 +223,10 @@ function attributeSessionRowsForRun(input: {
236
223
  if (pathContainedIn(input.runDirectory, input.sessionFile)) {
237
224
  return { kind: "attributed", rows: input.rows, closed: false };
238
225
  }
239
- const interval = intervalRowsForMatchingBinding(
240
- input.rows,
241
- isTicketSeatRunBindingRow,
242
- (row) => ticketSeatRunBindingRunId(row) === input.runId,
243
- );
244
- if (interval === undefined) {
245
- return {
246
- kind: "missing-boundary",
247
- reason:
248
- "shared session volume has no ticket-seat run binding for this run",
249
- };
250
- }
251
- return { kind: "attributed", rows: interval.rows, closed: interval.closed };
226
+ return {
227
+ kind: "missing-boundary",
228
+ reason: "session volume is outside the run directory",
229
+ };
252
230
  }
253
231
 
254
232
  /**
@@ -382,8 +360,7 @@ async function classifyScopedRun(input: {
382
360
  let partialFirstFrameAt: AnalystFirstFrameAt = { status: "absent" };
383
361
  let partialLastFrameAt: AnalystOptionalTimestamp = { status: "absent" };
384
362
 
385
- // 1) session timeline — shared ticket-seat volumes slice to this run's binding.
386
- // Damage after a closed binding interval must not erase that run's full facts.
363
+ // 1) session timeline from the run-private volume.
387
364
  const sessionFile = await resolveSessionFile(input.runDirectory);
388
365
  let rows: readonly LedgerSessionRow[] | undefined;
389
366
  const admitAttributed = (
@@ -516,21 +493,13 @@ async function classifyScopedRun(input: {
516
493
  // Nested auditor-roles gate pairs stay inside the sole scan (families must
517
494
  // not readdir this tree again). Missing directory → []. Damaged discovered
518
495
  // nested JSONL is page-local unreadable — never silently under-count rounds.
519
- // #636: ticket-seat memory nests via sole discovery helper.
520
496
  let gateCycles: readonly AnalystGateCycleRound[];
521
497
  try {
522
- const memoryNests = await resolveTicketSeatMemoryNestDirectories({
523
- runDirectory: input.runDirectory,
524
- seats: ["inspector", "notary"],
525
- });
526
- const directories = [
527
- join(input.runDirectory, "session", "auditor-roles"),
528
- ...memoryNests,
529
- ];
530
498
  const parentSessionFile = join(input.runDirectory, "session", "session.jsonl");
531
- gateCycles = await readAnalystGateCyclesFromAuditorRoles(directories, {
532
- parentSessionFile,
533
- });
499
+ gateCycles = await readAnalystGateCyclesFromAuditorRoles(
500
+ join(input.runDirectory, "session", "auditor-roles"),
501
+ { parentSessionFile },
502
+ );
534
503
  } catch (error) {
535
504
  return {
536
505
  kind: "unreadable",
@@ -136,10 +136,9 @@ export type RecordSessionOpen = {
136
136
  * identity is checked once before SessionManager.open (directory walk cannot see a
137
137
  * trailing .jsonl symlink). New principals mint under the already-validated sessionDir
138
138
  * via destination-free SessionManager.create — no derived postcondition.
139
- * Resume via the AK-owned current-session ledger is limited to subject-keyed identity and the
140
- * authorized worker-submission-gate durable path. Ordinary no-subject children
141
- * (evidence-children, auditor-roles, …) always mint a fresh session — never reopen
142
- * a sibling volume selected only by kind/cwd/mtime.
139
+ * Resume via the AK-owned current-session ledger is limited to subject-keyed identity
140
+ * and the authorized worker-submission-gate durable path (ADR 0066).
141
+ * Other ordinary no-subject children (auditor-roles, evidence-children, …) always mint fresh.
143
142
  * New persisted principals materialize their deferred session header before return so
144
143
  * custom-entry-only writers do not need a parallel delayed-header helper.
145
144
  *
@@ -183,8 +182,8 @@ export function createRecordSessionOpen(options: CreateRecordSessionOptions): Re
183
182
  const nestAlreadyExists = existsSync(sessionDir);
184
183
  // Directory-chain ownership: containment + physical components (no parallel assert).
185
184
  ensureRealDirectoryTree(ledgerHome, sessionDir);
186
- // Subject-keyed nests continue by subject digest; gate durable resume is the only
187
- // authorized no-subject same-nest continuation. All other kinds mint fresh.
185
+ // Subject-keyed nests continue by subject digest; worker-submission-gate is the
186
+ // sole authorized no-subject same-nest continuation. All other kinds mint fresh.
188
187
  const mayResumeSameNest =
189
188
  options.subject !== undefined || options.kind === WORKER_SUBMISSION_GATE_KIND;
190
189
  if (mayResumeSameNest && nestAlreadyExists) {
@@ -26,12 +26,6 @@ import {
26
26
  } from "./compliance-transport.ts";
27
27
  import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
28
28
  import { sitianReport } from "./sitian-facade.ts";
29
- import {
30
- isTicketSeatMemorySeat,
31
- readRunTicketNumber,
32
- ticketSeatMemorySubject,
33
- TICKET_SEAT_MEMORY_KIND,
34
- } from "./ticket-seat-memory.ts";
35
29
  import { createEngineDetourToolDefinition } from "./engine-detour-tool.ts";
36
30
  import { engineNameFromEnv } from "./engine-detour.ts";
37
31
  import {
@@ -518,7 +512,6 @@ function auditorSeatKey(gateSeat: GateOfficerSeat | undefined): string {
518
512
  export async function executeAuditorChild(
519
513
  options: AuditorRoleOptions,
520
514
  ): Promise<{ decision: unknown; response: AssistantMessage; noReceiptLifecycle?: NoReceiptLifecycleFacts }> {
521
- const { createRecordSession } = await import("./archivist-record-entry.ts");
522
515
  const runDirectory = options.runDirectory ?? auditorRunDirectory(options.context);
523
516
  if (runDirectory === undefined) {
524
517
  throw new Error(`${options.roleLabel} requires a run directory carrying the institutional resolution page`);
@@ -570,16 +563,13 @@ export async function executeAuditorChild(
570
563
  const parentHeader = parentSessionManager?.getHeader?.();
571
564
  const parentSessionFile = parentSessionManager?.getSessionFile?.();
572
565
  const parentAttemptEntryId = parentSessionManager?.getLeafId?.();
573
- // #636: ticket+seat subject resumes the same nest (navigator subject path); no ticket → fresh nest.
574
- const ticketNumber = await readRunTicketNumber(runDirectory);
575
- const memorySubject =
576
- ticketNumber !== undefined && isTicketSeatMemorySeat(seat)
577
- ? ticketSeatMemorySubject(ticketNumber, seat)
578
- : undefined;
566
+ // Institutional child under parent placement for gate/settlement discovery.
567
+ // No ticket:seat subject nest (#636 deleted / ADR 0079). Public officer seats
568
+ // resume via tryResumeSameTicketSeatRun on the seat's own run — not this nest.
569
+ const { createRecordSession } = await import("./archivist-record-entry.ts");
579
570
  const auditorSessionManager: SessionManager = createRecordSession({
580
571
  cwd,
581
- kind: TICKET_SEAT_MEMORY_KIND,
582
- ...(memorySubject === undefined ? {} : { subject: memorySubject }),
572
+ kind: "auditor-roles",
583
573
  ...(parentSessionManager === undefined ? {} : { parent: parentSessionManager }),
584
574
  });
585
575
 
@@ -166,26 +166,19 @@ export async function bindProductionGrokIsolation(
166
166
  * and AK seatbelt hooks via settleProductionGrokHomeCleanup (no silent catch)
167
167
  * while preserving the session dossier under runDirectory/grok-home. Success,
168
168
  * typed-result, and throw paths all clean up; cleanup failure and primary+cleanup
169
- * both surface.
170
- *
171
- * `noteOpened` fires only after bind succeeds — the actual open event, distinct
172
- * from turn return/throw — so last-host ownership can retain the opened run
173
- * even when the body or cleanup later throws (#636).
169
+ * both surface. Same-run Grok isolation only — no cross-run home override (#637).
174
170
  */
175
171
  export async function withProductionGrokIsolation<T>(
176
172
  runDirectory: string,
177
173
  operatorHome: string,
178
174
  packageRoot: string,
179
175
  run: (binding: ProductionGrokIsolationBinding) => Promise<T>,
180
- noteOpened?: (runDirectory: string) => void,
181
176
  ): Promise<T> {
182
177
  let binding: ProductionGrokIsolationBinding | undefined;
183
178
  let primaryFailure: ProductionGrokPrimaryFailure = NO_PRODUCTION_GROK_PRIMARY_FAILURE;
184
179
  let value!: T;
185
180
  try {
186
181
  binding = await bindProductionGrokIsolation(runDirectory, operatorHome, packageRoot);
187
- // Open fact: bind succeeded under runDirectory. Not deferred to turn outcome.
188
- noteOpened?.(runDirectory);
189
182
  value = await run(binding);
190
183
  } catch (error) {
191
184
  primaryFailure = { present: true, value: error };
@@ -285,26 +278,9 @@ async function recordGrokCapabilities(
285
278
  );
286
279
  }
287
280
 
288
- /**
289
- * Which run directory owns GROK_HOME for this turn (#636 / ADR 0077).
290
- * Same-host ticket-seat resume reopens the prior native home; otherwise the live run.
291
- * Private to the production host — not a test seam.
292
- */
293
- function resolveProductionGrokIsolationRunDirectory(
294
- request: Pick<RoleTurnRequest, "runDirectory" | "continuation" | "nativeHomeRunDirectory">,
295
- ): string {
296
- if (
297
- request.continuation.kind === "resume" &&
298
- typeof request.nativeHomeRunDirectory === "string" &&
299
- request.nativeHomeRunDirectory.length > 0
300
- ) {
301
- return request.nativeHomeRunDirectory;
302
- }
303
- return request.runDirectory;
304
- }
305
-
306
281
  /**
307
282
  * Assemble the production grok-build RoleTurnHost from the S6 true adapter.
283
+ * Isolation always under the live request.runDirectory (same-run resume keeps it).
308
284
  */
309
285
  export function createProductionGrokRoleTurnHost(options: ProductionGrokHostOptions): RoleTurnHost {
310
286
  const { packageRoot, principalAuthority } = options;
@@ -342,13 +318,9 @@ export function createProductionGrokRoleTurnHost(options: ProductionGrokHostOpti
342
318
 
343
319
  return {
344
320
  executeTurn(request) {
345
- // #636 same-host ticket-seat resume: reopen the prior run's grok-home so
346
- // session/load hits the ACP storage that bound the shared principal.
347
- // Fresh runs and cross-host arrivals keep isolating under request.runDirectory.
348
- const isolationRunDirectory = resolveProductionGrokIsolationRunDirectory(request);
349
321
  const execution = serial.then(() =>
350
322
  withProductionGrokIsolation(
351
- isolationRunDirectory,
323
+ request.runDirectory,
352
324
  request.home,
353
325
  packageRoot,
354
326
  async (binding) => {
@@ -360,7 +332,6 @@ export function createProductionGrokRoleTurnHost(options: ProductionGrokHostOpti
360
332
  turn = undefined;
361
333
  }
362
334
  },
363
- request.noteNativeHomeOpened,
364
335
  ),
365
336
  );
366
337
  serial = execution.then(
@@ -539,16 +539,19 @@ export async function prepareGrokRoleEnvelope(options: {
539
539
  const relay = fileURLToPath(new URL("./mcp-relay.mjs", import.meta.url));
540
540
  let disposed = false;
541
541
  // Tools execute in this process (relay is protocol-only). Mirror Pi's child-env
542
- // AK_ROLE_RUN_DIR injection onto the parent so ledger runIdentity correlates
543
- // with settlement's bare admitted.runId via runIdFromRunDirectory. Inject only
542
+ // AK_ROLE_RUN_DIR / AK_ROLE_COURT_ATTEMPT injection onto the parent so ledger
543
+ // runIdentity and court-attempt identity correlate with settlement. Inject only
544
544
  // after prepare succeeds (below); dispose must restore including unset.
545
545
  let priorAkRoleRunDir: string | undefined;
546
+ let priorAkRoleCourtAttempt: string | undefined;
546
547
  let runDirInjected = false;
547
- const restoreAkRoleRunDir = (): void => {
548
+ const restoreAkRoleRunEnv = (): void => {
548
549
  if (!runDirInjected) return;
549
550
  runDirInjected = false;
550
551
  if (priorAkRoleRunDir === undefined) delete process.env.AK_ROLE_RUN_DIR;
551
552
  else process.env.AK_ROLE_RUN_DIR = priorAkRoleRunDir;
553
+ if (priorAkRoleCourtAttempt === undefined) delete process.env.AK_ROLE_COURT_ATTEMPT;
554
+ else process.env.AK_ROLE_COURT_ATTEMPT = priorAkRoleCourtAttempt;
552
555
  };
553
556
  const dispose = async (): Promise<void> => {
554
557
  if (disposed) return;
@@ -560,7 +563,7 @@ export async function prepareGrokRoleEnvelope(options: {
560
563
  cleanupFailures.push(error);
561
564
  }
562
565
  try {
563
- restoreAkRoleRunDir();
566
+ restoreAkRoleRunEnv();
564
567
  } catch (error) {
565
568
  cleanupFailures.push(error);
566
569
  }
@@ -659,7 +662,10 @@ export async function prepareGrokRoleEnvelope(options: {
659
662
  }
660
663
 
661
664
  priorAkRoleRunDir = process.env.AK_ROLE_RUN_DIR;
665
+ priorAkRoleCourtAttempt = process.env.AK_ROLE_COURT_ATTEMPT;
662
666
  process.env.AK_ROLE_RUN_DIR = request.runDirectory;
667
+ if (request.courtAttemptId === undefined) delete process.env.AK_ROLE_COURT_ATTEMPT;
668
+ else process.env.AK_ROLE_COURT_ATTEMPT = request.courtAttemptId;
663
669
  runDirInjected = true;
664
670
 
665
671
  return {
@@ -166,18 +166,11 @@ export type RoleTurnRequest = {
166
166
  /** Set by post-admission only on a real host switch; never on same-host resume. */
167
167
  readonly hostTransition?: RoleTurnHostTransition;
168
168
  /**
169
- * Grok native isolation run for ticket-seat resume (#636 / ADR 0079):
170
- * reopen grok-home under this prior run (ACP storage). Covers same-host,
171
- * same-run retry after a new run, and return-to-Grok after another host.
172
- * Absent → isolate under runDirectory. Never a cross-host byte handoff.
169
+ * Same-ticket re-summons only (#637): opens a new court-turn attempt on the
170
+ * retained run so submission-ledger sole-final is per attempt, not forever.
171
+ * Manual resume never sets this — sealed idempotent short-circuit stays intact.
173
172
  */
174
- readonly nativeHomeRunDirectory?: string;
175
- /**
176
- * Production Grok isolation notes the actual open event here after bind succeeds
177
- * (#636). Open is not the same event as turn return/throw — post-admission uses
178
- * this fact for last-host ownership instead of inferring open from the turn outcome.
179
- */
180
- readonly noteNativeHomeOpened?: (runDirectory: string) => void;
173
+ readonly courtAttemptId?: string;
181
174
  };
182
175
 
183
176
  /** Turn result — only fields upper layers currently consume. */
@@ -72,17 +72,14 @@ export async function listGrokNativeRecordPaths(runDirectory: string): Promise<s
72
72
  * Unknown host names → undefined (no inject). Empty native volume still
73
73
  * yields a typed switch (empty path list).
74
74
  *
75
- * Grok native home lives under the run that wrote it. Ticket-seat memory
76
- * cross-run switches pass `previousRunDirectory` from last-host metadata so
77
- * the scan does not look at an empty fresh run (#636 / #617 DK-4).
75
+ * Grok native home lives under the live run that wrote it (#617 DK-4 / #637
76
+ * same-run resume). No cross-run previousRunDirectory override.
78
77
  */
79
78
  export async function projectHostTransitionPriorNative(input: {
80
79
  readonly previousHost: string;
81
80
  readonly liveHost: string;
82
81
  readonly runDirectory: string;
83
82
  readonly piSessionFile: string;
84
- /** Prior run that held Grok native records when principal continued across runs. */
85
- readonly previousRunDirectory?: string;
86
83
  }): Promise<RoleTurnHostTransition | undefined> {
87
84
  if (input.previousHost === input.liveHost) return undefined;
88
85
  if (!isKnownRoleTurnHost(input.previousHost) || !isKnownRoleTurnHost(input.liveHost)) {
@@ -96,8 +93,7 @@ export async function projectHostTransitionPriorNative(input: {
96
93
  };
97
94
  }
98
95
  // previousHost === "grok-build": DK-7 path handoff only — do not read bytes.
99
- const grokRoot = input.previousRunDirectory ?? input.runDirectory;
100
- const paths = await listGrokNativeRecordPaths(grokRoot);
96
+ const paths = await listGrokNativeRecordPaths(input.runDirectory);
101
97
  return {
102
98
  previousHost: "grok-build",
103
99
  priorNativePaths: paths,
@@ -297,20 +297,4 @@ export function intervalRowsAroundAnchor(
297
297
  return { rows: rows.slice(start, end), closed: end < rows.length };
298
298
  }
299
299
 
300
- /**
301
- * Interval owned by the first binding row that satisfies `matches`.
302
- * `undefined` when the volume carries no matching binding — callers must not
303
- * silently treat the whole continuous volume as that owner.
304
- */
305
- export function intervalRowsForMatchingBinding(
306
- rows: readonly LedgerSessionRow[],
307
- isBindingRow: (row: LedgerSessionRow) => boolean,
308
- matches: (row: LedgerSessionRow) => boolean,
309
- ): BindingInterval | undefined {
310
- for (let i = 0; i < rows.length; i += 1) {
311
- const row = rows[i]!;
312
- if (!isBindingRow(row) || !matches(row)) continue;
313
- return intervalRowsAroundAnchor(rows, i, isBindingRow);
314
- }
315
- return undefined;
316
- }
300
+
@@ -634,8 +634,8 @@ export async function openPiInstitutionalSession(
634
634
  }
635
635
 
636
636
  // 7. Create AgentSession — thinking is opaque pass-through. Absent selection
637
- // omits thinkingLevel (Pi default). Pi clamps unsupported levels itself;
638
- // we do not re-check or invent defaults.
637
+ // omits thinkingLevel (Pi owns default). Pi clamps unsupported levels itself;
638
+ // we do not re-check or invent package defaults.
639
639
  const { session } = await createAgentSession({
640
640
  cwd: options.cwd,
641
641
  model: effectiveModel,
@@ -651,6 +651,10 @@ export async function openPiInstitutionalSession(
651
651
  ...(options.toolsAllowlist === undefined ? {} : { tools: options.toolsAllowlist as string[] }),
652
652
  ...(customTools.length === 0 ? {} : { customTools }),
653
653
  });
654
+ await session.setModel(effectiveModel);
655
+ if (options.selection.thinking !== undefined) {
656
+ session.setThinkingLevel(options.selection.thinking as any);
657
+ }
654
658
 
655
659
  // 8. Event subscriptions
656
660
  const listeners = new Set<(event: HostInstitutionalSessionEvent) => void>();
@@ -50,6 +50,11 @@ export function buildExplicitInternalActivationArgs(
50
50
  return ["--no-extensions", "-e", selectedRoleEntry, ...extraArgs];
51
51
  }
52
52
 
53
+ /**
54
+ * Pi argv for seat model. Host only passes through resolved values — no local
55
+ * thinking whitelist and no package default fill. Bare provider/model omits
56
+ * --thinking so Pi owns its own default (#346/#384). Explicit thinking only.
57
+ */
53
58
  function buildSeatModelCliArgs(model: RoleTurnModelConfig | undefined): string[] {
54
59
  if (model === undefined) return [];
55
60
  return [
@@ -426,12 +431,16 @@ export function createPiRoleTurnHost(config: PiRoleTurnHostConfig): RoleTurnHost
426
431
  config.extraPiArgs ?? [],
427
432
  );
428
433
  const args = buildExplicitInternalActivationArgs(roleEntry, extraArgs);
434
+ // Shared envelope isolates this call's court identity: omitting courtAttemptId
435
+ // must not inherit a parent process.env.AK_ROLE_COURT_ATTEMPT (#637).
429
436
  const env: NodeJS.ProcessEnv = {
430
437
  ...process.env,
431
438
  HOME: request.home,
432
439
  PI_CODING_AGENT_DIR: request.agentDir,
433
440
  AK_ROLE_RUN_DIR: request.runDirectory,
434
441
  };
442
+ if (request.courtAttemptId === undefined) delete env.AK_ROLE_COURT_ATTEMPT;
443
+ else env.AK_ROLE_COURT_ATTEMPT = request.courtAttemptId;
435
444
  applyEngineChildEnv(env, request.engine);
436
445
  if (request.correlationId !== undefined && request.correlationId.trim() !== "") {
437
446
  env.AK_CORRELATION_ID = request.correlationId;
@@ -160,16 +160,16 @@ export async function runPublicCollectorResume(
160
160
  request,
161
161
  env,
162
162
  io,
163
- load: () =>
163
+ load: (effective) =>
164
164
  loadResumableCollectorRun(
165
165
  env.home,
166
- request.runId,
166
+ effective.runId,
167
167
  env.principalAuthority,
168
168
  ),
169
- buildTurnRequest: (admitted) =>
169
+ buildTurnRequest: (admitted, effective) =>
170
170
  buildCollectorTurnRequest(
171
171
  admitted,
172
- resumeTurnRequestProjectionOptions(admitted, request, env),
172
+ resumeTurnRequestProjectionOptions(admitted, effective, env),
173
173
  ),
174
174
  adapters: collectorAdapters(),
175
175
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),