@akagilnc/pi-workflow-roles 0.1.5013 → 0.1.5037

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.
@@ -1127,11 +1127,10 @@ async function runHasFormedSessionPrincipal(runDirectory: string): Promise<boole
1127
1127
  /**
1128
1128
  * Locate the latest retained run for one seat under a book (#637 / #747).
1129
1129
  * Same walk surface as findRunDirectoryById (listBookRunDirectories). Match by
1130
- * parent run path (officer / gate seats, #747 / #987). A typed ticket number
1131
- * does not select a run. runId is UUIDv7 — lexicographic max is latest among
1132
- * runs that formed a session principal. No parallel index. Public ticket-number
1133
- * selection of a prior run was deleted (#987 Result 7); callers use explicit
1134
- * `ak-role resume <runId>`.
1130
+ * parent run path (officer / gate seats, #747 / #987), narrowed to a supplied
1131
+ * typed ticket for same-ticket gate re-summons. runId is UUIDv7 — lexicographic
1132
+ * max is latest among runs that formed a session principal. No parallel index;
1133
+ * callers can use explicit `ak-role resume <runId>`.
1135
1134
  * Only a truly missing book directory means no history; damage/permission errors propagate.
1136
1135
  */
1137
1136
  export async function findLatestRunIdForSeatTicket(input: {
@@ -1148,12 +1147,8 @@ export async function findLatestRunIdForSeatTicket(input: {
1148
1147
  const bookDir = activationBookDirectory(ledgerHome, input.bookKey);
1149
1148
  let runDirectories: string[];
1150
1149
  try {
1151
- // A typed ticket narrows the existing walk surface to that ticket's runs;
1152
- // same-parent matching below still selects the actual prior leg.
1153
1150
  runDirectories = await listBookRunDirectories(
1154
- input.ticketNumber === undefined
1155
- ? bookDir
1156
- : join(bookDir, String(input.ticketNumber)),
1151
+ input.ticketNumber === undefined ? bookDir : join(bookDir, String(input.ticketNumber)),
1157
1152
  );
1158
1153
  } catch (error) {
1159
1154
  if (errorCodeOf(error) === "ENOENT") return undefined;
@@ -1167,7 +1162,9 @@ export async function findLatestRunIdForSeatTicket(input: {
1167
1162
  const runId = entry.slice(0, entry.length - suffix.length);
1168
1163
  if (runId.length === 0) continue;
1169
1164
  const parentPath = await readRunParentPath(runDirectory);
1170
- if (parentPath !== input.parentRunPath) continue;
1165
+ // The parent's ticket can change after this child was admitted. Within one
1166
+ // book the run leaf is its stable identity; directory placement is not.
1167
+ if (parentPath !== input.parentRunPath && basename(parentPath ?? "") !== basename(input.parentRunPath)) continue;
1171
1168
  // Durable fact: never resume-select a provisional that never formed principal.
1172
1169
  if (!(await runHasFormedSessionPrincipal(runDirectory))) continue;
1173
1170
  if (best === undefined || runId > best) best = runId;
@@ -1328,6 +1325,7 @@ async function loadResumableRunRecord(
1328
1325
  home: string,
1329
1326
  runId: string,
1330
1327
  authority: DurablePrincipalAuthority,
1328
+ allowUnformedAdmitted = false,
1331
1329
  ): Promise<{
1332
1330
  readonly run: RoleRunRecord;
1333
1331
  readonly principal: DurablePrincipal;
@@ -1350,7 +1348,7 @@ async function loadResumableRunRecord(
1350
1348
  throw new CliUsageError(`unknown role run id: ${runId}`);
1351
1349
  }
1352
1350
  const { run, principal } = materialized;
1353
- if (!(await isDurablePrincipalAvailable(principal, authority))) {
1351
+ if (!(allowUnformedAdmitted && run.state === "admitted") && !(await isDurablePrincipalAvailable(principal, authority))) {
1354
1352
  throw new CliUsageError(
1355
1353
  `role run Pi session principal is unavailable: ${runId}`,
1356
1354
  );
@@ -1757,8 +1755,9 @@ export async function loadResumablePublicRole(
1757
1755
  home: string,
1758
1756
  runId: string,
1759
1757
  authority: DurablePrincipalAuthority,
1758
+ allowUnformedAdmitted = false,
1760
1759
  ): Promise<LoadedResumablePublicRole> {
1761
- const loaded = await loadResumableRunRecord(home, runId, authority);
1760
+ const loaded = await loadResumableRunRecord(home, runId, authority, allowUnformedAdmitted);
1762
1761
  return seatLoadedResult(loaded, admitResumedRole(loaded));
1763
1762
  }
1764
1763
 
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * Shared same-parent resume seam for gate / officer seats (#635 / #637 / #747 / #987).
3
3
  *
4
- * Lookup key is parent run path only (#747 / #987 Result 7). Public seats no
5
- * longer select a prior run by ticket number — callers use explicit
6
- * `ak-role resume <runId>`. Officer and gate countersign same-parent re-summons
7
- * keep this seam. Lookup/resume failures propagate (失败诚实) — never wash into
4
+ * Lookup uses the parent run path; a typed ticket narrows same-ticket gate
5
+ * re-summons, never creates an issue identity. Callers can also use explicit
6
+ * `ak-role resume <runId>`. Lookup/resume failures propagate (失败诚实) — never wash into
8
7
  * a fresh mint. Returns undefined when the caller declared an explicit fresh
9
8
  * summons (`ak-role new`) or when no prior run exists; both mint new.
10
9
  * freshSummons is required so no seat can drift back into its own skip branch.
@@ -18,7 +17,7 @@ import {
18
17
 
19
18
  /**
20
19
  * Sole same-seat → resume decision by parent run path (#637 / #724 / #747 / #987).
21
- * A typed ticket number is a bind key, not a resume key. When a prior run is
20
+ * A supplied typed ticket only narrows the prior-run search. When a prior run is
22
21
  * found, resume carries this summons' materials. Lookup/resume failures
23
22
  * propagate (失败诚实) — never wash into a fresh mint. Returns undefined when
24
23
  * the caller declared an explicit fresh summons (`ak-role new`), the parent
@@ -45,9 +44,7 @@ export async function tryResumeSameTicketSeatRun<T>(input: {
45
44
  bookKey: resolveBookKeyFromGit(input.projectRoot),
46
45
  role: input.role,
47
46
  parentRunPath: input.parentRunPath,
48
- ...(input.ticketNumber === undefined
49
- ? {}
50
- : { ticketNumber: input.ticketNumber }),
47
+ ...(input.ticketNumber === undefined ? {} : { ticketNumber: input.ticketNumber }),
51
48
  });
52
49
  if (previousRunId === undefined) return undefined;
53
50
  return await input.resume(previousRunId, input.summons);
@@ -535,7 +535,16 @@ export async function summonPublicRole(
535
535
  }
536
536
 
537
537
  const stderr = captured?.stderrText();
538
- const runDirectory = result.admitted?.runDirectory;
538
+ // A parked officer can return its court diarist's escalation verbatim. The
539
+ // terminal's independent run, not the parked officer, is the gate pointer.
540
+ const runDirectory = result.terminal !== undefined
541
+ && result.terminal.roleOutcome.role !== options.role
542
+ && typeof result.terminal.runId === "string"
543
+ ? await (await import("./public-cli/run-lifecycle.ts")).findRunDirectoryById(
544
+ home,
545
+ result.terminal.runId,
546
+ )
547
+ : result.admitted?.runDirectory;
539
548
  return {
540
549
  exitCode: result.exitCode,
541
550
  ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
@@ -851,8 +860,12 @@ export async function summonGateOfficer(options: {
851
860
  `countersign gate summon requires parent runId from sourceRunDirectory: ${options.sourceRunDirectory}`,
852
861
  );
853
862
  }
854
- const { readBoardTicketNumber } = await import("./run-ticket-number.ts");
863
+ const { isSafePositiveTicketNumber, readBoardTicketNumber } = await import("./run-ticket-number.ts");
855
864
  const parentTicket = await readBoardTicketNumber(options.sourceRunDirectory);
865
+ const submittedTicket = options.submission !== null && typeof options.submission === "object" && !Array.isArray(options.submission)
866
+ ? (options.submission as { ticketNumber?: unknown }).ticketNumber
867
+ : undefined;
868
+ const courtTicket = isSafePositiveTicketNumber(submittedTicket) ? submittedTicket : parentTicket;
856
869
  // Argv instruction = reask or parent payload bytes only (never a fabricated line).
857
870
  const instruction = options.reask ?? gateReviewInstruction ?? "";
858
871
  return summonPublicRole({
@@ -861,7 +874,7 @@ export async function summonGateOfficer(options: {
861
874
  ...common,
862
875
  correlationId,
863
876
  parentRunPath: options.sourceRunDirectory,
864
- ...(parentTicket === undefined ? {} : { boundTicketNumber: parentTicket }),
877
+ ...(courtTicket === undefined ? {} : { boundTicketNumber: courtTicket }),
865
878
  });
866
879
  }
867
880
  // Inspector: argv stays pure 卷宗指针 (#747 parentRunPath lookup key).