@akagilnc/pi-workflow-roles 0.1.5762 → 0.1.5797

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 (52) hide show
  1. package/dist/acp-host/production-host.js +685 -285
  2. package/dist/external-host-turn-loop.js +3 -0
  3. package/dist/headless-host/production-host.js +658 -258
  4. package/dist/migrate-book-topology.js +70 -29
  5. package/dist/navigator-attendance.js +1 -1
  6. package/dist/packaged-role-registry.js +3 -2
  7. package/dist/pi/role-turn-host.js +6 -1
  8. package/dist/public-cli/instruction-seat-run.js +152 -60
  9. package/dist/public-cli/invocation.js +98 -46
  10. package/dist/public-cli/main.js +482 -222
  11. package/dist/public-cli/option-definitions.js +4 -4
  12. package/dist/public-cli/post-admission.js +19 -28
  13. package/dist/public-cli/run-lifecycle.js +10 -21
  14. package/dist/public-cli/turn-request.js +3 -0
  15. package/dist/public-role-summons.js +11 -8
  16. package/dist/report-ticket-tool.js +3 -3
  17. package/dist/role-run-placement.js +14 -2
  18. package/dist/role-run-relocation.js +2 -2
  19. package/dist/run-dossier.js +5 -4
  20. package/dist/run-terminal-artifacts.js +0 -7
  21. package/dist/session-identity.js +3 -2
  22. package/dist/sitian-appender.js +12 -3
  23. package/dist/submission-ledger.js +24 -0
  24. package/dist/ticket-progress.js +306 -0
  25. package/dist/user-dialogue-stdin.js +23 -17
  26. package/package.json +1 -1
  27. package/src/external-host-turn-loop.ts +7 -1
  28. package/src/host-contracts.ts +1 -1
  29. package/src/navigator-attendance.ts +2 -1
  30. package/src/packaged-role-registry.ts +3 -2
  31. package/src/pi/adapter.ts +57 -45
  32. package/src/pi/role-turn-host.ts +6 -1
  33. package/src/prepared-role-turn.ts +6 -0
  34. package/src/public-cli/instruction-seat-run.ts +166 -68
  35. package/src/public-cli/invocation.ts +120 -46
  36. package/src/public-cli/option-definitions.ts +5 -4
  37. package/src/public-cli/post-admission.ts +19 -30
  38. package/src/public-cli/run-lifecycle.ts +10 -27
  39. package/src/public-cli/turn-request.ts +3 -0
  40. package/src/public-role-summons.ts +27 -16
  41. package/src/report-ticket-tool.ts +7 -3
  42. package/src/role-envelope.ts +5 -0
  43. package/src/role-run-placement.ts +19 -2
  44. package/src/role-run-relocation.ts +2 -2
  45. package/src/role-runtime.ts +19 -2
  46. package/src/run-dossier.ts +5 -3
  47. package/src/run-terminal-artifacts.ts +0 -8
  48. package/src/session-identity.ts +3 -2
  49. package/src/sitian-appender.ts +14 -3
  50. package/src/submission-ledger.ts +27 -0
  51. package/src/ticket-progress.ts +383 -0
  52. package/src/user-dialogue-stdin.ts +21 -14
@@ -509,8 +509,8 @@ const NOTARY_OPTIONS = [
509
509
  repeatable: false,
510
510
  form: "option",
511
511
  description: {
512
- en: "Required source run locator (runId@role under the book home, or path to that run directory). Zero prompt/attachment projection.",
513
- zh: "必填源 run 定位符(簿内 runId@role,或该 run 目录路径)。零 prompt/附件投影。",
512
+ en: "Required source run locator (runId@role under the book home, or path to that run directory). Binds the audited identity; review dialogue is the fixed Notary dispatch (verdict stays in the ledger). Missing-ticket and unreadable-status reasks keep their existing diagnostic text.",
513
+ zh: "必填源 run 定位符(簿内 runId@role,或该 run 目录路径)。绑定受审身份;审稿对话为符宝郎固定派单文(判词留在卷宗自行读取)。缺票号与读不出 status 的重交仍递既有诊断原文。",
514
514
  },
515
515
  },
516
516
  ];
@@ -1040,7 +1040,7 @@ const ROLE_COMMAND_HELP = {
1040
1040
  },
1041
1041
  notary: {
1042
1042
  command: "notary",
1043
- summary: "Direct Notary summons; duties: souls/notary.md. Zero prompt/attachment.",
1043
+ summary: "Direct Notary summons; duties: souls/notary.md. Review uses fixed dispatch — binds --source-run identity; does not accept caller review prompt; verdict stays in the ledger. Missing-ticket (#1171) and unreadable-status reasks deliver their existing diagnostic text on the same seat, not the review sentence.",
1044
1044
  usage: ["ak-role notary --source-run <runId@role|path> [options]"],
1045
1045
  examples: [
1046
1046
  "ak-role notary --source-run 01a034f1-75bf-71a6-bcf5-d1299145b1a5@judge",
@@ -1101,7 +1101,7 @@ const SUPPORT_COMMAND_HELP = {
1101
1101
  },
1102
1102
  resume: {
1103
1103
  command: "resume",
1104
- summary: "Resume a role run under the live seat table (model/host/engine); session principal must still exist. Every callable seat, including Notary/符宝郎, accepts the optional caller message and passes it through unchanged as the continuation prompt. Notary explicit new still accepts only its source-run locator, not a caller prompt. Global --model/--thinking/--host/--engine must be placed before <runId> (either before `resume` or between `resume` and <runId>); the one argv after <runId> is the opaque message, not a flag position (#471).",
1104
+ summary: "Resume a role run under the live seat table (model/host/engine); session principal must still exist. Every callable seat accepts the optional caller message and passes it through unchanged as the continuation prompt. Notary new/direct review calls use only --source-run plus the fixed review dispatch (no caller review prompt); public manual resume still passes the caller message. Global --model/--thinking/--host/--engine must be placed before <runId> (either before `resume` or between `resume` and <runId>); the one argv after <runId> is the opaque message, not a flag position (#471).",
1105
1105
  usage: ["ak-role resume <runId> [message]"],
1106
1106
  examples: [
1107
1107
  "ak-role resume 01abc…",
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { randomUUID } from "node:crypto";
9
9
  import { writeFile } from "node:fs/promises";
10
- import { buildAutoResumeContinuationPrompt, findRunDirectoryById, resolveLiveRunDirectoryPath, readRoleRunState, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
10
+ import { findRunDirectoryById, resolveLiveRunDirectoryPath, readRoleRunState, } from "./run-lifecycle.js";
11
11
  import { CliUsageError } from "./cli-errors.js";
12
12
  import { projectPublicTurnAxes } from "./turn-request.js";
13
13
  import { appendCallerFileFlagPaths, bindAdmittedTicketNumber, relocateAdmittedRunToTicket, } from "./invocation.js";
@@ -394,31 +394,26 @@ function settlementScopeForTurn(request) {
394
394
  };
395
395
  }
396
396
  /**
397
- * #1132: one 没交卷催交 turn, dispatched through the SAME settlement path as the
398
- * first turn.
397
+ * #1132 / #1208: one 没交卷催交 turn, dispatched through the SAME settlement path
398
+ * as the first turn.
399
399
  *
400
- * There is deliberately no second settlement implementation here. A delivery
401
- * turn is an ordinary host turn on this same run/session: this builds the
402
- * host-neutral resume request (the adapter's stored native session id, ADR 0082;
403
- * pi's existing delivery-state content reused verbatim — no new prompt wording,
404
- * no new host capability). The caller settles it through the same
405
- * `settleCompletedHostTurn` the first turn uses (ADR 0080): the runner/host
406
- * failure re-read, the shouldPresent gate, the fresh-seal check, open-court
407
- * cleanup, cancel re-reads, run-state persist, and the stderr mirror.
400
+ * A delivery turn is an ordinary host turn on this same run/session: this builds
401
+ * the host-neutral resume request (the adapter's stored native session id,
402
+ * ADR 0082). #1208: resend this turn's actual prompt — never substitute
403
+ * delivery-state / run-stats JSON for dialogue. Counts, rejections, and
404
+ * no_receipt facts stay on receiptDelivery / lifecycle settlement.
408
405
  *
409
406
  * The caller owns the live delivery policy; the request projects its next send.
410
407
  */
411
408
  /** Bare in-call催交 resume: hostSessionId absent; adapter is the sole load authority. */
412
409
  function buildReceiptDeliveryRequest(input) {
413
410
  const { request, receiptDelivery } = input;
411
+ // #1199: progress instruction is this turn's prompt (envelope books continuation.prompt).
414
412
  return {
415
413
  ...request,
416
414
  continuation: {
417
415
  kind: "resume",
418
- prompt: JSON.stringify({
419
- ...receiptDelivery.deliveryState(),
420
- deliveryTurns: receiptDelivery.issuedDeliveryRequests() + 1,
421
- }),
416
+ prompt: request.continuation.prompt,
422
417
  },
423
418
  deliveryRequestLimit: receiptDelivery.limit,
424
419
  };
@@ -1076,6 +1071,7 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
1076
1071
  const officerDialogue = isStationChildOfficerDialogue(admitted.role, env);
1077
1072
  const fileFlags = summonsPrepared?.attachments ?? [];
1078
1073
  let prompt;
1074
+ // #1199: progress instruction = this prompt (actual seat input, file flags included).
1079
1075
  if (request.message !== undefined) {
1080
1076
  if (summonsPrepared !== undefined) {
1081
1077
  // #755: same-ticket review / open-court — caller words + caller file flags.
@@ -1099,8 +1095,8 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
1099
1095
  : appendCallerFileFlagPaths(body, fileFlags);
1100
1096
  }
1101
1097
  else if (request.summons !== undefined) {
1102
- // #879: same-ticket summons with no instruction (e.g. notary source-run binding
1103
- // only). Pointer is activation/sourceRun material — not dialogue content.
1098
+ // Same-ticket summons without prepared dialogue. Notary review fills via
1099
+ // resolveReviewSeatDialogueBody (#1208); other seats carry parent/caller words.
1104
1100
  prompt = "";
1105
1101
  }
1106
1102
  else {
@@ -1339,21 +1335,15 @@ export async function runPostAdmissionSeatResume(input) {
1339
1335
  buildInitialPayload: () => ({ resumeTurn: false }),
1340
1336
  buildResumePayload: () => ({ resumeTurn: true }),
1341
1337
  toRequest: async (payload) => {
1342
- // #840 r8 判词 class 2: this call-local retry must keep this
1343
- // court's frozen summons / 交卷 body / attachments verbatim
1344
- // (same object as firstTurn) and project only the minimal
1345
- // host-needed resume trigger — never engine handbook material,
1346
- // which would replace a 审核循环 same-ticket continuation with a bare outsourcing
1347
- // 「重新读」 envelope (#755 contract, resumeTurnRequestProjectionOptions
1348
- // above). RESUME_TRANSPORT_ENVELOPE is the same package-owned,
1349
- // non-semantic trigger that projection already uses for a
1350
- // same-ticket summons carrying no instruction/attachments.
1338
+ // #840 r8 / #1208: call-local retry keeps this court's frozen summons /
1339
+ // 交卷 body / attachments and resends the same turn prompt — never a
1340
+ // filler phrase or engine handbook substitute.
1351
1341
  if (payload.resumeTurn && firstTurn !== undefined) {
1352
1342
  return {
1353
1343
  ...firstTurn,
1354
1344
  continuation: {
1355
1345
  kind: "resume",
1356
- prompt: RESUME_TRANSPORT_ENVELOPE,
1346
+ prompt: firstTurn.continuation.prompt,
1357
1347
  },
1358
1348
  };
1359
1349
  }
@@ -1460,6 +1450,7 @@ async function writeResumeDiagnostic(runDirectory, runId, failure, role) {
1460
1450
  * entering; manual resume never re-admits.
1461
1451
  */
1462
1452
  export async function runPostAdmissionOneShot(input) {
1453
+ // #1208: auto-resume resends this turn's actual prompt, never a filler phrase.
1463
1454
  return await runPostAdmissionResumable({
1464
1455
  admitted: input.admitted,
1465
1456
  env: input.env,
@@ -1469,7 +1460,7 @@ export async function runPostAdmissionOneShot(input) {
1469
1460
  ...input.request,
1470
1461
  continuation: {
1471
1462
  kind: "resume",
1472
- prompt: buildAutoResumeContinuationPrompt(),
1463
+ prompt: input.request.continuation.prompt,
1473
1464
  },
1474
1465
  }),
1475
1466
  adapters: input.adapters,
@@ -13,7 +13,7 @@ import { findRoleRunDirectory, listBookRunDirectories, parseRunLeaf, sessionFile
13
13
  import { isSafePositiveTicketNumber } from "../run-ticket-number.js";
14
14
  import { CliUsageError } from "./cli-errors.js";
15
15
  import { parseCollectorRepository } from "../collector-config.js";
16
- import { retainedRunPathsMatch, rewriteAdmittedRoleRunPage, rewriteRunDirectoryPathValue, } from "../role-run-relocation.js";
16
+ import { retainedRunPathsMatch, rewriteAdmittedRoleRunPage, rewritePrincipalSessionPaths, } from "../role-run-relocation.js";
17
17
  import { packagedResumeSourcePath, packagedRoleMetadata, } from "../packaged-role-registry.js";
18
18
  import { admittedReviewerBaseFault, admittedSourceRunPath, recordEffectiveInvocationModel, requireAuthorityRef, isReviewerLens, normalizeAdmittedAttachment, } from "./invocation.js";
19
19
  import { readPageSync, writeSectionSync } from "../run-dossier.js";
@@ -23,23 +23,6 @@ import { isRecord, errorText, isEnoent } from "../unknown-value.js";
23
23
  * source: runWithAutoResumeLoop receives the effective value once per call.
24
24
  */
25
25
  export const AUTO_RESUME_LIMIT = 2;
26
- /**
27
- * Package-owned non-empty Chinese neutral resume transport (#959 / ADR 0073).
28
- * Used by:
29
- * - auto-resume (all seats via buildAutoResumeContinuationPrompt) — required so
30
- * hosts that reject empty stdin (codex) still receive a prompt.
31
- * Public manual resume forwards only the caller's bytes (#987).
32
- */
33
- export const RESUME_TRANSPORT_ENVELOPE = "继续。";
34
- /**
35
- * Auto-resume continuation only (#959 / ADR 0080).
36
- * Always non-empty Chinese neutral envelope. Engine / outsourcing material
37
- * rides startup readingMaterial (#1167), never this continuation.
38
- * Never call this from manual `ak-role resume`.
39
- */
40
- export function buildAutoResumeContinuationPrompt() {
41
- return RESUME_TRANSPORT_ENVELOPE;
42
- }
43
26
  const WRITER_LOCK_FILE = "writer.lock";
44
27
  export async function writeRoleRunState(runDirectory, record) {
45
28
  const payload = { ...record, runDirectory };
@@ -153,10 +136,16 @@ async function readRoleRunStateDisk(runDirectory) {
153
136
  : runDirectory;
154
137
  const runDir = runDirectory;
155
138
  // Principal wire stays uninterpreted — authority owns legacy sessionFile fallback.
139
+ // #1199: leg-local session/ moves with the run-prefix rewrite.
140
+ const principalCoords = {
141
+ sessionDirectory: record.sessionDirectory,
142
+ ...(typeof record.sessionFile === "string" ? { sessionFile: record.sessionFile } : {}),
143
+ };
144
+ rewritePrincipalSessionPaths(principalCoords, storedRunDirectory, runDirectory);
156
145
  const principalWire = {
157
- sessionDirectory: rewriteRunDirectoryPathValue(record.sessionDirectory, storedRunDirectory, runDirectory),
158
- ...(typeof record.sessionFile === "string"
159
- ? { sessionFile: rewriteRunDirectoryPathValue(record.sessionFile, storedRunDirectory, runDirectory) }
146
+ sessionDirectory: principalCoords.sessionDirectory,
147
+ ...(typeof principalCoords.sessionFile === "string"
148
+ ? { sessionFile: principalCoords.sessionFile }
160
149
  : {}),
161
150
  };
162
151
  const phase = record.phase === "plan" || record.phase === "apply"
@@ -51,6 +51,9 @@ export function projectRoleTurnRequest(admitted, roleDetails, options) {
51
51
  ...(options.deliveryRequestLimit === undefined
52
52
  ? {}
53
53
  : { deliveryRequestLimit: options.deliveryRequestLimit }),
54
+ ...(options.host === undefined || options.host.trim() === ""
55
+ ? {}
56
+ : { host: options.host.trim() }),
54
57
  };
55
58
  }
56
59
  /**
@@ -351,13 +351,9 @@ async function summonGateOfficer(options) {
351
351
  home = homeFromRunDirectory(options.sourceRunDirectory);
352
352
  }
353
353
  let gateReviewInstruction;
354
- if (options.reask === void 0 && options.submission !== void 0) {
355
- const { isTicketCourtCountersignSource } = await import("./run-terminal-artifacts.js");
356
- const blindTicketCourt = options.officer === "notary" && isTicketCourtCountersignSource(options.sourceRunDirectory);
357
- if (!blindTicketCourt) {
358
- const { readableGateItem } = await import("./readable-gate-item.js");
359
- gateReviewInstruction = readableGateItem(options.submission);
360
- }
354
+ if (options.reask === void 0 && options.submission !== void 0 && options.officer !== "notary") {
355
+ const { readableGateItem } = await import("./readable-gate-item.js");
356
+ gateReviewInstruction = readableGateItem(options.submission);
361
357
  }
362
358
  const { runIdFromRunDirectory } = await import("./run-terminal-artifacts.js");
363
359
  const parentRunId = runIdFromRunDirectory(options.sourceRunDirectory);
@@ -408,7 +404,14 @@ async function summonGateOfficer(options) {
408
404
  const parentTicket = await readBoardTicketNumber(options.sourceRunDirectory);
409
405
  const submittedTicket = isRecord(options.submission) ? options.submission.ticketNumber : void 0;
410
406
  const courtTicket = isSafePositiveTicketNumber(submittedTicket) ? submittedTicket : parentTicket;
411
- const instruction = options.reask ?? gateReviewInstruction ?? "";
407
+ const { resolveReviewSeatDialogueBody } = await import("./public-cli/instruction-seat-run.js");
408
+ const instruction = await resolveReviewSeatDialogueBody({
409
+ role: options.officer,
410
+ ...options.reask === void 0 ? {} : { reviewReask: options.reask },
411
+ ...gateReviewInstruction === void 0 ? {} : { gateReviewInstruction },
412
+ projectRoot: options.cwd,
413
+ home
414
+ }) ?? "";
412
415
  return summonPublicRole({
413
416
  role: options.officer,
414
417
  argv: ["--project", options.cwd, "--", instruction],
@@ -102,6 +102,7 @@ export async function reportTicketFromHostContext(context, rawTicketNumber, auth
102
102
  if (process.env.AK_ROLE_RUN_DIR === runDirectory) {
103
103
  process.env.AK_ROLE_RUN_DIR = nextDirectory;
104
104
  }
105
+ // Leg-local session/: rewrite the open handle under the renamed run prefix.
105
106
  const projectedSessionFile = typeof priorSessionFile === "string" && priorSessionFile.trim() !== ""
106
107
  ? rewriteRunDirectoryPathValue(priorSessionFile, runDirectory, nextDirectory)
107
108
  : sessionFileOf(nextDirectory);
@@ -109,9 +110,8 @@ export async function reportTicketFromHostContext(context, rawTicketNumber, auth
109
110
  context.sessionManager.setSessionFile?.(projectedSessionFile);
110
111
  }
111
112
  };
112
- const relocation = alreadyPlaced
113
- ? undefined
114
- : await relocateAdmittedRunToTicket(admitted, authority, { relocate: applyLiveHandles });
113
+ // Always enter relocate: unbound→rename+publish; already-on-ticket→publish leftover staging.
114
+ const relocation = await relocateAdmittedRunToTicket(admitted, authority, alreadyPlaced ? undefined : { relocate: applyLiveHandles });
115
115
  const nextDirectory = relocation?.newRunDirectory ?? admitted.runDirectory;
116
116
  if (relocation === undefined)
117
117
  applyLiveHandles(nextDirectory);
@@ -14,10 +14,20 @@ function parseRunLeaf(name) {
14
14
  if (at <= 0 || at === name.length - 1) return void 0;
15
15
  return { runId: name.slice(0, at), role: name.slice(at + 1) };
16
16
  }
17
+ const ROLE_RUN_SESSION_FILENAME = "session.jsonl";
18
+ function subjectDirectoryOfRun(runDirectory) {
19
+ const runsParent = dirname(runDirectory);
20
+ if (basename(runsParent) !== "runs") return void 0;
21
+ const subjectDirectory = dirname(runsParent);
22
+ const subjectName = basename(subjectDirectory);
23
+ if (subjectName === "unbound" || /^[1-9]\d*$/.test(subjectName)) {
24
+ return subjectDirectory;
25
+ }
26
+ return void 0;
27
+ }
17
28
  function sessionDirectoryOf(runDirectory) {
18
29
  return join(runDirectory, "session");
19
30
  }
20
- const ROLE_RUN_SESSION_FILENAME = "session.jsonl";
21
31
  function sessionFileIn(sessionDirectory) {
22
32
  return join(sessionDirectory, ROLE_RUN_SESSION_FILENAME);
23
33
  }
@@ -107,6 +117,7 @@ function ensureRoleRunDirectory(ledgerHome, directory) {
107
117
  return ensureRealDirectoryTree(ledgerHome, directory);
108
118
  }
109
119
  function ensureRoleRunPlacement(ledgerHome, placement) {
120
+ ensureRoleRunDirectory(ledgerHome, placement.runDirectory);
110
121
  ensureRoleRunDirectory(ledgerHome, placement.sessionDirectory);
111
122
  }
112
123
  export {
@@ -124,5 +135,6 @@ export {
124
135
  runDirectoryOfSessionFile,
125
136
  sessionDirectoryOf,
126
137
  sessionFileIn,
127
- sessionFileOf
138
+ sessionFileOf,
139
+ subjectDirectoryOfRun
128
140
  };
@@ -12,7 +12,7 @@ import { dirname, join } from "node:path";
12
12
  import { activationBookDirectory, physicalPathIdentity } from "./activation-ledger-topology.js";
13
13
  import { AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE } from "./compliance-transport.js";
14
14
  import { readRoleRunIdentity } from "./public-cli/run-lifecycle.js";
15
- import { roleRunPlacement } from "./role-run-placement.js";
15
+ import { roleRunPlacement, sessionDirectoryOf } from "./role-run-placement.js";
16
16
  import { INVOCATION_PAGE_PATH_FIELDS, PRINCIPAL_SESSION_PATH_FIELDS, RUN_STATE_PAGE_PATH_FIELDS, projectTurnRequestLiveRunDirectory, rewriteAdmittedRoleRunPage, rewritePrincipalSessionPaths, rewriteRunDirectoryPathFields, rewriteRunDirectoryPathFieldsAgainstRewrites, rewriteRunDirectoryPathValue, rewriteRunDirectoryPathValueAgainstRewrites, rewriteSummonsMaterials, } from "./role-run-path-rewrite.js";
17
17
  import { readPageSync, updateSectionSync } from "./run-dossier.js";
18
18
  import { isEnoent, isRecord } from "./unknown-value.js";
@@ -215,7 +215,7 @@ async function rewriteSessionTranscriptBindings(path, rewrites) {
215
215
  * session transcript typed parent bindings. Never walks caller file paths.
216
216
  */
217
217
  async function rewriteNestedMachinePathPages(pagesDirectory, rewrites) {
218
- const sessionRoot = join(pagesDirectory, "session");
218
+ const sessionRoot = sessionDirectoryOf(pagesDirectory);
219
219
  async function walk(directory) {
220
220
  let entries;
221
221
  try {
@@ -19,7 +19,7 @@
19
19
  import { randomUUID } from "node:crypto";
20
20
  import { readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
21
21
  import { basename, join } from "node:path";
22
- import { sessionFileOf } from "./role-run-placement.js";
22
+ import { sessionDirectoryOf, sessionFileOf } from "./role-run-placement.js";
23
23
  import { projectRenderedPagePaths } from "./role-run-path-rewrite.js";
24
24
  import { RUN_CURRENT_FILE, RUN_HISTORY_FILE, RUN_LOG_FILE, RUN_STATE_FILE, OFFICER_POINTER_RECORD_KIND } from "./run-dossier-files.js";
25
25
  import { appendSitianRecord } from "./sitian-appender.js";
@@ -181,9 +181,10 @@ function render(runDirectory, history, state, log, unreadable) {
181
181
  }
182
182
  }
183
183
  // The copy row keeps the absolute path it was written to; a run that was filed under its ticket
184
- // since lives elsewhere. The original is projected from where this run directory is now.
185
- if (typeof original === "string")
186
- original = join(runDirectory, "session", basename(original));
184
+ // since lives elsewhere. The original is projected from where this run's session lands now.
185
+ if (typeof original === "string") {
186
+ original = join(sessionDirectoryOf(runDirectory), basename(original));
187
+ }
187
188
  whole.host = { sessions, ...(original === undefined ? {} : { original }) };
188
189
  whole.submission = latest === undefined ? {} : { latest };
189
190
  if (Object.keys(unreadable).length > 0)
@@ -99,10 +99,3 @@ export function runIdFromRunDirectory(runDirectory) {
99
99
  export function roleFromRunDirectory(runDirectory) {
100
100
  return parseRunLeaf(basename(runDirectory))?.role;
101
101
  }
102
- /**
103
- * #1195 ticket-court blind review: notary source is a countersign run.
104
- * Identity still binds; countersign body is not preloaded into dialogue.
105
- */
106
- export function isTicketCourtCountersignSource(runDirectory) {
107
- return roleFromRunDirectory(runDirectory) === "countersign";
108
- }
@@ -1,5 +1,6 @@
1
- import { dirname, join } from "node:path";
1
+ import { join } from "node:path";
2
2
  import { DEFAULT_ROLE_TURN_HOST, lookupHeadlessHostDescription, lookupHostDescription, } from "./host-descriptions.js";
3
+ import { runDirectoryFromSessionDirectory } from "./role-run-placement.js";
3
4
  import { renderCurrentSync, RUN_STATE_FILE } from "./run-dossier.js";
4
5
  import { readSitianRecords, reportRunRecord } from "./sitian-facade.js";
5
6
  import { errorText, isRecord } from "./unknown-value.js";
@@ -14,7 +15,7 @@ export const HOST_SESSION_ID_RECORD_KIND = "host-session-id";
14
15
  * host/session failure (facts stay in state.jsonl; settlement still renders).
15
16
  */
16
17
  export function createSessionIdentityAuthority(authority, hostName) {
17
- const runDirectoryOf = (principal) => dirname(authority.decode(principal).sessionDirectory);
18
+ const runDirectoryOf = (principal) => runDirectoryFromSessionDirectory(authority.decode(principal).sessionDirectory);
18
19
  return {
19
20
  principalAuthority: authority,
20
21
  resolveSessionFile(principal) {
@@ -1,7 +1,10 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { appendFileSync } from "node:fs";
3
3
  import { basename, dirname, join } from "node:path";
4
- import { parseRunLeaf, sessionDirectoryOf } from "./role-run-placement.js";
4
+ import {
5
+ parseRunLeaf,
6
+ sessionDirectoryOf
7
+ } from "./role-run-placement.js";
5
8
  import { RUN_HISTORY_FILE, RUN_HISTORY_KINDS, RUN_LOG_FILE, RUN_STATE_FILE, RUN_STATE_KINDS } from "./run-dossier-files.js";
6
9
  import { ticketNumberFromSitianSubject } from "./run-ticket-number.js";
7
10
  import { resolveBookKeyFromGit } from "./activation-ledger-git.js";
@@ -33,6 +36,12 @@ function appendSitianRecordBlock(input, block) {
33
36
  function resolveSitianVolumeCategory(kind) {
34
37
  return kind;
35
38
  }
39
+ function runDirectoryOwningSessionParent(sessionParentDir) {
40
+ if (basename(sessionParentDir) !== "session") return void 0;
41
+ const runDirectory = dirname(sessionParentDir);
42
+ if (parseRunLeaf(basename(runDirectory)) === void 0) return void 0;
43
+ return runDirectory;
44
+ }
36
45
  const SITIAN_RECORDS_LEAF = "records.jsonl";
37
46
  function sitianVolumeDirectory(sessionDirectory, category) {
38
47
  return join(sessionDirectory, category);
@@ -68,8 +77,8 @@ function resolveSitianRecordPathInLedger(input, ledgerHome) {
68
77
  throw new Error("Sitian record ownership requires a parent session inside the ledger home");
69
78
  }
70
79
  const sessionParentDir = dirname(input.sessionParent);
71
- const runDirectory = dirname(sessionParentDir);
72
- if (basename(sessionParentDir) === "session" && parseRunLeaf(basename(runDirectory)) !== void 0) {
80
+ const runDirectory = runDirectoryOwningSessionParent(sessionParentDir);
81
+ if (runDirectory !== void 0) {
73
82
  sessionDir = runDirectory;
74
83
  recordFile = join(runDirectory, RUN_HISTORY_KINDS.has(input.kind) ? RUN_HISTORY_FILE : RUN_STATE_KINDS.has(input.kind) ? RUN_STATE_FILE : RUN_LOG_FILE);
75
84
  } else {
@@ -9,6 +9,7 @@ import { failOnInfrastructureFailureDeclaration, infrastructureFailureDiagnostic
9
9
  import { reportTicketFromHostContext } from "./report-ticket-tool.js";
10
10
  import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.js";
11
11
  import { readDeclaredTicketNumber } from "./run-ticket-number.js";
12
+ import { captureWorktreeHead, landTicketProgressForSealedSubmission, } from "./ticket-progress.js";
12
13
  import { isRecord, errorText } from "./unknown-value.js";
13
14
  /**
14
15
  * Admitted run identity for the ledger subject.
@@ -294,6 +295,8 @@ export async function sealAcceptedSubmission(options) {
294
295
  const attemptId = attemptIdentity(options.context, runId);
295
296
  const sessionParent = sessionParentFromHostContext(options.context);
296
297
  const home = homeFromHostContext(options.context, options.home);
298
+ // #1199: same seal-time HEAD + progress landing as the terminating-tool wrap.
299
+ const sealHead = captureWorktreeHead(options.context.cwd);
297
300
  sitianReport({
298
301
  level: "event",
299
302
  kind: "sealed",
@@ -310,6 +313,12 @@ export async function sealAcceptedSubmission(options) {
310
313
  ...(home !== undefined ? { home } : {}),
311
314
  ...(sessionParent === undefined ? {} : { sessionParent }),
312
315
  });
316
+ landTicketProgressForSealedSubmission({
317
+ context: options.context,
318
+ role: options.role,
319
+ accepted: options.accepted,
320
+ sealHead,
321
+ });
313
322
  }
314
323
  /**
315
324
  * Submission ledger host — record only (#836).
@@ -457,6 +466,9 @@ export function createSubmissionLedgerHost(host, outputTools, failInfrastructure
457
466
  kind: "accepted",
458
467
  accepted: params,
459
468
  };
469
+ // #1199: seal-time HEAD + ticket progress before projectClosure.
470
+ // One seal → one row; post-turn history guessing is not used.
471
+ const sealHead = captureWorktreeHead(context.cwd);
460
472
  append({
461
473
  type: "sealed",
462
474
  attemptId,
@@ -464,6 +476,18 @@ export function createSubmissionLedgerHost(host, outputTools, failInfrastructure
464
476
  role,
465
477
  accepted: params,
466
478
  });
479
+ const rawLens = typeof host.getFlag === "function"
480
+ ? host.getFlag("ak-review-lens")
481
+ : undefined;
482
+ landTicketProgressForSealedSubmission({
483
+ context,
484
+ role,
485
+ accepted: params,
486
+ sealHead,
487
+ ...(rawLens === "completeness" || rawLens === "correctness"
488
+ ? { lens: rawLens }
489
+ : {}),
490
+ });
467
491
  await projectClosure(closed, context);
468
492
  return result;
469
493
  },