@akagilnc/pi-workflow-roles 0.1.5614 → 0.1.5650

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 (35) hide show
  1. package/dist/acp-host/production-host.js +960 -465
  2. package/dist/activation-ledger-topology.js +12 -0
  3. package/dist/external-host-turn-loop.js +281 -0
  4. package/dist/headless-host/production-host.js +900 -406
  5. package/dist/migrate-book-topology.js +8 -5
  6. package/dist/pi/role-turn-host.js +33 -3
  7. package/dist/public-cli/instruction-seat-run.js +177 -46
  8. package/dist/public-cli/invocation.js +5 -12
  9. package/dist/public-cli/main.js +541 -165
  10. package/dist/public-cli/post-admission.js +171 -18
  11. package/dist/public-cli/settlement.js +20 -10
  12. package/dist/report-ticket-tool.js +136 -0
  13. package/dist/role-run-placement.js +1 -2
  14. package/dist/role-run-relocation.js +24 -4
  15. package/package.json +1 -1
  16. package/resources/engine-dispatch.md +6 -3
  17. package/resources/engines/claude-code.md +4 -5
  18. package/resources/missing-ticket-reask.md +1 -0
  19. package/src/acp-host/role-turn-host.ts +41 -31
  20. package/src/activation-ledger-topology.ts +16 -0
  21. package/src/external-host-turn-loop.ts +103 -12
  22. package/src/headless-host/role-turn-host.ts +41 -25
  23. package/src/pi/role-turn-host.ts +37 -3
  24. package/src/public-cli/instruction-seat-run.ts +248 -50
  25. package/src/public-cli/invocation.ts +15 -27
  26. package/src/public-cli/post-admission.ts +250 -33
  27. package/src/public-cli/run-lifecycle.ts +5 -5
  28. package/src/public-cli/settlement.ts +38 -11
  29. package/src/public-cli/terminal.ts +6 -0
  30. package/src/report-ticket-tool.ts +176 -0
  31. package/src/role-envelope.ts +28 -3
  32. package/src/role-run-placement.ts +0 -2
  33. package/src/role-run-relocation.ts +45 -4
  34. package/src/role-runtime.ts +6 -0
  35. package/src/worker-submission-gates.ts +26 -3
@@ -1365,8 +1365,7 @@ function roleRunPlacement(ledgerHome, input) {
1365
1365
  return {
1366
1366
  runDirectory,
1367
1367
  sessionDirectory,
1368
- sessionFile: sessionFileIn(sessionDirectory),
1369
- attachmentsDirectory: join5(runDirectory, "attachments")
1368
+ sessionFile: sessionFileIn(sessionDirectory)
1370
1369
  };
1371
1370
  }
1372
1371
  function isUnboundRunDirectory(runDirectory) {
@@ -9993,7 +9992,7 @@ function rewriteAdmittedRoleRunPage(page, rewrites) {
9993
9992
  if (isRecord(page.principal)) {
9994
9993
  rewriteRunDirectoryPathFieldsAgainstRewrites(
9995
9994
  page.principal,
9996
- ["sessionDirectory", "sessionFile"],
9995
+ PRINCIPAL_SESSION_PATH_FIELDS,
9997
9996
  rewrites
9998
9997
  );
9999
9998
  }
@@ -10166,7 +10165,7 @@ async function rewriteRoleRunDurablePages(input) {
10166
10165
  if (isRecord(page.principal)) {
10167
10166
  rewriteRunDirectoryPathFieldsAgainstRewrites(
10168
10167
  page.principal,
10169
- ["sessionDirectory", "sessionFile"],
10168
+ PRINCIPAL_SESSION_PATH_FIELDS,
10170
10169
  rewrites
10171
10170
  );
10172
10171
  }
@@ -10206,7 +10205,7 @@ async function rewriteRoleRunDurablePages(input) {
10206
10205
  }
10207
10206
  await rewriteNestedMachinePathPages(pagesDirectory, rewrites);
10208
10207
  }
10209
- var ADMITTED_PAGE_FIELDS, INVOCATION_PAGE_FIELDS, RUN_STATE_PAGE_FIELDS, SOURCE_RUN_LOCATOR_FIELDS, SUMMONS_PATH_FIELDS, OFFICER_POINTER_FIELDS, SITIAN_RECORD_FIELDS, SESSION_HEADER_FIELDS, BINDING_PARENT_FIELDS;
10208
+ var ADMITTED_PAGE_FIELDS, INVOCATION_PAGE_FIELDS, RUN_STATE_PAGE_FIELDS, PRINCIPAL_SESSION_PATH_FIELDS, SOURCE_RUN_LOCATOR_FIELDS, SUMMONS_PATH_FIELDS, OFFICER_POINTER_FIELDS, SITIAN_RECORD_FIELDS, SESSION_HEADER_FIELDS, BINDING_PARENT_FIELDS;
10210
10209
  var init_role_run_relocation = __esm({
10211
10210
  "src/role-run-relocation.ts"() {
10212
10211
  "use strict";
@@ -10240,6 +10239,10 @@ var init_role_run_relocation = __esm({
10240
10239
  "sessionDirectory",
10241
10240
  "sessionFile"
10242
10241
  ];
10242
+ PRINCIPAL_SESSION_PATH_FIELDS = [
10243
+ "sessionDirectory",
10244
+ "sessionFile"
10245
+ ];
10243
10246
  SOURCE_RUN_LOCATOR_FIELDS = ["runDirectory"];
10244
10247
  SUMMONS_PATH_FIELDS = ["sourceRunPath"];
10245
10248
  OFFICER_POINTER_FIELDS = ["sessionFile", "runDirectory"];
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { execFile, spawn } from "node:child_process";
7
7
  import { constants } from "node:fs";
8
+ import { existsSync } from "node:fs";
8
9
  import { access, appendFile, realpath } from "node:fs/promises";
9
10
  import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
10
11
  import { platform } from "node:process";
@@ -16,6 +17,8 @@ import { projectActivationFlags } from "../role-activation-flags.js";
16
17
  import { encodeUserDialogueStdin } from "../user-dialogue-stdin.js";
17
18
  import { readLedgerSessionJsonlLines, readStrictPiSessionJsonl } from "../ledger-session-read.js";
18
19
  import { copyAndRecordHostDossier } from "../host-session-record.js";
20
+ import { resolveLiveRunDirectoryPath, retainPackageFaultBesideLivePlacement, syncTurnRequestLivePlacement, } from "../external-host-turn-loop.js";
21
+ import { homeFromRunDirectory } from "../activation-ledger-topology.js";
19
22
  import { projectThrownFailureLeaf, retainPackageFault } from "../public-cli/settlement.js";
20
23
  import { describeErrorIdentity } from "../public-cli/run-lifecycle.js";
21
24
  import { RECEIPT_DELIVERY_LIMIT_ENV } from "../receipt-delivery-policy.js";
@@ -264,7 +267,34 @@ export function createDefaultPiSpawnRunner(options) {
264
267
  packageErrors.push(stdinDeliveryError);
265
268
  for (const error of packageErrors) {
266
269
  const diagnostic = `Pi transport handling failed beside host terminal: ${describeErrorIdentity(error)}`;
267
- const runDirectory = spawnOptions.env.AK_ROLE_RUN_DIR;
270
+ // #1171 F4: spawn-time path may have been renamed; reuse
271
+ // resolveLiveRunDirectoryPath (same authority as syncTurnRequestLivePlacement).
272
+ const spawnDir = spawnOptions.env.AK_ROLE_RUN_DIR;
273
+ let runDirectory;
274
+ if (typeof spawnDir === "string" && spawnDir.trim() !== "") {
275
+ try {
276
+ runDirectory = await resolveLiveRunDirectoryPath(spawnDir, homeFromRunDirectory(spawnDir));
277
+ }
278
+ catch (resolveError) {
279
+ // Resolve threw beside an already-closed child: keep both causes
280
+ // on the existing retain/stderr presentation path (失败诚实).
281
+ runDirectory = existsSync(spawnDir) ? spawnDir : undefined;
282
+ const resolveDiagnostic = `live run path resolve failed beside Pi transport fault: ${describeErrorIdentity(resolveError)}`;
283
+ if (runDirectory !== undefined) {
284
+ await retainPackageFault({
285
+ runDirectory,
286
+ diagnostic: resolveDiagnostic,
287
+ error: resolveError,
288
+ });
289
+ }
290
+ else {
291
+ try {
292
+ process.stderr.write(`${resolveDiagnostic}\n`);
293
+ }
294
+ catch { /* Best-effort presentation beside the already closed child. */ }
295
+ }
296
+ }
297
+ }
268
298
  if (runDirectory !== undefined) {
269
299
  await retainPackageFault({ runDirectory, diagnostic, error });
270
300
  }
@@ -374,6 +404,7 @@ export function createPiRoleTurnHost(config) {
374
404
  // bind projects host.sessions; copy records the landing. Host adapters do
375
405
  // not write current.json — public bind/settlement seams own that render.
376
406
  try {
407
+ await syncTurnRequestLivePlacement(request);
377
408
  const { sessionFile, sessionDirectory } = config.principalAuthority.decode(request.principal);
378
409
  const sessionId = await readPiSessionHeaderId(sessionFile);
379
410
  if (sessionId !== undefined) {
@@ -389,8 +420,7 @@ export function createPiRoleTurnHost(config) {
389
420
  }
390
421
  }
391
422
  catch (error) {
392
- await retainPackageFault({
393
- runDirectory: request.runDirectory,
423
+ await retainPackageFaultBesideLivePlacement(request, {
394
424
  diagnostic: `pi host dossier record failed beside host terminal: ${describeErrorIdentity(error)}`,
395
425
  error,
396
426
  });
@@ -4,7 +4,10 @@
4
4
  * starts two ordinary single-axis runs here.
5
5
  */
6
6
  import { dirname, resolve } from "node:path";
7
+ import { MISSING_TICKET_REASK_MATERIAL } from "../report-ticket-tool.js";
7
8
  import { isUnboundRunDirectory, sessionFileOf } from "../role-run-placement.js";
9
+ import { rewriteRunDirectoryPathValue } from "../role-run-relocation.js";
10
+ import { readPackageMaterial } from "../session-opening-materials.js";
8
11
  import { bookDirectOfficerRunPointer, readDirectOfficerRunPointer } from "../archivist-record-pointer.js";
9
12
  import { createPiDoctorAuditor } from "../doctor-auditor.js";
10
13
  import { officerConclusionReask, gateOfficerForSubject } from "../gatekeeper-role.js";
@@ -21,7 +24,7 @@ import { readRecordedSubmissionRows } from "../submission-ledger.js";
21
24
  import { summonPublicRole } from "../public-role-summons.js";
22
25
  import { sessionFileFromPublicSummon } from "../session-assistant-usage.js";
23
26
  import { latestQueuePayload, latestQueueStatus, } from "../submission-gate.js";
24
- import { isSafePositiveTicketNumber, readBoardTicketNumber } from "../run-ticket-number.js";
27
+ import { isSafePositiveTicketNumber, readBoardTicketNumber, } from "../run-ticket-number.js";
25
28
  import { NotarySourceRunError, resolveNotarySourceRunLocator } from "../notary-source-run.js";
26
29
  import { packagedAdmitsCountersign, packagedBindsBoardTicket, packagedRebindSourceOnResume, packagedRoleMetadata, } from "../packaged-role-registry.js";
27
30
  import { readAuditorResumeBinding } from "../auditor-soul.js";
@@ -86,11 +89,21 @@ function unreadablePostSubmissionStatus(admitted, terminal) {
86
89
  * status: the caller presents the volumes already in hand and does not advance
87
90
  * review. ADR 0007 audit continues are not this shape.
88
91
  */
92
+ /**
93
+ * Drop turn-scoped soft-reask identity; keep cross-turn budgets and ownership cell.
94
+ * #1171 F2-R5 / F2-R9: ordinary status reask and court continue must not inherit
95
+ * softTicketReaskTurn (that flag only governs the soft-reask turn's own settle).
96
+ */
97
+ function withoutSoftTicketReaskTurn(env) {
98
+ const { softTicketReaskTurn: _softTicketReaskTurn, ...rest } = env;
99
+ return rest;
100
+ }
89
101
  function withUnreadableReask(env) {
90
102
  const spent = env.unreadableReasksSpent ?? 0;
91
103
  if (spent >= deliveryLimitFromConfig(env.autoResumeLimit))
92
104
  return undefined;
93
- return { ...env, unreadableReasksSpent: spent + 1 };
105
+ // Ordinary status reask: strip soft-reask turn identity; keep spent budget (#1171 F2-R9).
106
+ return { ...withoutSoftTicketReaskTurn(env), unreadableReasksSpent: spent + 1 };
94
107
  }
95
108
  /** Budget spent on an unreadable status. Distinct from "status is readable". */
96
109
  const UNREADABLE_REASK_EXHAUSTED = { unreadableReaskExhausted: true };
@@ -156,6 +169,146 @@ async function reaskUnreadablePostSubmissionStatus(admitted, terminal, env, io,
156
169
  return UNREADABLE_REASK_EXHAUSTED;
157
170
  return runPublicInstructionSeatResume({ runId: admitted.runId, summons: { instruction: message } }, next, io);
158
171
  }
172
+ /**
173
+ * #1171: sealed submission still leaves the leg unbound → soft reask once.
174
+ * Does not reject the sealed receipt; exhaustion leaves unbound as filed.
175
+ * Reask copy lives in package resources (ADR 0073).
176
+ * #1171 F2-R7: ownership comes from this nested resume's settlement seam
177
+ * (softTicketReaskOwnership.sealedSubstitute set when the soft-reask turn itself seals),
178
+ * not full-leg sealed count / time-window deltas that can absorb another court's seal.
179
+ */
180
+ async function reaskMissingTicketOnce(admitted, terminal, env, io) {
181
+ if ((env.ticketReasksSpent ?? 0) >= 1)
182
+ return { kind: "none" };
183
+ if (admitted.ticketNumber !== undefined)
184
+ return { kind: "none" };
185
+ if (!isUnboundRunDirectory(admitted.runDirectory))
186
+ return { kind: "none" };
187
+ if (terminal?.roleOutcome.kind !== "accepted" && terminal?.roleOutcome.kind !== "audit_escalation") {
188
+ return { kind: "none" };
189
+ }
190
+ const payloads = terminal.roleOutcome.payloads ?? [];
191
+ if (payloads.length === 0)
192
+ return { kind: "none" };
193
+ // Sealed-with-ticket skips via admitted.ticketNumber after bindSealedTicketNumber
194
+ // (post-admission). Do not re-parse payload.ticketNumber here — that would
195
+ // duplicate the existing ticket seam (#1171 notary: no rule copy).
196
+ const instruction = (await readPackageMaterial(MISSING_TICKET_REASK_MATERIAL)).trim();
197
+ // Spend budget and mark this turn as the soft reask (settlement identity).
198
+ // Ownership cell is set at that turn's settlement; env spreads keep the same
199
+ // ref through audit continue — see envForCourtContinue (#1171 F2-R5 / F2-R7).
200
+ const softTicketReaskOwnership = { sealedSubstitute: false };
201
+ const softReaskEnv = {
202
+ ...env,
203
+ ticketReasksSpent: 1,
204
+ softTicketReaskTurn: true,
205
+ softTicketReaskOwnership,
206
+ };
207
+ const result = await runPublicInstructionSeatResume({ runId: admitted.runId, summons: { instruction } }, softReaskEnv, io);
208
+ if (softTicketReaskOwnership.sealedSubstitute
209
+ || terminalCarriesSealedSubmission(result.terminal)) {
210
+ return { kind: "nested_owns", result };
211
+ }
212
+ return { kind: "no_substitute", result };
213
+ }
214
+ /** Court continue: keep cross-turn budgets + ownership cell; drop turn-scoped soft-reask identity. */
215
+ function envForCourtContinue(env) {
216
+ return { ...withoutSoftTicketReaskTurn(env), unreadableReasksSpent: 0 };
217
+ }
218
+ /** True when a terminal still carries a sealed accepted / audit_escalation volume. */
219
+ function terminalCarriesSealedSubmission(terminal) {
220
+ const kind = terminal?.roleOutcome.kind;
221
+ if (kind !== "accepted" && kind !== "audit_escalation")
222
+ return false;
223
+ return (terminal?.roleOutcome.payloads ?? []).length > 0;
224
+ }
225
+ /**
226
+ * #1171 F2-R2: kept original terminal may still name pre-relocate artifact
227
+ * paths after a report-only reask moved the leg. Project onto live admitted
228
+ * via the same rewrite seam post-admission uses after relocate.
229
+ */
230
+ function projectKeptOriginalArtifactsOntoLiveAdmitted(original, kept) {
231
+ const oldRunDirectory = original.admitted?.runDirectory;
232
+ const newRunDirectory = kept.admitted?.runDirectory;
233
+ if (oldRunDirectory === undefined
234
+ || newRunDirectory === undefined
235
+ || oldRunDirectory === newRunDirectory
236
+ || kept.terminal === undefined) {
237
+ return;
238
+ }
239
+ for (const artifact of kept.terminal.artifacts) {
240
+ artifact.path = rewriteRunDirectoryPathValue(artifact.path, oldRunDirectory, newRunDirectory);
241
+ }
242
+ }
243
+ /**
244
+ * #1171 F2: missing-ticket reask did not seal a substitute — keep original
245
+ * terminal; take live placement from the reask when it relocated.
246
+ * #1171 F2-R1: silence / report-only stays on the original chain; a real host
247
+ * failure (nonzero exit) must be delivered honestly — do not wash it into the
248
+ * original accepted result. Sealed original volume remains on disk either way.
249
+ * #1171 F2-R2: after keep, project report artifact paths onto the live
250
+ * admitted directory so public result, durable face, and artifact refs agree.
251
+ * Substitute ownership is decided once at the soft-reask call seam (F2-R6);
252
+ * this helper only finishes the keep path.
253
+ */
254
+ function mergeMissingTicketReaskKeepOriginal(original, ticketReask) {
255
+ if (ticketReask.exitCode !== 0)
256
+ return ticketReask;
257
+ const admitted = ticketReask.admitted ?? original.admitted;
258
+ if (admitted === undefined)
259
+ return { ...original };
260
+ const kept = { ...original, admitted };
261
+ projectKeptOriginalArtifactsOntoLiveAdmitted(original, kept);
262
+ return kept;
263
+ }
264
+ /**
265
+ * Post-submission status reask, then missing-ticket soft reask, then audit.
266
+ * #1171 F3: status exhaustion must not skip the one missing-ticket reask.
267
+ * #1171 F2: ticket reask without a substitute seal continues the original chain.
268
+ */
269
+ async function continueAfterPostSubmissionGuards(result, admittedRole, env, io, held) {
270
+ const live = result.admitted;
271
+ if (live === undefined) {
272
+ await presentOriginalVolume(held, result.terminal, io);
273
+ return result;
274
+ }
275
+ const unreadableReask = await reaskUnreadablePostSubmissionStatus(live, result.terminal, env, io);
276
+ let afterStatus = result;
277
+ if (isUnreadableReaskExhausted(unreadableReask)) {
278
+ const terminal = result.terminal === undefined
279
+ ? undefined
280
+ : withUnsettledDirection(result.terminal);
281
+ afterStatus = terminal === undefined
282
+ ? { ...result, admitted: live }
283
+ : { ...result, admitted: live, terminal };
284
+ // Fall through: missing-ticket reask still owed on the original sealed volume.
285
+ }
286
+ else if (unreadableReask !== undefined) {
287
+ return unreadableReask;
288
+ }
289
+ const forTicket = afterStatus.admitted ?? live;
290
+ // Ownership is decided once inside reaskMissingTicketOnce (#1171 F2-R6).
291
+ const softReask = await reaskMissingTicketOnce(forTicket, afterStatus.terminal ?? result.terminal, env, io);
292
+ if (softReask.kind === "nested_owns") {
293
+ // Nested resume already finished guards + audit/present for the substitute.
294
+ // Do not run a second outer settlement of the same public call (#1171 F2-R4b).
295
+ return softReask.result;
296
+ }
297
+ const continued = softReask.kind === "none"
298
+ ? afterStatus
299
+ : mergeMissingTicketReaskKeepOriginal(afterStatus, softReask.result);
300
+ const continuedAdmitted = continued.admitted ?? live;
301
+ // Spent budget rides the outer audit/continue chain (#1171 F2-R4a). Soft-reask
302
+ // settlement identity does not — ordinary continue turns publish no_receipt (#1171 F2-R5).
303
+ const continuedEnv = softReask.kind !== "none"
304
+ ? { ...env, ticketReasksSpent: 1 }
305
+ : env;
306
+ if (AUDITED_ROLES.has(admittedRole) && continued.terminal?.roleOutcome.kind === "accepted") {
307
+ return auditSubmittedRole({ ...continued, admitted: continuedAdmitted }, continuedEnv, io);
308
+ }
309
+ await presentOriginalVolume(held, continued.terminal, io, continuedAdmitted.runDirectory);
310
+ return { ...continued, admitted: continuedAdmitted };
311
+ }
159
312
  function roleRecord(role) {
160
313
  const record = packagedRoleMetadata(role);
161
314
  if (record === undefined) {
@@ -360,21 +513,9 @@ async function dispatchAdmitted(admitted, env, io) {
360
513
  });
361
514
  };
362
515
  const result = await execute(env);
363
- const unreadableReask = await reaskUnreadablePostSubmissionStatus(admitted, result.terminal, env, io);
364
- if (isUnreadableReaskExhausted(unreadableReask)) {
365
- const terminal = result.terminal === undefined
366
- ? undefined
367
- : withUnsettledDirection(result.terminal);
368
- await presentOriginalVolume(held, terminal, io, admitted.runDirectory);
369
- return terminal === undefined ? result : { ...result, terminal };
370
- }
371
- if (unreadableReask !== undefined)
372
- return unreadableReask;
373
- if (AUDITED_ROLES.has(admitted.role) && result.terminal?.roleOutcome.kind === "accepted") {
374
- return auditSubmittedRole(result, env, io);
375
- }
376
- await presentOriginalVolume(held, result.terminal, io, admitted.runDirectory);
377
- return result;
516
+ // Prefer the live admitted object returned from the turn (may have relocated).
517
+ const live = result.admitted ?? admitted;
518
+ return continueAfterPostSubmissionGuards({ ...result, admitted: live }, admitted.role, env, io, held);
378
519
  }
379
520
  /**
380
521
  * Omitted lens on a parallel-lens seat: two ordinary single-axis runs, no parent run.
@@ -594,10 +735,9 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
594
735
  const result = await runCountersignBody(parsed, env, {
595
736
  stdout: (value) => held.push(value), stderr: io.stderr,
596
737
  });
597
- if (result.terminal?.roleOutcome.kind === "accepted")
598
- return auditSubmittedRole(result, env, io);
599
- await presentOriginalVolume(held, result.terminal, io, result.admitted?.runDirectory);
600
- return result;
738
+ // Same post-submission guards as every other seat (#1171 F8): missing-ticket
739
+ // soft reask once, then audit — no early return that skips the reask.
740
+ return continueAfterPostSubmissionGuards(result, "countersign", env, io, held);
601
741
  }
602
742
  if ("parallelLenses" in record && record.parallelLenses === true) {
603
743
  if (parsed.baseRevision === undefined) {
@@ -842,24 +982,11 @@ export async function runPublicInstructionSeatResume(request, env, io) {
842
982
  },
843
983
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
844
984
  });
845
- const unreadableReask = result.admitted === undefined
846
- ? undefined
847
- : await reaskUnreadablePostSubmissionStatus(result.admitted, result.terminal, env, io);
848
- if (isUnreadableReaskExhausted(unreadableReask)) {
849
- const terminal = result.terminal === undefined
850
- ? undefined
851
- : withUnsettledDirection(result.terminal);
852
- await presentOriginalVolume(held, terminal, io, result.admitted?.runDirectory);
853
- return terminal === undefined ? result : { ...result, terminal };
854
- }
855
- if (unreadableReask !== undefined)
856
- return unreadableReask;
857
- if (result.admitted !== undefined && AUDITED_ROLES.has(result.admitted.role)
858
- && result.terminal?.roleOutcome.kind === "accepted") {
859
- return auditSubmittedRole(result, env, io);
985
+ if (result.admitted === undefined) {
986
+ await presentOriginalVolume(held, result.terminal, io);
987
+ return result;
860
988
  }
861
- await presentOriginalVolume(held, result.terminal, io, result.admitted?.runDirectory);
862
- return result;
989
+ return continueAfterPostSubmissionGuards(result, result.admitted.role, env, io, held);
863
990
  };
864
991
  let binding;
865
992
  try {
@@ -905,7 +1032,7 @@ async function queueConclusionFromChild(child, env, io) {
905
1032
  // Notary, auditor, and inspector have no seat-local status reask. This loop
906
1033
  // is their one budget. The child resume keeps the caller's env so delivery,
907
1034
  // failure recovery, and other reask loops stay separate.
908
- let budgetEnv = { ...env, unreadableReasksSpent: 0 };
1035
+ let budgetEnv = envForCourtContinue(env);
909
1036
  for (;;) {
910
1037
  const terminal = await trySettlePublicSeat(current, env.principalAuthority, { ...await readCurrentCourt(current.runDirectory), ...packageFaultScope(current, env, io) });
911
1038
  const status = latestQueueStatus(terminal);
@@ -966,7 +1093,7 @@ export async function continueParentAfterChild(parentRunId, child, env, io) {
966
1093
  return runPublicInstructionSeatResume({
967
1094
  runId: parentRunId,
968
1095
  summons: { instruction: readableGateItem(latestQueuePayload(resolved.terminal)) },
969
- }, { ...env, unreadableReasksSpent: 0 }, io);
1096
+ }, envForCourtContinue(env), io);
970
1097
  }
971
1098
  const result = await dispatchAdmitted(admitted, env, { ...io, omitFailureStderrDiagnostic: true });
972
1099
  showResumeErrorPointer(io, result.exitCode, result.terminal);
@@ -1005,6 +1132,8 @@ async function officerPassedCurrentSubmission(input) {
1005
1132
  }
1006
1133
  /** Finished submissions enter the existing audit gate after their tool call has returned. */
1007
1134
  async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt) {
1135
+ // Ordinary audit chain: drop soft-reask turn identity; keep budgets/ownership (#1171 F2-R9).
1136
+ env = withoutSoftTicketReaskTurn(env);
1008
1137
  const admitted = turn.admitted;
1009
1138
  if (admitted === undefined || !AUDITED_ROLES.has(admitted.role)
1010
1139
  || turn.terminal?.roleOutcome.kind !== "accepted")
@@ -1053,11 +1182,13 @@ async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt)
1053
1182
  await presentTerminal(turn.terminal, { ...io, omitFailureStderrDiagnostic: true }, admitted.runDirectory);
1054
1183
  return turn;
1055
1184
  }
1056
- const rows = await readRecordedSubmissionRows(admitted.projectRoot, admitted.runId, env.home);
1057
- const acceptedRow = [...rows].reverse().find((row) => row.role === admitted.role && row.kind === "accepted");
1058
- const toolCallId = acceptedRow?.toolCallId;
1059
- if (toolCallId === undefined)
1185
+ // #1171 F2-R8: bind audit to the sealed identity carried on this turn's
1186
+ // settlement terminal — never the whole-leg latest accepted row (another
1187
+ // lawful public call may have sealed and passed台院 meanwhile).
1188
+ const toolCallId = turn.terminal.submissionToolCallId;
1189
+ if (toolCallId === undefined || toolCallId.length === 0) {
1060
1190
  throw new Error("accepted submission has no tool call identity");
1191
+ }
1061
1192
  const sessionFile = env.principalAuthority.decode(admitted.principal).sessionFile;
1062
1193
  const entries = admitted.role === "doctor"
1063
1194
  ? await readBoundSessionEntries(sessionFile)
@@ -1118,7 +1249,7 @@ async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt)
1118
1249
  if (decision.status === "continue") {
1119
1250
  return runPublicInstructionSeatResume({
1120
1251
  runId: admitted.runId, summons: { instruction: readableGateItem(decision.receipt ?? decision.violations) },
1121
- }, { ...env, unreadableReasksSpent: 0 }, io);
1252
+ }, envForCourtContinue(env), io);
1122
1253
  }
1123
1254
  if (decision.status === "received") {
1124
1255
  if (lastSummon?.terminal === undefined) {
@@ -1224,7 +1355,7 @@ async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt)
1224
1355
  return runPublicInstructionSeatResume({
1225
1356
  runId: admitted.runId,
1226
1357
  summons: { instruction: readableGateItem(chain.passes.at(-1)?.receipt) },
1227
- }, { ...env, unreadableReasksSpent: 0 }, io);
1358
+ }, envForCourtContinue(env), io);
1228
1359
  }
1229
1360
  if (admitted.role === "secretariat") {
1230
1361
  const pass = chain.passes.at(-1);
@@ -11,7 +11,7 @@ import { resolveBookKeyFromGit } from "../activation-ledger-git.js";
11
11
  import { ensureRoleRunDirectory, ensureRoleRunPlacement, formatRunLeaf, isUnboundRunDirectory, listBookRunDirectories, roleRunPlacement, } from "../role-run-placement.js";
12
12
  import { packagedAdmittedSubject, packagedArgvResult, packagedBareToken, packagedBaseIsRevision, packagedEmitsAuthorityRefs, packagedPublicInstructionSubject, packagedRoleMetadata, packagedStoresSourceRunRaw, packagedSubjectChoices, } from "../packaged-role-registry.js";
13
13
  import { isSafePositiveTicketNumber, parseTicketNumber, readBoardTicketNumber, requireSafePositiveTicketNumber, } from "../run-ticket-number.js";
14
- import { rewriteRunDirectoryPathFields, } from "../role-run-relocation.js";
14
+ import { rewriteAdmittedRoleRunPage, } from "../role-run-relocation.js";
15
15
  import { readPageSync, renderCurrentSync, updateSectionSync, writeSectionSync } from "../run-dossier.js";
16
16
  import { loadDoctorCase, } from "../doctor-evidence.js";
17
17
  import { parseCollectorPrNumber, parseCollectorRepository, } from "../collector-config.js";
@@ -267,13 +267,7 @@ export async function relocateAdmittedRunToTicket(admitted, authority, heldLease
267
267
  // admitted path ownership immediately after the commit — before any derived
268
268
  // render that may refuse (#1161 L1 / BASE order).
269
269
  heldLease?.relocate(target.runDirectory);
270
- const admittedRecord = admitted;
271
- rewriteRunDirectoryPathFields(admittedRecord, [
272
- "runDirectory",
273
- "mergerInputPath",
274
- ], oldRunDirectory, target.runDirectory);
275
- // #1165/#1168: caller file-flag paths stay as given. Obsolete copy fields
276
- // (taskPath/packetPath) are not rewritten — stock volumes keep their bytes.
270
+ rewriteAdmittedRoleRunPage(admitted, [{ oldRunDirectory, newRunDirectory: target.runDirectory }]);
277
271
  admitted.principal = principal;
278
272
  // current.json projects host.original from this run directory. Ownership is
279
273
  // already transferred; a refuse still carries the true cause to the caller
@@ -969,7 +963,7 @@ async function placeRoleAdmission(options) {
969
963
  const projectRoot = resolve(options.project ?? options.cwd);
970
964
  const runId = options.runId ?? (options.createRunId ?? uuidv7)();
971
965
  const ticketFields = ticketAdmissionFields(options.assertedTicketNumber);
972
- const { principal, sessionDirectory, sessionFile, runDirectory, attachmentsDirectory, ledgerHome, bookKey, } = issueAdmissionPlacement(options.principalAuthority, {
966
+ const { principal, sessionDirectory, sessionFile, runDirectory, ledgerHome, bookKey, } = issueAdmissionPlacement(options.principalAuthority, {
973
967
  cwd: projectRoot,
974
968
  runId,
975
969
  role: options.role,
@@ -989,7 +983,6 @@ async function placeRoleAdmission(options) {
989
983
  sessionDirectory,
990
984
  sessionFile,
991
985
  attachments,
992
- attachmentsDirectory,
993
986
  ledgerHome,
994
987
  ticketFields,
995
988
  };
@@ -1009,8 +1002,8 @@ async function persistPlacedAdmission(admitted, placed, model) {
1009
1002
  }, admitted.role, model);
1010
1003
  }
1011
1004
  /**
1012
- * Shared admission: project check, placement, attachment freeze,
1013
- * admitted and invocation section write.
1005
+ * Shared admission: project check, placement, admitted and invocation section write.
1006
+ * Caller file-flag paths are recorded opaque on the admitted page (ADR 0087).
1014
1007
  * Countersign passes deferPersistence so same-ticket lookup can reserve coordinates
1015
1008
  * before materializeCountersignInvocation writes the page.
1016
1009
  * Seats whose extra facts are known before placement pass them as admittedFields.