@akagilnc/pi-workflow-roles 0.1.5483 → 0.1.5571

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 (112) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/acp-host/production-host.js +6312 -6376
  4. package/dist/analyst-gate-cycles-read.js +28 -78
  5. package/dist/analyst-ledger.js +26 -42
  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-entry.js +4 -3
  9. package/dist/archivist-record-pointer.js +71 -36
  10. package/dist/atomic-write.js +2 -2
  11. package/dist/auditor-dossier-tool.js +11 -6
  12. package/dist/auditor-soul.js +5 -12
  13. package/dist/collector-config.js +6 -5
  14. package/dist/doctor-evidence.js +8 -4
  15. package/dist/engine-detour-usage.js +8 -22
  16. package/dist/headless-host/production-host.js +5969 -6011
  17. package/dist/host-descriptions.js +0 -4
  18. package/dist/host-session-record.js +266 -267
  19. package/dist/ledger-session-read.js +5 -0
  20. package/dist/migrate-book-topology.js +1369 -722
  21. package/dist/navigator-public-session.js +13 -17
  22. package/dist/notary-source-run.js +8 -16
  23. package/dist/packaged-role-registry.js +5 -56
  24. package/dist/pi/durable-principal.js +2 -3
  25. package/dist/pi/role-turn-host.js +79 -15
  26. package/dist/public-cli/auto-resume.js +8 -47
  27. package/dist/public-cli/instruction-seat-run.js +9 -7
  28. package/dist/public-cli/invocation.js +67 -100
  29. package/dist/public-cli/main.js +2689 -2532
  30. package/dist/public-cli/post-admission.js +96 -55
  31. package/dist/public-cli/run-lifecycle.js +44 -100
  32. package/dist/public-cli/settlement.js +184 -429
  33. package/dist/public-cli/terminal.js +0 -12
  34. package/dist/role-run-placement.js +53 -16
  35. package/dist/role-run-relocation.js +111 -52
  36. package/dist/run-dossier-files.js +30 -0
  37. package/dist/run-dossier.js +322 -0
  38. package/dist/run-terminal-artifacts.js +69 -202
  39. package/dist/run-ticket-number.js +49 -7
  40. package/dist/session-assistant-usage.js +9 -18
  41. package/dist/session-identity.js +49 -19
  42. package/dist/sitian-appender.js +29 -18
  43. package/dist/sitian-facade.js +11 -0
  44. package/dist/sitian-reader.js +44 -29
  45. package/dist/submission-gate.js +1 -1
  46. package/dist/submission-ledger.js +4 -4
  47. package/dist/ticket-provenance-contracts.js +2 -8
  48. package/dist/ticket-provenance.js +25 -9
  49. package/package.json +1 -1
  50. package/scripts/build-package.mjs +2 -0
  51. package/src/acp-host/production-host.ts +1 -1
  52. package/src/acp-host/role-turn-host.ts +11 -4
  53. package/src/analyst-gate-cycles-read.ts +31 -88
  54. package/src/analyst-ledger.ts +36 -51
  55. package/src/analyst-metric-families/acceptance-success-rework.ts +24 -1
  56. package/src/analyst-metric-families/gate-cycles.ts +1 -1
  57. package/src/analyst-metric-families/round-timeline.ts +2 -5
  58. package/src/analyst-page.ts +3 -3
  59. package/src/archivist-record-entry.ts +6 -5
  60. package/src/archivist-record-pointer.ts +92 -42
  61. package/src/atomic-write.ts +3 -1
  62. package/src/auditor-dossier-tool.ts +14 -8
  63. package/src/auditor-soul.ts +5 -10
  64. package/src/book-topology-migration-placement.ts +16 -3
  65. package/src/book-topology-record-class-migrators.ts +4 -2
  66. package/src/book-topology-runs-migrator.ts +6 -3
  67. package/src/collector-config.ts +5 -3
  68. package/src/doctor-contracts.ts +1 -1
  69. package/src/doctor-evidence.ts +7 -3
  70. package/src/engine-detour-tool.ts +1 -1
  71. package/src/engine-detour-usage.ts +12 -25
  72. package/src/external-host-turn-loop.ts +25 -0
  73. package/src/factory-board.ts +16 -81
  74. package/src/headless-host/production-host.ts +1 -4
  75. package/src/headless-host/role-turn-host.ts +56 -21
  76. package/src/host-descriptions.ts +0 -6
  77. package/src/host-session-record.ts +224 -140
  78. package/src/ledger-session-read.ts +6 -0
  79. package/src/navigator-public-session.ts +13 -18
  80. package/src/navigator-work-context.ts +3 -1
  81. package/src/notary-role.ts +2 -1
  82. package/src/notary-source-run.ts +8 -18
  83. package/src/packaged-role-registry.ts +1 -65
  84. package/src/pi/adapter.ts +50 -7
  85. package/src/pi/durable-principal.ts +2 -4
  86. package/src/pi/role-turn-host.ts +85 -14
  87. package/src/public-cli/auto-resume.ts +7 -43
  88. package/src/public-cli/instruction-seat-run.ts +13 -6
  89. package/src/public-cli/invocation.ts +90 -144
  90. package/src/public-cli/post-admission.ts +111 -72
  91. package/src/public-cli/run-lifecycle.ts +47 -115
  92. package/src/public-cli/settlement.ts +221 -589
  93. package/src/public-cli/terminal.ts +1 -61
  94. package/src/role-envelope.ts +4 -2
  95. package/src/role-run-placement.ts +65 -17
  96. package/src/role-run-relocation.ts +110 -70
  97. package/src/role-runtime.ts +46 -69
  98. package/src/run-dossier-files.ts +33 -0
  99. package/src/run-dossier.ts +357 -0
  100. package/src/run-terminal-artifacts.ts +84 -240
  101. package/src/run-ticket-number.ts +82 -14
  102. package/src/session-assistant-usage.ts +8 -20
  103. package/src/session-identity.ts +52 -21
  104. package/src/sitian-appender.ts +33 -26
  105. package/src/sitian-facade.ts +22 -0
  106. package/src/sitian-reader.ts +62 -32
  107. package/src/submission-gate.ts +1 -1
  108. package/src/submission-ledger.ts +4 -4
  109. package/src/ticket-provenance-contracts.ts +3 -6
  110. package/src/ticket-provenance.ts +23 -11
  111. package/src/ticket-snapshot.ts +3 -2
  112. package/src/ticket-trajectory.ts +95 -109
@@ -1,6 +1,6 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { basename } from "node:path";
3
- import { sitianReport } from "./sitian-facade.js";
3
+ import { sitianReportSafe } from "./host-session-record.js";
4
4
  import {
5
5
  NavigatorUnavailableError,
6
6
  navigatorProviderFailureFromPublicTerminal,
@@ -9,6 +9,7 @@ import {
9
9
  parseNavigatorModelSetting
10
10
  } from "./navigator-session-contracts.js";
11
11
  import { runDirectoryFromHostContext } from "./host-contracts.js";
12
+ import { parseRunLeaf } from "./role-run-placement.js";
12
13
  import { CliUsageError } from "./public-cli/cli-errors.js";
13
14
  import { isNavigatorSeat } from "./packaged-role-registry.js";
14
15
  import {
@@ -32,11 +33,9 @@ async function resolveNavigatorLedgerHome(context) {
32
33
  }
33
34
  const NAVIGATOR_HOST_RUN_POINTER_ENTRY = "ak-navigator-host-run";
34
35
  function runIdFromNavigatorDirectory(runDirectory) {
35
- const entry = basename(runDirectory);
36
- const suffix = "@navigator";
37
- if (!entry.endsWith(suffix)) return void 0;
38
- const runId = entry.slice(0, entry.length - suffix.length);
39
- return runId.length > 0 ? runId : void 0;
36
+ const parsed = parseRunLeaf(basename(runDirectory));
37
+ if (parsed === void 0 || parsed.role !== "navigator") return void 0;
38
+ return parsed.runId;
40
39
  }
41
40
  function readNavigatorHostRunPointer(entries) {
42
41
  for (let index = entries.length - 1; index >= 0; index -= 1) {
@@ -188,17 +187,14 @@ function createNativeNavigatorSessionFactory(deps) {
188
187
  routePlaybookReadFailure: () => routePlaybookReadFailure,
189
188
  appendEntry: (customType, data) => {
190
189
  sessionManager.appendCustomEntry(customType, data);
191
- try {
192
- sitianReport({
193
- level: "event",
194
- kind: "attendance",
195
- cwd: context.cwd,
196
- sessionParent: sessionManager.getSessionFile(),
197
- payload: { customType, data },
198
- source: "navigator-public-session"
199
- });
200
- } catch {
201
- }
190
+ sitianReportSafe({
191
+ level: "event",
192
+ kind: "attendance",
193
+ cwd: context.cwd,
194
+ sessionParent: sessionManager.getSessionFile(),
195
+ payload: { customType, data },
196
+ source: "navigator-public-session"
197
+ });
202
198
  },
203
199
  entries: () => sessionManager.getEntries(),
204
200
  setModel: async (next, nextThinking) => {
@@ -8,6 +8,7 @@ import { lstat, realpath } from "node:fs/promises";
8
8
  import { resolveBookKeyFromGit } from "./activation-ledger-git.js";
9
9
  import { activationBookDirectory, physicalPathIdentity, resolveActivationLedgerHome, } from "./activation-ledger-topology.js";
10
10
  import { findRunDirectoryById, readRoleRunIdentity } from "./public-cli/run-lifecycle.js";
11
+ import { formatRunLeaf, parseRunLeaf } from "./role-run-placement.js";
11
12
  async function readRetainedIdentity(runDirectory) {
12
13
  try {
13
14
  return await readRoleRunIdentity(runDirectory);
@@ -20,21 +21,12 @@ async function readRetainedIdentity(runDirectory) {
20
21
  });
21
22
  }
22
23
  }
23
- // Run directory leaf: <runId>@<role>. Production uses uuidv7 runIds; offline tracers
24
- // may use deterministic non-uuid leaves. Role token stays identifier-shaped.
25
- const RUN_DIR_NAME = /^([A-Za-z0-9][A-Za-z0-9._-]*)@([A-Za-z][A-Za-z0-9_-]*)$/;
26
24
  export class NotarySourceRunError extends Error {
27
25
  constructor(message, options) {
28
26
  super(message, options);
29
27
  this.name = "NotarySourceRunError";
30
28
  }
31
29
  }
32
- function parseRunDirectoryName(name) {
33
- const match = RUN_DIR_NAME.exec(name);
34
- if (match === null)
35
- return undefined;
36
- return { runId: match[1], role: match[2] };
37
- }
38
30
  async function requireRunDirectory(candidate, display) {
39
31
  let real;
40
32
  try {
@@ -53,7 +45,7 @@ async function requireRunDirectory(candidate, display) {
53
45
  if (!stat.isDirectory()) {
54
46
  throw new NotarySourceRunError(`notary --source-run must be a run directory: ${display}`);
55
47
  }
56
- const identity = parseRunDirectoryName(basename(real));
48
+ const identity = parseRunLeaf(basename(real));
57
49
  if (identity === undefined) {
58
50
  throw new NotarySourceRunError(`notary --source-run must be named <runId>@<role>: ${basename(real)}`);
59
51
  }
@@ -66,7 +58,7 @@ async function requireRunDirectory(candidate, display) {
66
58
  * - absolute/relative path that realpath's to that same book runs slot
67
59
  *
68
60
  * Rejects project-tree projections and any path outside the project's ledger book runs.
69
- * Retained `run-state.json` must match basename identity and book binding.
61
+ * Retained state.jsonl run-state must match basename identity and book binding.
70
62
  */
71
63
  export async function resolveNotarySourceRunLocator(options) {
72
64
  const raw = options.sourceRun.trim();
@@ -78,14 +70,14 @@ export async function resolveNotarySourceRunLocator(options) {
78
70
  const bookDirectory = activationBookDirectory(ledgerHome, bookKey);
79
71
  const bookRunsRoot = join(bookDirectory, "runs");
80
72
  let candidate;
81
- const bare = parseRunDirectoryName(raw);
73
+ const bare = parseRunLeaf(raw);
82
74
  if (bare !== undefined && !raw.includes("/") && !raw.includes("\\")) {
83
75
  candidate = (await findRunDirectoryById(options.home, bare.runId, bookKey, bare.role))
84
- ?? join(bookRunsRoot, `${bare.runId}@${bare.role}`);
76
+ ?? join(bookRunsRoot, formatRunLeaf(bare.runId, bare.role));
85
77
  }
86
78
  else {
87
79
  candidate = isAbsolute(raw) ? raw : resolve(options.projectRoot, raw);
88
- const identity = parseRunDirectoryName(basename(candidate));
80
+ const identity = parseRunLeaf(basename(candidate));
89
81
  const subjectDirectory = dirname(dirname(candidate));
90
82
  const isLegacyUnboundLocator = identity !== undefined &&
91
83
  basename(dirname(candidate)) === "runs" &&
@@ -96,7 +88,7 @@ export async function resolveNotarySourceRunLocator(options) {
96
88
  }
97
89
  }
98
90
  const real = await requireRunDirectory(candidate, raw);
99
- const identity = parseRunDirectoryName(basename(real));
91
+ const identity = parseRunLeaf(basename(real));
100
92
  const bookIdentity = physicalPathIdentity(bookDirectory);
101
93
  const parentIdentity = physicalPathIdentity(dirname(real));
102
94
  const subjectBookIdentity = physicalPathIdentity(dirname(dirname(dirname(real))));
@@ -128,7 +120,7 @@ export async function resolveNotarySourceRunLocator(options) {
128
120
  /** Internal activation loader: path is the admitted absolute source run directory. */
129
121
  export async function loadNotarySourceRunLocator(path) {
130
122
  const real = await requireRunDirectory(path, path);
131
- const identity = parseRunDirectoryName(basename(real));
123
+ const identity = parseRunLeaf(basename(real));
132
124
  const runState = await readRetainedIdentity(real);
133
125
  if (runState === undefined) {
134
126
  throw new NotarySourceRunError("notary source-run lacks retained run-state identity");
@@ -88,14 +88,7 @@ const PUBLIC_ROLE_RECORDS = [
88
88
  outputTool: FIXER_OUTPUT_TOOL_NAME,
89
89
  settlement: "sealed",
90
90
  artifactFace: {
91
- reportPhase: true,
92
- evidenceRole: true,
93
- leaves: [
94
- { key: "phase" },
95
- { key: "packetPath" },
96
- { key: "prerequisitesPath", omitUndefined: true },
97
- { key: "prerequisites" }
98
- ]
91
+ reportPhase: true
99
92
  },
100
93
  boardPlacement: "marshal",
101
94
  activationFlags: [
@@ -126,12 +119,7 @@ const PUBLIC_ROLE_RECORDS = [
126
119
  outputTool: CODER_OUTPUT_TOOL_NAME,
127
120
  settlement: "sealed",
128
121
  artifactFace: {
129
- reportPhase: true,
130
- evidenceRole: true,
131
- leaves: [
132
- { key: "phase" },
133
- { key: "taskPath" }
134
- ]
122
+ reportPhase: true
135
123
  },
136
124
  activationFlags: [
137
125
  { field: "taskPath", flag: "ak-coder-task", binds: "input" },
@@ -165,15 +153,7 @@ const PUBLIC_ROLE_RECORDS = [
165
153
  settlement: "sealed",
166
154
  /** Publish only an accepted ledger outcome; audit escalation stays unsettled here. */
167
155
  sealedAcceptedOnly: true,
168
- artifactFace: {
169
- evidenceRole: true,
170
- leaves: [
171
- { key: "baseRevision" },
172
- { key: "lens" },
173
- { key: "authorityRefs", copyArray: true },
174
- { key: "callerProvenance", callerProvenance: true }
175
- ]
176
- },
156
+ artifactFace: {},
177
157
  /** Frozen base/lens/authority become the initial prompt; instruction follows. */
178
158
  transportPrompt: "skill-args",
179
159
  boardPlacement: "marshal",
@@ -204,14 +184,7 @@ const PUBLIC_ROLE_RECORDS = [
204
184
  bareCommand: false,
205
185
  outputTool: COLLECTOR_OUTPUT_TOOL,
206
186
  settlement: "sealed",
207
- artifactFace: {
208
- evidenceRole: true,
209
- leaves: [
210
- { key: "prNumber", omitUndefined: true },
211
- { key: "repository", from: "repository.canonical" },
212
- { key: "manifestDigest" }
213
- ]
214
- },
187
+ artifactFace: {},
215
188
  boardPlacement: "collector",
216
189
  /** Taishi acceptance is a typed groups array, not a status leaf. */
217
190
  analystTerminal: "groups",
@@ -240,12 +213,6 @@ const PUBLIC_ROLE_RECORDS = [
240
213
  auditTool: DOCTOR_AUDIT_TOOL_NAME,
241
214
  settlement: "sealed",
242
215
  artifactFace: {
243
- evidenceRole: true,
244
- leaves: [
245
- { key: "issueNumber" },
246
- { key: "caseRunsPath" },
247
- { key: "caseIdentity" }
248
- ],
249
216
  doctorReportFacts: true
250
217
  },
251
218
  /** Navigator subject is the doctor case, not the case-path file bytes. */
@@ -271,13 +238,7 @@ const PUBLIC_ROLE_RECORDS = [
271
238
  phases: [null],
272
239
  outputTool: MERGER_OUTPUT_TOOL_NAME,
273
240
  settlement: "sealed",
274
- artifactFace: {
275
- evidenceRole: true,
276
- leaves: [
277
- { key: "mergerInputPath" },
278
- { key: "derived" }
279
- ]
280
- },
241
+ artifactFace: {},
281
242
  activationFlags: [
282
243
  { field: "inputPath", from: "mergerInputPath", flag: "ak-merger-input", binds: "input" }
283
244
  ],
@@ -297,8 +258,6 @@ const PUBLIC_ROLE_RECORDS = [
297
258
  sourceRunStored: "raw",
298
259
  argvResult: "source-run",
299
260
  sameParent: "source-locator",
300
- /** Infrastructure failure at this stage has no accepted gate cycle to project. */
301
- skipGateOnInfrastructureStage: true,
302
261
  /** 符宝郎 is a review officer and inherits the gatekeeper model. */
303
262
  reviewOfficer: true,
304
263
  provinceConfig: true,
@@ -406,8 +365,6 @@ const PUBLIC_ROLE_RECORDS = [
406
365
  sameParent: "gate-pointer",
407
366
  /** Resume restores an optional source-run path onto the admitted face. */
408
367
  resumeSourcePath: true,
409
- /** Infrastructure failure at this stage has no accepted gate cycle to project. */
410
- skipGateOnInfrastructureStage: true,
411
368
  /** 台院 is a review officer and inherits the gatekeeper model. */
412
369
  reviewOfficer: true,
413
370
  provinceConfig: true,
@@ -439,8 +396,6 @@ const PUBLIC_ROLE_RECORDS = [
439
396
  presentSettled: "always",
440
397
  summonResume: true,
441
398
  admission: "instruction",
442
- /** Infrastructure failure at this stage has no accepted gate cycle to project. */
443
- skipGateOnInfrastructureStage: true,
444
399
  sameParent: "none",
445
400
  /** Province model root. Officers name this seat via modelInheritsFrom. */
446
401
  provinceConfig: true,
@@ -608,11 +563,6 @@ function packagedAuditToolName(role) {
608
563
  if (record === void 0 || !("auditTool" in record)) return void 0;
609
564
  return record.auditTool;
610
565
  }
611
- function packagedSkipsGateOnInfrastructureStage(stage) {
612
- return typeof stage === "string" && PUBLIC_ROLE_RECORDS.some(
613
- (record) => "skipGateOnInfrastructureStage" in record && record.skipGateOnInfrastructureStage === true && record.role === stage
614
- );
615
- }
616
566
  function packagedDurableOfficerEntry(officer) {
617
567
  return typeof officer === "string" && PUBLIC_ROLE_RECORDS.some(
618
568
  (record) => "durableOfficerEntry" in record && record.durableOfficerEntry === true && record.role === officer
@@ -699,7 +649,6 @@ export {
699
649
  packagedRoleOutputTool,
700
650
  packagedRolePhaseFlag,
701
651
  packagedRoleSessionMaterials,
702
- packagedSkipsGateOnInfrastructureStage,
703
652
  packagedStoresSourceRunRaw,
704
653
  packagedSubjectChoices
705
654
  };
@@ -1,7 +1,6 @@
1
- import { join } from "node:path";
2
1
  import { resolveBookKeyFromGit } from "../activation-ledger-git.js";
3
2
  import { resolveActivationLedgerHome } from "../activation-ledger-topology.js";
4
- import { roleRunPlacement } from "../role-run-placement.js";
3
+ import { roleRunPlacement, sessionFileIn } from "../role-run-placement.js";
5
4
  function encode(coordinates) {
6
5
  return coordinates;
7
6
  }
@@ -47,7 +46,7 @@ export const piDurablePrincipalAuthority = {
47
46
  sessionDirectory: record.sessionDirectory,
48
47
  sessionFile: typeof record.sessionFile === "string" && record.sessionFile.trim() !== ""
49
48
  ? record.sessionFile
50
- : join(record.sessionDirectory, "session.jsonl"),
49
+ : sessionFileIn(record.sessionDirectory),
51
50
  };
52
51
  },
53
52
  };
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { execFile, spawn } from "node:child_process";
7
7
  import { constants } from "node:fs";
8
- import { access, appendFile, readFile, realpath } from "node:fs/promises";
8
+ import { access, appendFile, realpath } from "node:fs/promises";
9
9
  import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
10
10
  import { platform } from "node:process";
11
11
  import { promisify } from "node:util";
@@ -14,9 +14,13 @@ import { ExplicitInternalActivationError } from "../host-contracts.js";
14
14
  import { applyEngineChildEnv, ENGINE_MODEL_FLAG_NAME, normalizeEngineName } from "../engine-detour.js";
15
15
  import { projectActivationFlags } from "../role-activation-flags.js";
16
16
  import { encodeUserDialogueStdin } from "../user-dialogue-stdin.js";
17
+ import { readLedgerSessionJsonlLines, readStrictPiSessionJsonl } from "../ledger-session-read.js";
18
+ import { copyAndRecordHostDossier } from "../host-session-record.js";
17
19
  import { projectThrownFailureLeaf, retainPackageFault } from "../public-cli/settlement.js";
18
20
  import { describeErrorIdentity } from "../public-cli/run-lifecycle.js";
19
21
  import { RECEIPT_DELIVERY_LIMIT_ENV } from "../receipt-delivery-policy.js";
22
+ import { createSessionIdentityAuthority } from "../session-identity.js";
23
+ import { sitianReport } from "../sitian-facade.js";
20
24
  /** Package-relative Internal role entrypoint (ADR 0052; same path as public-cli registry). */
21
25
  const INTERNAL_ROLE_ENTRYPOINT_RELATIVE = "extensions/role-runtime.ts";
22
26
  export function resolveInternalRoleEntrypoint(packageRoot) {
@@ -350,39 +354,99 @@ export function createPiRoleTurnHost(config) {
350
354
  await config.recordLaunchedRolePackageIdentity(request.runDirectory, await config.observeLaunchedRolePackageIdentity(config.packageRoot, roleEntry));
351
355
  }
352
356
  const timeoutMs = request.timeoutMs ?? config.timeoutMs;
353
- return await spawnRunner(args, {
357
+ const result = await spawnRunner(args, {
354
358
  cwd: request.cwd,
355
359
  env,
356
360
  stdin,
357
361
  ...(timeoutMs === undefined ? {} : { timeoutMs }),
358
362
  ...(request.signal === undefined ? {} : { signal: request.signal }),
359
363
  });
364
+ // Deliver already-written Pi host facts. Pi lands at session/session.jsonl;
365
+ // bind projects host.sessions; copy records the landing. Host adapters do
366
+ // not write current.json — public bind/settlement seams own that render.
367
+ try {
368
+ const { sessionFile, sessionDirectory } = config.principalAuthority.decode(request.principal);
369
+ const sessionId = await readPiSessionHeaderId(sessionFile);
370
+ if (sessionId !== undefined) {
371
+ await createSessionIdentityAuthority(config.principalAuthority, "pi").bind(request.principal, sessionId);
372
+ copyAndRecordHostDossier({
373
+ host: "pi",
374
+ sessionId,
375
+ cwd: request.cwd,
376
+ sessionDirectory,
377
+ sessionParent: sessionFile,
378
+ ...(request.home !== undefined ? { home: request.home } : {}),
379
+ });
380
+ }
381
+ }
382
+ catch (error) {
383
+ await retainPackageFault({
384
+ runDirectory: request.runDirectory,
385
+ diagnostic: `pi host dossier record failed beside host terminal: ${describeErrorIdentity(error)}`,
386
+ error,
387
+ });
388
+ }
389
+ return result;
360
390
  },
361
391
  };
362
392
  }
363
- import { sitianReport } from "../sitian-facade.js";
364
393
  /**
365
- * Append one custom JSONL entry to the durable principal's session file.
366
- * Pi session codec only — AK artifact O_EXCL retention stays in public-cli.
394
+ * Session header id from the Pi volume the child (or fixture) already wrote.
395
+ * Reuses the ledger line kernel: a later malformed line must not erase a valid
396
+ * header already present (ADR 0086 originals are not validated whole-file).
367
397
  */
368
- export async function appendPiSessionCustomEntry(authority, principal, customType, data) {
369
- const { sessionFile } = authority.decode(principal);
370
- const text = await readFile(sessionFile, "utf8");
398
+ async function readPiSessionHeaderId(sessionFile) {
399
+ try {
400
+ for (const line of await readLedgerSessionJsonlLines(sessionFile)) {
401
+ const entry = line.row;
402
+ if (entry === undefined || entry.type !== "session")
403
+ continue;
404
+ if (typeof entry.id === "string" && entry.id.trim() !== "")
405
+ return entry.id;
406
+ }
407
+ }
408
+ catch (error) {
409
+ if (error.code === "ENOENT")
410
+ return undefined;
411
+ throw error;
412
+ }
413
+ return undefined;
414
+ }
415
+ /** Last non-session entry id. Session headers are not parents of custom lines. */
416
+ export function piSessionCustomParentId(entries) {
371
417
  let parentId = null;
372
- for (const line of text.trim().split("\n").filter(Boolean)) {
373
- const entry = JSON.parse(line);
418
+ for (const entry of entries) {
374
419
  if (typeof entry.id === "string" && entry.type !== "session")
375
420
  parentId = entry.id;
376
421
  }
377
- const timestamp = new Date().toISOString();
378
- const pointerLine = `${JSON.stringify({
422
+ return parentId;
423
+ }
424
+ /** One Pi custom JSONL line: type, customType, data, id, parentId, timestamp. */
425
+ export function formatPiSessionCustomEntry(input) {
426
+ return `${JSON.stringify({
379
427
  type: "custom",
428
+ customType: input.customType,
429
+ data: input.data,
430
+ id: randomUUID(),
431
+ parentId: input.parentId,
432
+ timestamp: input.timestamp,
433
+ })}\n`;
434
+ }
435
+ /**
436
+ * Append one custom JSONL entry to the durable principal's session file.
437
+ * Pi session codec only — AK artifact O_EXCL retention stays in public-cli.
438
+ * Read is raw JSON.parse (SyntaxError propagates). Sitian full-traversal stays elsewhere.
439
+ */
440
+ export async function appendPiSessionCustomEntry(authority, principal, customType, data) {
441
+ const { sessionFile } = authority.decode(principal);
442
+ const entries = await readStrictPiSessionJsonl(sessionFile);
443
+ const timestamp = new Date().toISOString();
444
+ const pointerLine = formatPiSessionCustomEntry({
380
445
  customType,
381
446
  data,
382
- id: randomUUID(),
383
- parentId,
447
+ parentId: piSessionCustomParentId(entries),
384
448
  timestamp,
385
- })}\n`;
449
+ });
386
450
  await appendFile(sessionFile, pointerLine, "utf8");
387
451
  sitianReport({
388
452
  level: "event",
@@ -1,27 +1,10 @@
1
- /**
2
- * Single generic auto-resume loop for #416 (owner scope = single LLM call).
3
- * Call-local retries, at most the effective autoResumeLimit times (injected once
4
- * per call by the caller, #422 — never re-read from disk inside the loop),
5
- * in-place (same runId/session).
6
- * Unifies presentation: intermediate attempts use dummyIo, only final Terminal is presented.
7
- *
8
- * Owner 2026-08-23: a dispatch that exits by throwing used to bypass the entire
9
- * retry mechanism (the throw escaped the while-loop before the count check ever
10
- * ran). Every exception is now retained whole, in place, and the ordinary retry
11
- * path continues: same budget, same call-local count semantics. No failure-type
12
- * classification — every thrown value is treated identically.
13
- */
14
- import { constants as fsConstants } from "node:fs";
15
- import { randomUUID } from "node:crypto";
16
- import { open } from "node:fs/promises";
17
- import { join } from "node:path";
18
1
  import { AUTO_RESUME_LIMIT, describeErrorIdentity, acquireRunWriterLease, markRunTerminal, RunWriterLeaseHeldError, } from "./run-lifecycle.js";
19
2
  import { parseAutoResumeLimit } from "./config.js";
20
3
  import { processCancelSignalName } from "./process-cancel.js";
21
4
  import { isLawfulTypedTerminalOutcome, formatTerminalResult } from "./terminal.js";
22
- import { attachRecordedSubmissions, ensureRealArtifactsDirectory, presentFailureTerminal, retainPackageFault, } from "./settlement.js";
23
- export { ensureRealArtifactsDirectory };
5
+ import { attachRecordedSubmissions, presentFailureTerminal, retainPackageFault, } from "./settlement.js";
24
6
  import { serializeThrownValue } from "../serialize-thrown-value.js";
7
+ import { reportRunRecord } from "../sitian-facade.js";
25
8
  import { errorText } from "../unknown-value.js";
26
9
  const dummyIo = { stdout: () => { }, stderr: () => { } };
27
10
  /**
@@ -105,41 +88,19 @@ function jsonSafeReplacer() {
105
88
  };
106
89
  }
107
90
  /**
108
- * Retain one throwing dispatch attempt's complete exception as an independent
109
- * per-attempt file under the run's artifacts directory, then leave an
110
- * addressable pointer in the session principal (custom entry). Exclusive-create
111
- * open (O_EXCL) with a per-attempt unique name enforces 史必追加 (#419): a later
112
- * attempt can never overwrite an earlier attempt's file.
91
+ * Retain one throwing dispatch attempt's complete exception as a line in the
92
+ * run's log.jsonl, then leave an addressable pointer in the session principal
93
+ * (custom entry). The log only appends (史必追加, #419): a later attempt can
94
+ * never overwrite an earlier attempt's record.
113
95
  */
114
- /**
115
- * Hardened create-once JSON write shared by every durable artifact this loop
116
- * retains directly (dispatch-error dumps, lawful-persist-failure error/
117
- * evidence records): O_EXCL (fail loud on a colliding name, never overwrite)
118
- * + O_NOFOLLOW where the platform provides it (a planted symlink is never
119
- * followed) — mirrors settlement.ts's own hardened artifact writers.
120
- */
121
- export async function writeHardenedArtifactFile(artifactsDir, namePrefix, payload) {
122
- const filePath = join(artifactsDir, `${namePrefix}-${randomUUID()}.json`);
123
- const body = `${JSON.stringify(payload, jsonSafeReplacer(), 2)}\n`;
124
- const noFollowFlag = typeof fsConstants.O_NOFOLLOW === "number" ? fsConstants.O_NOFOLLOW : 0;
125
- const handle = await open(filePath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | noFollowFlag, 0o600);
126
- try {
127
- await handle.writeFile(body, "utf8");
128
- }
129
- finally {
130
- await handle.close();
131
- }
132
- return filePath;
133
- }
134
96
  async function retainDispatchError(admitted, principalAuthority, sessionAppender, attempt, error) {
135
- const artifactsDir = await ensureRealArtifactsDirectory(admitted.runDirectory);
136
97
  // Whole-object dump: everything the thrown value carries, nothing picked.
137
- const filePath = await writeHardenedArtifactFile(artifactsDir, `dispatch-error-attempt-${attempt}`, {
98
+ const filePath = reportRunRecord(admitted.runDirectory, "dispatch-exception", JSON.parse(JSON.stringify({
138
99
  version: 1,
139
100
  attempt,
140
101
  recordedAt: new Date().toISOString(),
141
102
  error: serializeThrownValue(error),
142
- });
103
+ }, jsonSafeReplacer())), "auto-resume").recordFile;
143
104
  // Addressable pointer in the dossier (卷宗): Pi session custom-entry codec
144
105
  // (appendPiSessionCustomEntry). Lease still owned here with run-writer.
145
106
  let pointerLease;
@@ -29,11 +29,11 @@ import { packagedAdmitsCountersign, packagedBindsBoardTicket, packagedRebindSour
29
29
  import { readAuditorResumeBinding } from "../auditor-soul.js";
30
30
  import { CliUsageError } from "./cli-errors.js";
31
31
  import { admitPublicRole, bindAdmittedTicketNumber, buildInstructionTransportPrompt, materializeCountersignInvocation, persistAdmittedSourceRunPath, recordAdmittedCorrelation, recordChildDiaristRun, relocateAdmittedRunToTicket, withPreparedAttachments, } from "./invocation.js";
32
- import { presentControlledFailure, packageFaultNoteFor, prepareSummonsResumeMaterials, roleTurnOptions, runPostAdmissionOneShot, runPostAdmissionResumable, runPostAdmissionSeatResume, showResumeErrorPointer, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
32
+ import { bindDiaristAssertedTicketNumber, presentControlledFailure, packageFaultNoteFor, prepareSummonsResumeMaterials, roleTurnOptions, runPostAdmissionOneShot, runPostAdmissionResumable, runPostAdmissionSeatResume, showResumeErrorPointer, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
33
33
  import { presentTerminal } from "./auto-resume.js";
34
34
  import { buildAutoResumeContinuationPrompt, loadResumablePublicRole, markRunAdmitted, parentRunPathFromGatePointerInstruction, readCurrentCourt, } from "./run-lifecycle.js";
35
35
  import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
36
- import { presentStructuralRejection, readBoundSessionEntries, attachPostAuditProjection, trySettlePublicSeat, } from "./settlement.js";
36
+ import { presentStructuralRejection, readBoundSessionEntries, attachPostAuditCountersignFact, trySettlePublicSeat, } from "./settlement.js";
37
37
  import { warnMissingMethodSkills } from "./machine-method-skills.js";
38
38
  import { formatTerminalResult, isLawfulTypedTerminalOutcome, } from "./terminal.js";
39
39
  import { admittedSeatTurnDetails, projectRoleTurnRequest, requiredMethodSkills, } from "./turn-request.js";
@@ -184,6 +184,8 @@ function isBoardTicketSeat(seat) {
184
184
  return packagedBindsBoardTicket(seat.role);
185
185
  }
186
186
  async function bindAndRelocateDiarist(admitted, authority, lease) {
187
+ // The diarist's asserted ticketNumber binds here, in the public call's process.
188
+ await bindDiaristAssertedTicketNumber(admitted);
187
189
  const boardTicket = await readBoardTicketNumber(admitted.runDirectory);
188
190
  if (boardTicket === undefined)
189
191
  return undefined;
@@ -856,7 +858,7 @@ export async function continueParentAfterChild(parentRunId, child, env, io) {
856
858
  if (stopped.exitCode === 0
857
859
  && heldUnreadableTerminal(stopped.terminal)
858
860
  && stopped.terminal !== undefined) {
859
- const parentTerminal = await trySettlePublicSeat(admitted, env.principalAuthority, undefined);
861
+ const parentTerminal = await trySettlePublicSeat(admitted, env.principalAuthority, { ...await readCurrentCourt(admitted.runDirectory), ...packageFaultScope(admitted, env, io) });
860
862
  if (parentTerminal?.roleOutcome.kind === "accepted"
861
863
  || parentTerminal?.roleOutcome.kind === "audit_escalation") {
862
864
  const terminal = await presentUnsettledAudit(parentTerminal, stopped.terminal, io, admitted.runDirectory);
@@ -870,7 +872,7 @@ export async function continueParentAfterChild(parentRunId, child, env, io) {
870
872
  }
871
873
  if (resolved !== undefined && (resolved.status === "continue" || resolved.status === "converged")) {
872
874
  if (AUDITED_ROLES.has(admitted.role) && resolved.status === "converged") {
873
- const terminal = await trySettlePublicSeat(admitted, env.principalAuthority, packageFaultScope(admitted, env, io));
875
+ const terminal = await trySettlePublicSeat(admitted, env.principalAuthority, { ...await readCurrentCourt(admitted.runDirectory), ...packageFaultScope(admitted, env, io) });
874
876
  if (terminal?.roleOutcome.kind !== "accepted")
875
877
  throw new Error("pending submission is not recorded");
876
878
  return auditSubmittedRole({ exitCode: 0, admitted, terminal }, env, io, resolved.admitted, latestQueuePayload(resolved.terminal));
@@ -1151,11 +1153,11 @@ async function auditSubmittedRole(turn, env, io, resumedOfficer, passedReceipt)
1151
1153
  }
1152
1154
  }
1153
1155
  // The turn already settled this court (payloads, usage, autoResumeCount, history).
1154
- // Audit only adds gate rounds and, for 中书省, the officer fact. A second
1155
- // settle would republish the same attempt.
1156
+ // Audit only adds, for 中书省, the officer fact. A second settle would
1157
+ // republish the same attempt.
1156
1158
  if (turn.terminal === undefined)
1157
1159
  throw new Error(`audited ${admitted.role} submission did not settle`);
1158
- const terminal = await attachPostAuditProjection(admitted, env.principalAuthority, turn.terminal, packageFaultScope(admitted, env, io));
1160
+ const terminal = await attachPostAuditCountersignFact(admitted, env.principalAuthority, turn.terminal, packageFaultScope(admitted, env, io));
1159
1161
  turn = { ...turn, terminal };
1160
1162
  await presentTerminal(terminal, { ...io, omitFailureStderrDiagnostic: true }, admitted.runDirectory);
1161
1163
  return turn;