@akagilnc/pi-workflow-roles 0.1.5504 → 0.1.5575

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 (89) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/acp-host/production-host.js +5965 -5994
  4. package/dist/analyst-gate-cycles-read.js +23 -55
  5. package/dist/analyst-ledger.js +21 -35
  6. package/dist/analyst-metric-families/acceptance-success-rework.js +25 -1
  7. package/dist/analyst-metric-families/round-timeline.js +2 -5
  8. package/dist/archivist-record-pointer.js +38 -30
  9. package/dist/auditor-dossier-tool.js +4 -3
  10. package/dist/auditor-soul.js +3 -11
  11. package/dist/doctor-evidence.js +3 -3
  12. package/dist/engine-detour-usage.js +6 -14
  13. package/dist/headless-host/production-host.js +5316 -5324
  14. package/dist/host-descriptions.js +0 -4
  15. package/dist/host-session-record.js +266 -267
  16. package/dist/migrate-book-topology.js +1314 -696
  17. package/dist/notary-source-run.js +1 -1
  18. package/dist/packaged-role-registry.js +5 -44
  19. package/dist/pi/role-turn-host.js +53 -3
  20. package/dist/public-cli/auto-resume.js +8 -47
  21. package/dist/public-cli/instruction-seat-run.js +5 -3
  22. package/dist/public-cli/invocation.js +51 -56
  23. package/dist/public-cli/main.js +1916 -1748
  24. package/dist/public-cli/post-admission.js +96 -55
  25. package/dist/public-cli/run-lifecycle.js +27 -53
  26. package/dist/public-cli/settlement.js +157 -302
  27. package/dist/role-run-placement.js +1 -11
  28. package/dist/role-run-relocation.js +72 -50
  29. package/dist/run-dossier-files.js +30 -0
  30. package/dist/run-dossier.js +322 -0
  31. package/dist/run-terminal-artifacts.js +68 -197
  32. package/dist/run-ticket-number.js +16 -3
  33. package/dist/session-identity.js +49 -18
  34. package/dist/sitian-appender.js +13 -16
  35. package/dist/sitian-facade.js +11 -0
  36. package/dist/sitian-reader.js +44 -29
  37. package/dist/submission-gate.js +1 -1
  38. package/dist/ticket-provenance.js +22 -7
  39. package/package.json +1 -1
  40. package/scripts/build-package.mjs +2 -0
  41. package/src/acp-host/production-host.ts +1 -1
  42. package/src/acp-host/role-turn-host.ts +9 -2
  43. package/src/analyst-gate-cycles-read.ts +26 -60
  44. package/src/analyst-ledger.ts +27 -42
  45. package/src/analyst-metric-families/acceptance-success-rework.ts +24 -1
  46. package/src/analyst-metric-families/gate-cycles.ts +1 -1
  47. package/src/analyst-metric-families/round-timeline.ts +2 -5
  48. package/src/analyst-page.ts +3 -3
  49. package/src/archivist-record-entry.ts +2 -2
  50. package/src/archivist-record-pointer.ts +50 -36
  51. package/src/auditor-dossier-tool.ts +7 -5
  52. package/src/auditor-soul.ts +3 -9
  53. package/src/book-topology-migration-placement.ts +16 -3
  54. package/src/book-topology-record-class-migrators.ts +4 -2
  55. package/src/book-topology-runs-migrator.ts +6 -3
  56. package/src/doctor-contracts.ts +1 -1
  57. package/src/doctor-evidence.ts +2 -2
  58. package/src/engine-detour-tool.ts +1 -1
  59. package/src/engine-detour-usage.ts +6 -16
  60. package/src/external-host-turn-loop.ts +25 -0
  61. package/src/headless-host/production-host.ts +1 -4
  62. package/src/headless-host/role-turn-host.ts +54 -20
  63. package/src/host-descriptions.ts +0 -6
  64. package/src/host-session-record.ts +224 -140
  65. package/src/notary-source-run.ts +1 -1
  66. package/src/packaged-role-registry.ts +1 -50
  67. package/src/pi/adapter.ts +50 -7
  68. package/src/pi/role-turn-host.ts +51 -3
  69. package/src/public-cli/auto-resume.ts +7 -43
  70. package/src/public-cli/instruction-seat-run.ts +9 -2
  71. package/src/public-cli/invocation.ts +61 -87
  72. package/src/public-cli/post-admission.ts +111 -72
  73. package/src/public-cli/run-lifecycle.ts +27 -72
  74. package/src/public-cli/settlement.ts +193 -418
  75. package/src/public-cli/terminal.ts +1 -1
  76. package/src/role-run-placement.ts +2 -13
  77. package/src/role-run-relocation.ts +71 -68
  78. package/src/role-runtime.ts +6 -10
  79. package/src/run-dossier-files.ts +33 -0
  80. package/src/run-dossier.ts +357 -0
  81. package/src/run-terminal-artifacts.ts +83 -237
  82. package/src/run-ticket-number.ts +41 -11
  83. package/src/session-identity.ts +52 -20
  84. package/src/sitian-appender.ts +17 -18
  85. package/src/sitian-facade.ts +22 -0
  86. package/src/sitian-reader.ts +62 -32
  87. package/src/submission-gate.ts +1 -1
  88. package/src/ticket-provenance.ts +20 -7
  89. package/src/ticket-trajectory.ts +10 -10
@@ -26,10 +26,11 @@ import {
26
26
  relocateAdmittedRunToTicket,
27
27
  } from "./invocation.ts";
28
28
  import { readRecordedSubmissionRows } from "../submission-ledger.ts";
29
- import { readDeclaredTicketNumber } from "../run-ticket-number.ts";
29
+ import { parseTicketNumber, readDeclaredTicketNumber } from "../run-ticket-number.ts";
30
30
  import { pathContainedIn } from "../activation-ledger-topology.ts";
31
31
  import { pickEngineAxis } from "../package-resources/engine-material.ts";
32
32
  import { readStoredHostSessionId } from "../session-identity.ts";
33
+ import { reportRunRecord } from "../sitian-facade.ts";
33
34
  import {
34
35
  createReceiptDeliveryPolicy,
35
36
  deliveryLimitFromConfig,
@@ -86,7 +87,6 @@ import {
86
87
  type CatchableProcessSignal,
87
88
  } from "./process-cancel.ts";
88
89
  import { DEFAULT_ROLE_TURN_HOST } from "../host-descriptions.ts";
89
- import { recordRunStart } from "../host-session-record.ts";
90
90
  import {
91
91
  classifyPostAdmissionFailure,
92
92
  exitCodeForTerminalOutcome,
@@ -112,7 +112,6 @@ import {
112
112
  type TerminalResult,
113
113
  } from "./terminal.ts";
114
114
  import {
115
- ensureRealArtifactsDirectory,
116
115
  persistReturnedRunState,
117
116
  presentTerminal,
118
117
  runWithAutoResumeLoop,
@@ -175,6 +174,7 @@ async function settleProcessCancelAfterTurn<A extends AdmittedRoleInvocation, T
175
174
  deferredPersist: { needsPersist?: true } | Record<string, never>;
176
175
  invocationScopeId: string | undefined;
177
176
  notePackageFault?: (diagnostic: string) => void | Promise<void>;
177
+ courtAttemptId?: string;
178
178
  }): Promise<{
179
179
  exitCode: number;
180
180
  admitted: A;
@@ -205,6 +205,7 @@ async function settleProcessCancelAfterTurn<A extends AdmittedRoleInvocation, T
205
205
  input.io,
206
206
  input.persistRunState,
207
207
  input.notePackageFault,
208
+ input.courtAttemptId,
208
209
  );
209
210
  return {
210
211
  ...failed,
@@ -379,7 +380,7 @@ export type ControlledFailureInput = {
379
380
  knownDetails?: Readonly<Record<string, unknown>>;
380
381
  /**
381
382
  * A real failure this package hit while handling the call (e.g. the
382
- * stderr.log durable write). Recorded beside the host's report; never a
383
+ * stderr log-line write). Recorded beside the host's report; never a
383
384
  * cause, and never written into the host's open `details`.
384
385
  */
385
386
  packageFact?: PackageSideFact;
@@ -473,6 +474,8 @@ export async function presentControlledFailure<
473
474
  io: CliIo,
474
475
  persistRunState = true,
475
476
  notePackageFault?: (diagnostic: string) => void | Promise<void>,
477
+ /** Open-court identity already on the turn request / currentCourt (#1161). */
478
+ courtAttemptId?: string,
476
479
  ): Promise<{
477
480
  exitCode: number;
478
481
  admitted: A;
@@ -545,6 +548,9 @@ export async function presentControlledFailure<
545
548
  const settled = await settleFailureTerminalResult(admitted, failure, authority, {
546
549
  recordAttemptHistory: true,
547
550
  ...(notePackageFault === undefined ? {} : { notePackageFault }),
551
+ ...(courtAttemptId === undefined || courtAttemptId.length === 0
552
+ ? {}
553
+ : { courtAttemptId }),
548
554
  ...(failureInput.invocationScopeId === undefined ||
549
555
  failureInput.invocationScopeId.length === 0
550
556
  ? {}
@@ -588,6 +594,7 @@ async function settleAfterTurnStarted<
588
594
  io: CliIo,
589
595
  persistRunState: boolean,
590
596
  notePackageFault?: (diagnostic: string) => void | Promise<void>,
597
+ courtAttemptId?: string,
591
598
  ): Promise<{ exitCode: number; admitted: A; terminal: T }> {
592
599
  try {
593
600
  return (await presentControlledFailure(
@@ -598,6 +605,7 @@ async function settleAfterTurnStarted<
598
605
  io,
599
606
  persistRunState,
600
607
  notePackageFault,
608
+ courtAttemptId,
601
609
  )) as { exitCode: number; admitted: A; terminal: T };
602
610
  } catch (error) {
603
611
  throw new TurnDispatchedFailure(error);
@@ -666,18 +674,12 @@ function settlementScopeForTurn(request: RoleTurnRequest): SettlementCourtScope
666
674
  *
667
675
  * The caller owns the live delivery policy; the request projects its next send.
668
676
  */
669
- async function buildReceiptDeliveryRequest<A extends AdmittedRoleInvocation>(input: {
670
- admitted: A;
671
- env: PostAdmissionEnv;
677
+ /** Bare in-call催交 resume: hostSessionId absent; adapter is the sole load authority. */
678
+ function buildReceiptDeliveryRequest(input: {
672
679
  request: RoleTurnRequest;
673
680
  receiptDelivery: ReturnType<typeof createReceiptDeliveryPolicy>;
674
- }): Promise<RoleTurnRequest> {
675
- const { admitted, env, request, receiptDelivery } = input;
676
- const hostSessionId = await readStoredHostSessionId(
677
- env.host,
678
- env.principalAuthority,
679
- admitted.principal,
680
- );
681
+ }): RoleTurnRequest {
682
+ const { request, receiptDelivery } = input;
681
683
  return {
682
684
  ...request,
683
685
  continuation: {
@@ -686,7 +688,6 @@ async function buildReceiptDeliveryRequest<A extends AdmittedRoleInvocation>(inp
686
688
  ...receiptDelivery.deliveryState(),
687
689
  deliveryTurns: receiptDelivery.issuedDeliveryRequests() + 1,
688
690
  }),
689
- ...(hostSessionId === undefined ? {} : { hostSessionId }),
690
691
  },
691
692
  deliveryRequestLimit: receiptDelivery.limit,
692
693
  };
@@ -760,20 +761,16 @@ async function settleCompletedHostTurn<
760
761
  // machinery once the retry budget is exhausted.
761
762
  let stderrLogWriteFailure: unknown;
762
763
  try {
763
- await writeFile(
764
- join(admitted.runDirectory, "stderr.log"),
765
- result.stderr,
766
- "utf8",
767
- );
764
+ reportRunRecord(admitted.runDirectory, "stderr", { text: result.stderr }, "post-admission");
768
765
  } catch (error) {
769
766
  stderrLogWriteFailure = error;
770
- // Best-effort: stderr.log is secondary to the host terminal. A write
767
+ // Best-effort: the stderr log line is secondary to the host terminal. A write
771
768
  // failure leaves a durable note and, on a host failure, rides in
772
769
  // packageFact. It does not become the cause.
773
770
  await recordBestEffortPostDispatchDiagnostic(
774
771
  admitted,
775
772
  env,
776
- `stderr.log write failed (best-effort continue): ${describeErrorIdentity(error)}`,
773
+ `stderr log write failed (best-effort continue): ${describeErrorIdentity(error)}`,
777
774
  io,
778
775
  );
779
776
  }
@@ -781,7 +778,7 @@ async function settleCompletedHostTurn<
781
778
  // Host facts are resolved before trySettle. A host failure skips trySettle
782
779
  // so a later read cannot replace that report. A clean host whose settlement
783
780
  // read throws is noted above and is not turned into a failure terminal.
784
- // stderr.log and run-state writes are notes beside the host terminal.
781
+ // the stderr log line and run-state writes are notes beside the host terminal.
785
782
  let settled: T | undefined;
786
783
  let settledOutcome:
787
784
  | { exitCode: number; admitted: A; terminal: T; turnDispatched: true }
@@ -841,6 +838,9 @@ async function settleCompletedHostTurn<
841
838
  deferredPersist,
842
839
  invocationScopeId: request.invocationScopeId,
843
840
  notePackageFault: courtScope.notePackageFault,
841
+ ...(courtScope.courtAttemptId === undefined
842
+ ? {}
843
+ : { courtAttemptId: courtScope.courtAttemptId }),
844
844
  }),
845
845
  ) };
846
846
  }
@@ -892,6 +892,7 @@ async function settleCompletedHostTurn<
892
892
  io,
893
893
  persistRunState,
894
894
  courtScope.notePackageFault,
895
+ courtScope.courtAttemptId,
895
896
  );
896
897
  return { kind: "settled", outcome: await finishAfterTurn(
897
898
  withProcessCancelSkipAutoResume(
@@ -904,6 +905,64 @@ async function settleCompletedHostTurn<
904
905
  return { kind: "still-missing" };
905
906
  }
906
907
 
908
+ /**
909
+ * #858 / #1071: the first identifiable ticketNumber field declaration among the
910
+ * run's sealed rows files an unbound run (integer, digit string, or leading #N
911
+ * token). Terminal rows match settlement (#881): accepted and audit-escalation —
912
+ * an escalated submission still carries the original role params. Later
913
+ * receipts remain untouched; prose note/report is never consulted; this seam
914
+ * does not adjudicate a ticket change. Runs in the public call's own process
915
+ * (no role leg writes the run's current.json).
916
+ */
917
+ export async function bindSealedTicketNumber(admitted: AdmittedRoleInvocation): Promise<void> {
918
+ if (admitted.ticketNumber !== undefined) return;
919
+ const rows = await readRecordedSubmissionRows(
920
+ admitted.projectRoot,
921
+ admitted.runId,
922
+ ledgerReadScope(admitted),
923
+ );
924
+ let ticketNumber: number | undefined;
925
+ for (const row of rows) {
926
+ if (
927
+ row.role !== admitted.role
928
+ || (row.kind !== "accepted" && row.kind !== "audit-escalation")
929
+ ) {
930
+ continue;
931
+ }
932
+ const payload = row.accepted;
933
+ if (!isRecord(payload)) continue;
934
+ ticketNumber = readDeclaredTicketNumber(
935
+ (payload as { ticketNumber?: unknown }).ticketNumber,
936
+ );
937
+ if (ticketNumber !== undefined) break;
938
+ }
939
+ if (ticketNumber !== undefined) await bindAdmittedTicketNumber(admitted, ticketNumber);
940
+ }
941
+
942
+ /**
943
+ * The ticket a diarist asserted on a completed submission, whether or not the
944
+ * rest of that submission was later accepted (a bounds reask does not unsay the
945
+ * ticket). Before this ran in the leg's accept hook; the leg now only appends its
946
+ * ledger rows and the public call's own process binds the ticket from them.
947
+ */
948
+ export async function bindDiaristAssertedTicketNumber(admitted: AdmittedRoleInvocation): Promise<void> {
949
+ if (admitted.ticketNumber !== undefined) return;
950
+ const rows = await readRecordedSubmissionRows(
951
+ admitted.projectRoot,
952
+ admitted.runId,
953
+ ledgerReadScope(admitted),
954
+ );
955
+ for (const row of rows) {
956
+ if (row.role !== admitted.role || !isRecord(row.accepted)) continue;
957
+ if (row.accepted.status !== "completed" || !("ticketNumber" in row.accepted)) continue;
958
+ const ticketNumber = parseTicketNumber(row.accepted.ticketNumber);
959
+ if (ticketNumber !== undefined) {
960
+ await bindAdmittedTicketNumber(admitted, ticketNumber);
961
+ return;
962
+ }
963
+ }
964
+ }
965
+
907
966
  export async function dispatchPostAdmissionTurn<
908
967
  A extends AdmittedRoleInvocation,
909
968
  T extends TerminalResult = TerminalResult,
@@ -991,6 +1050,7 @@ export async function dispatchPostAdmissionTurn<
991
1050
  io,
992
1051
  persistRunState,
993
1052
  courtScope.notePackageFault,
1053
+ courtScope.courtAttemptId,
994
1054
  )),
995
1055
  turnDispatched: true as const,
996
1056
  skipAutoResume: true as const,
@@ -1042,29 +1102,7 @@ export async function dispatchPostAdmissionTurn<
1042
1102
  // an escalated submission still carries the original role params.
1043
1103
  // Later receipts remain untouched; prose note/report is never consulted;
1044
1104
  // this seam does not adjudicate a ticket change.
1045
- if (admitted.ticketNumber === undefined) {
1046
- const rows = await readRecordedSubmissionRows(
1047
- admitted.projectRoot,
1048
- admitted.runId,
1049
- ledgerReadScope(admitted),
1050
- );
1051
- let ticketNumber: number | undefined;
1052
- for (const row of rows) {
1053
- if (
1054
- row.role !== admitted.role
1055
- || (row.kind !== "accepted" && row.kind !== "audit-escalation")
1056
- ) {
1057
- continue;
1058
- }
1059
- const payload = row.accepted;
1060
- if (!isRecord(payload)) continue;
1061
- ticketNumber = readDeclaredTicketNumber(
1062
- (payload as { ticketNumber?: unknown }).ticketNumber,
1063
- );
1064
- if (ticketNumber !== undefined) break;
1065
- }
1066
- if (ticketNumber !== undefined) await bindAdmittedTicketNumber(admitted, ticketNumber);
1067
- }
1105
+ await bindSealedTicketNumber(admitted);
1068
1106
  // #863: shared post-admission bind must relocate unbound→ticket in-home
1069
1107
  // before lease release (work-seat self-report and any prior board bind).
1070
1108
  const relocation = await relocateAdmittedRunToTicket(admitted, env.principalAuthority, lease);
@@ -1102,6 +1140,7 @@ export async function dispatchPostAdmissionTurn<
1102
1140
  io,
1103
1141
  persistRunState,
1104
1142
  courtScope.notePackageFault,
1143
+ courtScope.courtAttemptId,
1105
1144
  )) as { exitCode: number; admitted: A; terminal: T },
1106
1145
  ...(result.turnDispatched === true ? { turnDispatched: true as const } : {}),
1107
1146
  ...(result.skipAutoResume === true ? { skipAutoResume: true as const } : {}),
@@ -1140,6 +1179,7 @@ export async function dispatchPostAdmissionTurn<
1140
1179
  io,
1141
1180
  persistRunState,
1142
1181
  courtScope.notePackageFault,
1182
+ courtScope.courtAttemptId,
1143
1183
  )) as { exitCode: number; admitted: A; terminal: T };
1144
1184
  return { ...settled, ...deferredPersist };
1145
1185
  }
@@ -1160,7 +1200,7 @@ export async function dispatchPostAdmissionTurn<
1160
1200
  turnRequest = { ...turnRequest, stationChild: env.stationChild };
1161
1201
  }
1162
1202
  // Selected host axis rides the shared Host envelope for in-turn tools
1163
- // (detour usage ledger) — never a pre-spawn invocation.json reread.
1203
+ // (detour usage ledger) — never a pre-spawn current.json reread.
1164
1204
  if (typeof env.host === "string" && env.host.trim() !== "") {
1165
1205
  turnRequest = { ...turnRequest, host: env.host.trim() };
1166
1206
  }
@@ -1186,7 +1226,6 @@ export async function dispatchPostAdmissionTurn<
1186
1226
  // host CLI's native resume. Nothing here may gate, redirect or reshape
1187
1227
  // that dispatch on prior conclusions, row counts or report presence;
1188
1228
  // the authoritative post-turn settlement reads whatever really happened.
1189
- recordRunStart(admitted.runDirectory);
1190
1229
  turnDispatched = true;
1191
1230
  result = await env.roleTurnHost.executeTurn(turnRequest);
1192
1231
  } catch (error) {
@@ -1207,6 +1246,7 @@ export async function dispatchPostAdmissionTurn<
1207
1246
  io,
1208
1247
  persistRunState,
1209
1248
  courtScope.notePackageFault,
1249
+ courtScope.courtAttemptId,
1210
1250
  );
1211
1251
  return await finishAfterTurn(
1212
1252
  withProcessCancelSkipAutoResume(
@@ -1248,11 +1288,9 @@ export async function dispatchPostAdmissionTurn<
1248
1288
  let deliveryResult: RoleTurnResult;
1249
1289
  let deliveryTurnRequest: RoleTurnRequest;
1250
1290
  try {
1251
- const deliveryRequest = await buildReceiptDeliveryRequest({
1252
- admitted,
1253
- env,
1254
- // Host and the other axes already selected on this turn ride the
1255
- // shared projection. The pre-projection request does not have them.
1291
+ // Host and the other axes already selected on this turn ride the
1292
+ // shared projection. The pre-projection request does not have them.
1293
+ const deliveryRequest = buildReceiptDeliveryRequest({
1256
1294
  request: turnRequest,
1257
1295
  receiptDelivery,
1258
1296
  });
@@ -1281,6 +1319,8 @@ export async function dispatchPostAdmissionTurn<
1281
1319
  env.principalAuthority,
1282
1320
  io,
1283
1321
  persistRunState,
1322
+ courtScope.notePackageFault,
1323
+ courtScope.courtAttemptId,
1284
1324
  );
1285
1325
  return await finishAfterTurn(
1286
1326
  withProcessCancelSkipAutoResume(
@@ -1361,6 +1401,9 @@ export async function dispatchPostAdmissionTurn<
1361
1401
  deferredPersist,
1362
1402
  invocationScopeId: request.invocationScopeId,
1363
1403
  notePackageFault: courtScope.notePackageFault,
1404
+ ...(courtScope.courtAttemptId === undefined
1405
+ ? {}
1406
+ : { courtAttemptId: courtScope.courtAttemptId }),
1364
1407
  }),
1365
1408
  );
1366
1409
  }
@@ -1758,6 +1801,9 @@ export async function runPostAdmissionSeatResume<
1758
1801
  }
1759
1802
  }
1760
1803
 
1804
+ // Public explicit resume: attach the stored native id when the caller
1805
+ // left it unset (399c3c8c). In-call auto-resume / 催交 / gate retry omit
1806
+ // the field; those load inside the host adapter (host-contracts).
1761
1807
  let turnRequest = await input.buildTurnRequest(admittedForBuild, request);
1762
1808
  if (
1763
1809
  turnRequest.continuation.kind === "resume"
@@ -1872,7 +1918,7 @@ export async function runPostAdmissionSeatResume<
1872
1918
  await presentResumeFailurePointer(
1873
1919
  input.io,
1874
1920
  error,
1875
- (failure) => writeResumeDiagnosticFile(
1921
+ (failure) => writeResumeDiagnostic(
1876
1922
  loaded.admitted.runDirectory,
1877
1923
  loaded.admitted.runId,
1878
1924
  failure,
@@ -1904,13 +1950,13 @@ async function pointExistingRunFailure(
1904
1950
  record = await readRoleRunState(runDirectory, authority);
1905
1951
  } catch {
1906
1952
  await presentResumeFailurePointer(io, thrown, (failure) =>
1907
- writeResumeDiagnosticFile(runDirectory, runId, failure),
1953
+ writeResumeDiagnostic(runDirectory, runId, failure),
1908
1954
  );
1909
1955
  return true;
1910
1956
  }
1911
1957
  if (record === undefined) return false;
1912
1958
  await presentResumeFailurePointer(io, thrown, (failure) =>
1913
- writeResumeDiagnosticFile(runDirectory, runId, failure, record.role),
1959
+ writeResumeDiagnostic(runDirectory, runId, failure, record.role),
1914
1960
  );
1915
1961
  return true;
1916
1962
  }
@@ -1966,27 +2012,20 @@ async function presentResumeFailurePointer(
1966
2012
  }
1967
2013
  }
1968
2014
 
1969
- async function writeResumeDiagnosticFile(
2015
+ async function writeResumeDiagnostic(
1970
2016
  runDirectory: string,
1971
2017
  runId: string,
1972
2018
  failure: ControlledFailure,
1973
2019
  role?: AdmittedRoleInvocation["role"],
1974
2020
  ): Promise<string> {
1975
- const dir = await ensureRealArtifactsDirectory(runDirectory);
1976
- const path = join(dir, `resume-diagnostic-${randomUUID()}.json`);
1977
- await writeFile(
1978
- path,
1979
- `${JSON.stringify({
1980
- runId,
1981
- ...(role === undefined ? {} : { role }),
1982
- diagnostic: failure.diagnostic,
1983
- ...(failure.cause === undefined ? {} : { cause: failure.cause }),
1984
- ...(failure.identity === undefined ? {} : { identity: failure.identity }),
1985
- ...(failure.details === undefined ? {} : { details: failure.details }),
1986
- }, null, 2)}\n`,
1987
- { encoding: "utf8", flag: "wx" },
1988
- );
1989
- return path;
2021
+ return reportRunRecord(runDirectory, "resume-diagnostic", {
2022
+ runId,
2023
+ ...(role === undefined ? {} : { role }),
2024
+ diagnostic: failure.diagnostic,
2025
+ ...(failure.cause === undefined ? {} : { cause: failure.cause }),
2026
+ ...(failure.identity === undefined ? {} : { identity: failure.identity }),
2027
+ ...(failure.details === undefined ? {} : { details: failure.details }),
2028
+ }, "post-admission").recordFile;
1990
2029
  }
1991
2030
 
1992
2031
  /**
@@ -63,6 +63,7 @@ import {
63
63
  type ReviewerLens,
64
64
  } from "./invocation.ts";
65
65
 
66
+ import { readPageSync, writeSectionSync } from "../run-dossier.ts";
66
67
  import { isRecord, errorText, isEnoent } from "../unknown-value.ts";
67
68
 
68
69
  export type RoleRunState = "admitted" | "running" | "resumable" | "terminal";
@@ -83,7 +84,6 @@ export type RoleRunRecord = {
83
84
  /** Exact Pi session file principal reopened on resume (not directory-latest). */
84
85
  readonly sessionFile: string;
85
86
  readonly runDirectory: string;
86
- readonly admittedRequestPath: string;
87
87
  /** Coder/Fixer — preserved for resume continuation. */
88
88
  readonly phase?: CoderPhase | FixerPhase;
89
89
  };
@@ -154,7 +154,6 @@ export function buildAutoResumeContinuationPrompt(options: {
154
154
  ).join("\n");
155
155
  }
156
156
 
157
- const RUN_STATE_FILE = "run-state.json";
158
157
  const WRITER_LOCK_FILE = "writer.lock";
159
158
 
160
159
  export async function writeRoleRunState(
@@ -162,15 +161,11 @@ export async function writeRoleRunState(
162
161
  record: Omit<RoleRunRecord, "runDirectory">,
163
162
  ): Promise<void> {
164
163
  const payload: RoleRunRecord = { ...record, runDirectory };
165
- await writeFile(
166
- join(runDirectory, RUN_STATE_FILE),
167
- `${JSON.stringify(payload, null, 2)}\n`,
168
- "utf8",
169
- );
164
+ writeSectionSync(runDirectory, "runState", { ...payload });
170
165
  }
171
166
 
172
167
  /**
173
- * Uninterpreted principal wire as stored on run-state.json.
168
+ * Uninterpreted principal wire as stored on the state.jsonl run-state row.
174
169
  * Legacy rows may omit sessionFile; only DurablePrincipalAuthority decodes it.
175
170
  */
176
171
  type RoleRunPrincipalWire = {
@@ -186,7 +181,6 @@ type RoleRunStateDisk = {
186
181
  readonly bookKey: string;
187
182
  readonly projectRoot: string;
188
183
  readonly runDirectory: string;
189
- readonly admittedRequestPath: string;
190
184
  readonly principalWire: RoleRunPrincipalWire;
191
185
  readonly phase?: CoderPhase | FixerPhase;
192
186
  /** Open court turn (#637); omit when no unsealed current court. */
@@ -263,20 +257,13 @@ function parseCurrentCourtState(raw: unknown): CurrentCourtState | undefined {
263
257
  }
264
258
 
265
259
  async function readRoleRunStateRaw(runDirectory: string): Promise<unknown | undefined> {
266
- let raw: unknown;
267
- try {
268
- raw = JSON.parse(await readFile(join(runDirectory, RUN_STATE_FILE), "utf8"));
269
- } catch (error) {
270
- // Only true absence (ENOENT) is a lawful "no run state yet". A real read
271
- // failure (EISDIR, EACCES, ...) or a JSON.parse SyntaxError on a present
272
- // file is genuine infrastructure/data damage and must keep its own
273
- // identity — callers route it through the controlled-failure seam
274
- // (markRunRunning/markRunTerminal/recordCurrentCourt)
275
- // instead of it being relabeled "run state missing" (#836).
276
- if (errorCodeOf(error) === "ENOENT") return undefined;
277
- throw error;
278
- }
279
- return raw;
260
+ // Only true absence (no state.jsonl run-state row) is a lawful "no run state
261
+ // yet". A real read failure (EISDIR, EACCES, ...) or an illegal latest payload
262
+ // is genuine infrastructure/data damage and must keep its own identity —
263
+ // callers route it through the controlled-failure seam
264
+ // (markRunRunning/markRunTerminal/recordCurrentCourt) instead of it being
265
+ // relabeled "run state missing" (#836). readPageSync throws those.
266
+ return readPageSync(runDirectory, "runState");
280
267
  }
281
268
 
282
269
  function parseRoleRunIdentity(raw: unknown, runDirectory: string): {
@@ -287,15 +274,15 @@ function parseRoleRunIdentity(raw: unknown, runDirectory: string): {
287
274
  readonly state: RoleRunState;
288
275
  } {
289
276
  if (!isRecord(raw)) {
290
- throw new TypeError("Invalid run-state.json");
277
+ throw new TypeError("Invalid state.jsonl run-state");
291
278
  }
292
279
  const record = raw;
293
280
  if (typeof record.runId !== "string" || record.runId.trim() === "") {
294
- throw new TypeError("Invalid run-state.json");
281
+ throw new TypeError("Invalid state.jsonl run-state");
295
282
  }
296
283
  const role = typeof record.role === "string" ? packagedRoleMetadata(record.role)?.role : undefined;
297
284
  if (role === undefined) {
298
- throw new TypeError("Invalid run-state.json");
285
+ throw new TypeError("Invalid state.jsonl run-state");
299
286
  }
300
287
  if (
301
288
  record.state !== "admitted" &&
@@ -303,9 +290,9 @@ function parseRoleRunIdentity(raw: unknown, runDirectory: string): {
303
290
  record.state !== "resumable" &&
304
291
  record.state !== "terminal"
305
292
  ) {
306
- throw new TypeError("Invalid run-state.json");
293
+ throw new TypeError("Invalid state.jsonl run-state");
307
294
  }
308
- if (typeof record.bookKey !== "string") throw new TypeError("Invalid run-state.json");
295
+ if (typeof record.bookKey !== "string") throw new TypeError("Invalid state.jsonl run-state");
309
296
  return {
310
297
  runId: record.runId,
311
298
  role,
@@ -322,9 +309,8 @@ async function readRoleRunStateDisk(
322
309
  if (raw === undefined) return undefined;
323
310
  const identity = parseRoleRunIdentity(raw, runDirectory);
324
311
  const record = raw as Record<string, unknown>;
325
- if (typeof record.projectRoot !== "string") throw new TypeError("Invalid run-state.json");
326
- if (typeof record.sessionDirectory !== "string") throw new TypeError("Invalid run-state.json");
327
- if (typeof record.admittedRequestPath !== "string") throw new TypeError("Invalid run-state.json");
312
+ if (typeof record.projectRoot !== "string") throw new TypeError("Invalid state.jsonl run-state");
313
+ if (typeof record.sessionDirectory !== "string") throw new TypeError("Invalid state.jsonl run-state");
328
314
  const storedRunDirectory =
329
315
  typeof record.runDirectory === "string" && record.runDirectory.trim() !== ""
330
316
  ? record.runDirectory
@@ -353,11 +339,6 @@ async function readRoleRunStateDisk(
353
339
  bookKey: identity.bookKey,
354
340
  projectRoot: record.projectRoot,
355
341
  runDirectory: runDir,
356
- admittedRequestPath: rewriteRunDirectoryPathValue(
357
- record.admittedRequestPath,
358
- storedRunDirectory,
359
- runDirectory,
360
- ) as string,
361
342
  principalWire,
362
343
  ...(phase === undefined ? {} : { phase }),
363
344
  ...(currentCourt === undefined ? {} : { currentCourt }),
@@ -379,15 +360,10 @@ async function writeRoleRunStateDisk(
379
360
  ...(disk.principalWire.sessionFile === undefined
380
361
  ? {}
381
362
  : { sessionFile: disk.principalWire.sessionFile }),
382
- admittedRequestPath: disk.admittedRequestPath,
383
363
  ...(disk.phase === undefined ? {} : { phase: disk.phase }),
384
364
  ...(disk.currentCourt === undefined ? {} : { currentCourt: disk.currentCourt }),
385
365
  };
386
- await writeFile(
387
- join(runDirectory, RUN_STATE_FILE),
388
- `${JSON.stringify(payload, null, 2)}\n`,
389
- "utf8",
390
- );
366
+ writeSectionSync(runDirectory, "runState", payload);
391
367
  }
392
368
 
393
369
  /** One authority.decode of the uninterpreted wire → record + opaque principal (frozen wire itself). */
@@ -408,7 +384,6 @@ function materializeRoleRunFromDisk(
408
384
  sessionDirectory: coordinates.sessionDirectory,
409
385
  sessionFile: coordinates.sessionFile,
410
386
  runDirectory: disk.runDirectory,
411
- admittedRequestPath: disk.admittedRequestPath,
412
387
  ...(disk.phase === undefined ? {} : { phase: disk.phase }),
413
388
  },
414
389
  };
@@ -467,7 +442,6 @@ export async function markRunAdmitted(
467
442
  projectRoot: admitted.projectRoot,
468
443
  sessionDirectory,
469
444
  sessionFile,
470
- admittedRequestPath: admitted.admittedRequestPath,
471
445
  ...(workerPhase === undefined ? {} : { phase: workerPhase }),
472
446
  });
473
447
  }
@@ -475,7 +449,7 @@ export async function markRunAdmitted(
475
449
  /**
476
450
  * Shared dispatch execution seam: transition to running, then record the
477
451
  * effective launch model (initial or resume override) and the authoritative
478
- * seat engine/host onto invocation.json.
452
+ * seat engine/host onto the invocation section.
479
453
  * Role runners must not coordinate lifecycle ledger writes themselves.
480
454
  * Engine axis is authoritative here (#617): present string is written; omit/undefined
481
455
  * clears any prior engine key so unset-engine + resume does not keep a stale value.
@@ -945,19 +919,8 @@ export function parentRunPathFromGatePointerInstruction(
945
919
  export async function readRunParentPath(
946
920
  runDirectory: string,
947
921
  ): Promise<string | undefined> {
948
- let raw: unknown;
949
- try {
950
- raw = JSON.parse(
951
- await readFile(join(runDirectory, "admitted-request.json"), "utf8"),
952
- );
953
- } catch (error) {
954
- if (errorCodeOf(error) === "ENOENT") return undefined;
955
- throw error;
956
- }
957
- if (!isRecord(raw)) {
958
- return undefined;
959
- }
960
- const record = raw;
922
+ const record = readPageSync(runDirectory, "admitted");
923
+ if (record === undefined) return undefined;
961
924
  if (typeof record.sourceRunPath === "string" && record.sourceRunPath.trim() !== "") {
962
925
  return record.sourceRunPath;
963
926
  }
@@ -1127,7 +1090,7 @@ async function loadResumableRunRecord(
1127
1090
  throw new CliUsageError(`unknown role run id: ${runId}`);
1128
1091
  }
1129
1092
  const { run, principal } = materialized;
1130
- // Reconstruct admitted identity from durable run record + admitted-request.json.
1093
+ // Reconstruct admitted identity from durable run record + the admitted section.
1131
1094
  let instruction = "";
1132
1095
  let instructionEmpty = true;
1133
1096
  let attachments: FrozenAttachment[] = [];
@@ -1155,11 +1118,9 @@ async function loadResumableRunRecord(
1155
1118
  let sourceRunPath: string | undefined;
1156
1119
  let sourceRun: NotarySourceRunLocator | undefined;
1157
1120
  try {
1158
- const raw: unknown = JSON.parse(
1159
- await readFile(run.admittedRequestPath, "utf8"),
1160
- );
1161
- if (isRecord(raw)) {
1162
- const record = raw as Record<string, unknown>;
1121
+ const record = readPageSync(run.runDirectory, "admitted");
1122
+ if (record === undefined) throw new Error("state.jsonl has no admitted-request row");
1123
+ {
1163
1124
  const storedRunDirectory =
1164
1125
  typeof record.runDirectory === "string" && record.runDirectory.trim() !== ""
1165
1126
  ? record.runDirectory
@@ -1318,13 +1279,8 @@ async function loadResumableRunRecord(
1318
1279
  }
1319
1280
  let model: InvocationEffectiveModel | undefined;
1320
1281
  try {
1321
- const invocationRaw: unknown = JSON.parse(
1322
- await readFile(join(run.runDirectory, "invocation.json"), "utf8"),
1323
- );
1324
- if (
1325
- isRecord(invocationRaw)
1326
- ) {
1327
- const rec = invocationRaw as Record<string, unknown>;
1282
+ const rec = readPageSync(run.runDirectory, "invocation");
1283
+ if (rec !== undefined) {
1328
1284
  if (typeof rec.provider === "string" && typeof rec.model === "string") {
1329
1285
  model = {
1330
1286
  provider: rec.provider,
@@ -1423,7 +1379,6 @@ function resumedBaseAdmitted(loaded: {
1423
1379
  attachments: loaded.admittedFields.attachments,
1424
1380
  runDirectory: loaded.run.runDirectory,
1425
1381
  principal: loaded.principal,
1426
- admittedRequestPath: loaded.run.admittedRequestPath,
1427
1382
  ...(loaded.admittedFields.model === undefined ? {} : { model: loaded.admittedFields.model }),
1428
1383
  ...restoredTicketFields(loaded.admittedFields),
1429
1384
  };