@akagilnc/pi-workflow-roles 0.1.5941 → 0.1.5946

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.
@@ -14233,24 +14233,48 @@ var init_post_admission = __esm({
14233
14233
  });
14234
14234
 
14235
14235
  // src/public-cli/seat-ticket-binding.ts
14236
+ import { join as join22 } from "node:path";
14237
+ function resumedSessionIdMissing(result, runId) {
14238
+ if (typeof result !== "object" || result === null) return false;
14239
+ const terminal = result.terminal;
14240
+ if (typeof terminal !== "object" || terminal === null) return false;
14241
+ if (terminal.runId !== runId) return false;
14242
+ const outcome = terminal.roleOutcome;
14243
+ if (typeof outcome !== "object" || outcome === null) return false;
14244
+ if (outcome.kind !== "failure") return false;
14245
+ const facts = outcome.decisiveFacts;
14246
+ if (typeof facts !== "object" || facts === null) return false;
14247
+ return facts.errorCode === SESSION_ID_MISSING;
14248
+ }
14249
+ async function writerLockBlocksFreshSummons(runDirectory) {
14250
+ const autopsy = await autopsyWriterLock(join22(runDirectory, WRITER_LOCK_FILE));
14251
+ return autopsy.verdict === "alive" || autopsy.verdict === "unknown";
14252
+ }
14236
14253
  async function tryResumeSameTicketSeatRun(input) {
14237
14254
  if (input.freshSummons === true) return void 0;
14238
14255
  if (input.parentRunPath.trim() === "") return void 0;
14256
+ const bookKey = resolveBookKeyFromGit(input.projectRoot);
14239
14257
  const previous = await findLatestRunForSeatTicket({
14240
14258
  home: input.home,
14241
- bookKey: resolveBookKeyFromGit(input.projectRoot),
14259
+ bookKey,
14242
14260
  role: input.role,
14243
14261
  parentRunPath: input.parentRunPath,
14244
14262
  ...input.ticketNumber === void 0 ? {} : { ticketNumber: input.ticketNumber }
14245
14263
  });
14246
14264
  if (previous === void 0) return void 0;
14247
- return await input.resume(previous.runId, input.summons, previous.runDirectory);
14265
+ const resumed = await input.resume(previous.runId, input.summons, previous.runDirectory);
14266
+ if (!resumedSessionIdMissing(resumed, previous.runId)) return resumed;
14267
+ const live = await resolveLiveRunDirectoryPath(previous.runDirectory, input.home, bookKey);
14268
+ if (live !== void 0 && await writerLockBlocksFreshSummons(live)) return resumed;
14269
+ return void 0;
14248
14270
  }
14271
+ var SESSION_ID_MISSING;
14249
14272
  var init_seat_ticket_binding = __esm({
14250
14273
  "src/public-cli/seat-ticket-binding.ts"() {
14251
14274
  "use strict";
14252
14275
  init_activation_ledger_git();
14253
14276
  init_run_lifecycle();
14277
+ SESSION_ID_MISSING = "session-id-missing";
14254
14278
  }
14255
14279
  });
14256
14280
 
@@ -15909,10 +15933,10 @@ var init_analyst_book_key = __esm({
15909
15933
  // src/atomic-write.ts
15910
15934
  import { randomUUID as randomUUID7 } from "node:crypto";
15911
15935
  import { rename as rename2, rm, writeFile as writeFile3 } from "node:fs/promises";
15912
- import { dirname as dirname14, join as join22 } from "node:path";
15936
+ import { dirname as dirname14, join as join23 } from "node:path";
15913
15937
  async function writeFileAtomically(destination, contents, options) {
15914
15938
  const parent = dirname14(destination);
15915
- const temporary = join22(parent, `.atomic-write-${randomUUID7()}.tmp`);
15939
+ const temporary = join23(parent, `.atomic-write-${randomUUID7()}.tmp`);
15916
15940
  try {
15917
15941
  await writeFile3(temporary, contents, options);
15918
15942
  await rename2(temporary, destination);
@@ -15929,12 +15953,12 @@ var init_atomic_write = __esm({
15929
15953
 
15930
15954
  // src/analyst-index.ts
15931
15955
  import { open, readFile as readFile10, unlink } from "node:fs/promises";
15932
- import { dirname as dirname15, join as join23 } from "node:path";
15956
+ import { dirname as dirname15, join as join24 } from "node:path";
15933
15957
  import { setTimeout as sleep } from "node:timers/promises";
15934
15958
  async function withAnalystLibraryIndexLock(ledgerHome, fn) {
15935
15959
  const indexPath = analystLibraryIndexPath(ledgerHome);
15936
15960
  ensureRealDirectoryTree(ledgerHome, dirname15(indexPath));
15937
- const lockPath = join23(dirname15(indexPath), LIBRARY_INDEX_LOCK_NAME);
15961
+ const lockPath = join24(dirname15(indexPath), LIBRARY_INDEX_LOCK_NAME);
15938
15962
  assertLedgerFileInsideHome(lockPath, ledgerHome);
15939
15963
  const startedAt = Date.now();
15940
15964
  while (true) {
@@ -15961,7 +15985,7 @@ async function withAnalystLibraryIndexLock(ledgerHome, fn) {
15961
15985
  }
15962
15986
  }
15963
15987
  function analystLibraryIndexPath(ledgerHome) {
15964
- return join23(ledgerHome, "analyst", "library-index.json");
15988
+ return join24(ledgerHome, "analyst", "library-index.json");
15965
15989
  }
15966
15990
  function rowFromIssueMetricsPage(page) {
15967
15991
  return {
@@ -16255,7 +16279,7 @@ var init_analyst_cohort = __esm({
16255
16279
  });
16256
16280
 
16257
16281
  // src/archivist-record-pointer.ts
16258
- import { basename as basename11, join as join24 } from "node:path";
16282
+ import { basename as basename11, join as join25 } from "node:path";
16259
16283
  async function resolveOfficerPointer(raw, parentRunDirectory) {
16260
16284
  const where = `${parentRunDirectory} officers`;
16261
16285
  if (!isRecord(raw) || raw.kind !== DIRECT_OFFICER_RUN_POINTER_KIND || raw.version !== 1) {
@@ -16288,7 +16312,7 @@ async function resolveOfficerPointer(raw, parentRunDirectory) {
16288
16312
  };
16289
16313
  }
16290
16314
  async function readBookedOfficerPointers(parentRunDirectory) {
16291
- const { records, diagnostics } = await readSitianRecords(join24(parentRunDirectory, RUN_HISTORY_FILE));
16315
+ const { records, diagnostics } = await readSitianRecords(join25(parentRunDirectory, RUN_HISTORY_FILE));
16292
16316
  if (diagnostics.length > 0) {
16293
16317
  throw new Error(
16294
16318
  `${RUN_HISTORY_FILE} has ${diagnostics.length} malformed row(s); officer pointers unreadable: ${parentRunDirectory}`
@@ -16571,7 +16595,7 @@ var init_analyst_gate_cycles_read = __esm({
16571
16595
 
16572
16596
  // src/analyst-ledger.ts
16573
16597
  import { readdir as readdir3 } from "node:fs/promises";
16574
- import { basename as basename12, join as join25 } from "node:path";
16598
+ import { basename as basename12, join as join26 } from "node:path";
16575
16599
  async function readExistingRunLifecycleState(runDirectory) {
16576
16600
  return (await readRoleRunIdentity(runDirectory))?.state;
16577
16601
  }
@@ -16591,7 +16615,7 @@ function ghostLeaseCheckFromAutopsy(autopsy) {
16591
16615
  }
16592
16616
  }
16593
16617
  async function classifyGhostCandidate(input) {
16594
- const autopsy = await autopsyWriterLock(join25(input.runDirectory, "writer.lock"));
16618
+ const autopsy = await autopsyWriterLock(join26(input.runDirectory, "writer.lock"));
16595
16619
  const leaseCheck = ghostLeaseCheckFromAutopsy(autopsy);
16596
16620
  if (leaseCheck === void 0) return { kind: "live" };
16597
16621
  return {
@@ -16881,7 +16905,7 @@ async function classifyScopedRun(input) {
16881
16905
  async function scanAnalystIssueRuns(input) {
16882
16906
  const ledgerHome = resolveActivationLedgerHome(input.home);
16883
16907
  const scopeTicketNumber = input.ticketNumber;
16884
- const booksRoot = join25(ledgerHome, "books");
16908
+ const booksRoot = join26(ledgerHome, "books");
16885
16909
  let wholeBook = false;
16886
16910
  let scopeRootIdentity;
16887
16911
  let bookNames;
@@ -16914,7 +16938,7 @@ async function scanAnalystIssueRuns(input) {
16914
16938
  const ghostLegs = [];
16915
16939
  const scopeConflicts = [];
16916
16940
  for (const book of bookNames) {
16917
- const bookDir = join25(booksRoot, book);
16941
+ const bookDir = join26(booksRoot, book);
16918
16942
  let runDirectories;
16919
16943
  try {
16920
16944
  runDirectories = await listBookRunDirectories(bookDir);
@@ -17660,7 +17684,7 @@ var init_analyst_metric_family = __esm({
17660
17684
  });
17661
17685
 
17662
17686
  // src/analyst-page.ts
17663
- import { dirname as dirname16, join as join26 } from "node:path";
17687
+ import { dirname as dirname16, join as join27 } from "node:path";
17664
17688
  function analystIssuePageKey(address) {
17665
17689
  const parts = ["book", address.bookKey];
17666
17690
  if (address.issueNumber !== void 0) {
@@ -17671,7 +17695,7 @@ function analystIssuePageKey(address) {
17671
17695
  return sha256Hex(parts.join("\0")).slice(0, 32);
17672
17696
  }
17673
17697
  function analystIssuePagePath(ledgerHome, address) {
17674
- return join26(ledgerHome, "analyst", "issues", `${analystIssuePageKey(address)}.json`);
17698
+ return join27(ledgerHome, "analyst", "issues", `${analystIssuePageKey(address)}.json`);
17675
17699
  }
17676
17700
  function analystIssuePageAddressFromPage(page) {
17677
17701
  return {
@@ -19235,7 +19259,7 @@ __export(public_role_summons_exports, {
19235
19259
  import { execFile as execFile3 } from "node:child_process";
19236
19260
  import { existsSync as existsSync12 } from "node:fs";
19237
19261
  import { realpath as realpath6 } from "node:fs/promises";
19238
- import { join as join27 } from "node:path";
19262
+ import { join as join28 } from "node:path";
19239
19263
  import { promisify as promisify3 } from "node:util";
19240
19264
  function createCapturingIo() {
19241
19265
  const chunks = [];
@@ -19259,7 +19283,7 @@ function parentDir(path) {
19259
19283
  function walkPackageRoot(start) {
19260
19284
  let dir = start;
19261
19285
  for (let i = 0; i < 12; i += 1) {
19262
- if (existsSync12(join27(dir, "package.json")) && existsSync12(join27(dir, "souls"))) {
19286
+ if (existsSync12(join28(dir, "package.json")) && existsSync12(join28(dir, "souls"))) {
19263
19287
  return dir;
19264
19288
  }
19265
19289
  const parent = parentDir(dir);
@@ -19808,7 +19832,7 @@ var init_compliance_transport = __esm({
19808
19832
  });
19809
19833
 
19810
19834
  // src/role-run-relocation.ts
19811
- import { dirname as dirname17, join as join28 } from "node:path";
19835
+ import { dirname as dirname17, join as join29 } from "node:path";
19812
19836
  async function retainedRunPathsMatch(recordedPath, currentPath, currentRunDirectory) {
19813
19837
  if (recordedPath === currentPath) return true;
19814
19838
  if (typeof recordedPath !== "string") return false;
@@ -19854,6 +19878,7 @@ var run_lifecycle_exports = {};
19854
19878
  __export(run_lifecycle_exports, {
19855
19879
  AUTO_RESUME_LIMIT: () => AUTO_RESUME_LIMIT,
19856
19880
  RunWriterLeaseHeldError: () => RunWriterLeaseHeldError,
19881
+ WRITER_LOCK_FILE: () => WRITER_LOCK_FILE,
19857
19882
  acquireRunWriterLease: () => acquireRunWriterLease,
19858
19883
  autopsyWriterLock: () => autopsyWriterLock,
19859
19884
  clearCurrentCourt: () => clearCurrentCourt,
@@ -19876,7 +19901,7 @@ __export(run_lifecycle_exports, {
19876
19901
  });
19877
19902
  import { existsSync as existsSync13 } from "node:fs";
19878
19903
  import { chmod, lstat as lstat3, open as open2, readdir as readdir4, readFile as readFile14, unlink as unlink2 } from "node:fs/promises";
19879
- import { basename as basename13, join as join29 } from "node:path";
19904
+ import { basename as basename13, join as join30 } from "node:path";
19880
19905
  async function writeRoleRunState(runDirectory, record) {
19881
19906
  const payload = { ...record, runDirectory };
19882
19907
  writeSectionSync(runDirectory, "runState", { ...payload });
@@ -20171,7 +20196,7 @@ async function createWriterLease(lockPath, runDirectory, reportCleanupFailure) {
20171
20196
  },
20172
20197
  relocate(nextRunDirectory) {
20173
20198
  currentRunDirectory = nextRunDirectory;
20174
- currentLockPath = join29(nextRunDirectory, WRITER_LOCK_FILE);
20199
+ currentLockPath = join30(nextRunDirectory, WRITER_LOCK_FILE);
20175
20200
  },
20176
20201
  async release() {
20177
20202
  if (released) return;
@@ -20210,10 +20235,10 @@ async function acquireRunWriterLease(runDirectory, onCleanupFailure) {
20210
20235
  };
20211
20236
  const reportReadFailure = (error) => {
20212
20237
  reportDiagnostic(
20213
- `writer lease lock read failed (holder liveness unverifiable; lock left in place) at ${join29(runDirectory, WRITER_LOCK_FILE)}: ${describeErrorIdentity(error)}`
20238
+ `writer lease lock read failed (holder liveness unverifiable; lock left in place) at ${join30(runDirectory, WRITER_LOCK_FILE)}: ${describeErrorIdentity(error)}`
20214
20239
  );
20215
20240
  };
20216
- const lockPath = join29(runDirectory, WRITER_LOCK_FILE);
20241
+ const lockPath = join30(runDirectory, WRITER_LOCK_FILE);
20217
20242
  let lastAutopsy = { verdict: "absent" };
20218
20243
  let lastReclaimFailure;
20219
20244
  for (let reclaimsLeft = WRITER_LEASE_RECLAIM_ROUNDS; ; reclaimsLeft -= 1) {
@@ -20279,7 +20304,7 @@ async function findRunDirectoryById(home, runId, onlyBookKey, onlyRole, knownRun
20279
20304
  }
20280
20305
  if (runId.trim() === "") return void 0;
20281
20306
  const ledgerHome = resolveActivationLedgerHome(home);
20282
- const booksRoot = join29(ledgerHome, "books");
20307
+ const booksRoot = join30(ledgerHome, "books");
20283
20308
  let bookKeys;
20284
20309
  try {
20285
20310
  bookKeys = await readdir4(booksRoot);
@@ -20329,7 +20354,7 @@ async function findLatestRunForSeatTicket(input) {
20329
20354
  let runDirectories;
20330
20355
  try {
20331
20356
  runDirectories = await listBookRunDirectories(
20332
- input.ticketNumber === void 0 ? bookDir : join29(bookDir, String(input.ticketNumber))
20357
+ input.ticketNumber === void 0 ? bookDir : join30(bookDir, String(input.ticketNumber))
20333
20358
  );
20334
20359
  } catch (error) {
20335
20360
  if (errorCodeOf(error) === "ENOENT") return void 0;
@@ -21068,7 +21093,7 @@ init_submission_ledger();
21068
21093
  init_filed_submission();
21069
21094
  init_report_ticket_tool();
21070
21095
  import { writeSync as writeSync2 } from "node:fs";
21071
- import { join as join30 } from "node:path";
21096
+ import { join as join31 } from "node:path";
21072
21097
  import { Value as Value2 } from "typebox/value";
21073
21098
 
21074
21099
  // src/activation-trace.ts
@@ -22865,7 +22890,7 @@ function readRoleRunCoordinates(ctx, label) {
22865
22890
  if (runDirectory === void 0) throw new Error(`${label} requires AK_ROLE_RUN_DIR`);
22866
22891
  const admitted = readPageSync(runDirectory, "admitted");
22867
22892
  if (typeof admitted?.projectRoot !== "string" || admitted.projectRoot.trim() === "") {
22868
- throw new Error(`${label} state.jsonl admitted-request row missing projectRoot (${join30(runDirectory, RUN_STATE_FILE)})`);
22893
+ throw new Error(`${label} state.jsonl admitted-request row missing projectRoot (${join31(runDirectory, RUN_STATE_FILE)})`);
22869
22894
  }
22870
22895
  return {
22871
22896
  runDirectory,
@@ -24632,12 +24657,12 @@ function createAcpRoleTurnHost(config) {
24632
24657
  // src/acp-host/seat-profile-soul.ts
24633
24658
  import { constants as constants3 } from "node:fs";
24634
24659
  import { access as access3, copyFile, lstat as lstat4, mkdir as mkdir4, readlink, symlink as symlink2, unlink as unlink3 } from "node:fs/promises";
24635
- import { dirname as dirname19, join as join31, relative as relative4, resolve as resolve16 } from "node:path";
24660
+ import { dirname as dirname19, join as join32, relative as relative4, resolve as resolve16 } from "node:path";
24636
24661
  function seatProfileName(spec, role) {
24637
24662
  return `${spec.namePrefix}${role}`;
24638
24663
  }
24639
24664
  function packageRoleSoulPath(packageRoot, role) {
24640
- return join31(packageRoot, "souls", `${role}.md`);
24665
+ return join32(packageRoot, "souls", `${role}.md`);
24641
24666
  }
24642
24667
  async function pathExists2(path) {
24643
24668
  try {
@@ -24654,16 +24679,16 @@ async function ensureSeatProfileSoul(options) {
24654
24679
  if (!await pathExists2(soulTarget)) {
24655
24680
  throw new Error(`packaged role soul missing: ${soulTarget}`);
24656
24681
  }
24657
- const profilesRoot = join31(operatorHome, ...spec.profilesRootFromHome);
24658
- const profileDir = join31(profilesRoot, profileName);
24682
+ const profilesRoot = join32(operatorHome, ...spec.profilesRootFromHome);
24683
+ const profileDir = join32(profilesRoot, profileName);
24659
24684
  const hostRoot = dirname19(profilesRoot);
24660
- const soulPath = join31(profileDir, spec.soulFileName);
24685
+ const soulPath = join32(profileDir, spec.soulFileName);
24661
24686
  if (!await pathExists2(profileDir)) {
24662
24687
  await mkdir4(profileDir, { recursive: true });
24663
24688
  for (const name of ["auth.json", ".env", "config.yaml"]) {
24664
- const source = join31(hostRoot, name);
24689
+ const source = join32(hostRoot, name);
24665
24690
  if (!await pathExists2(source)) continue;
24666
- await copyFile(source, join31(profileDir, name));
24691
+ await copyFile(source, join32(profileDir, name));
24667
24692
  }
24668
24693
  } else {
24669
24694
  await mkdir4(profileDir, { recursive: true });
@@ -9390,6 +9390,7 @@ var run_lifecycle_exports = {};
9390
9390
  __export(run_lifecycle_exports, {
9391
9391
  AUTO_RESUME_LIMIT: () => AUTO_RESUME_LIMIT,
9392
9392
  RunWriterLeaseHeldError: () => RunWriterLeaseHeldError,
9393
+ WRITER_LOCK_FILE: () => WRITER_LOCK_FILE,
9393
9394
  acquireRunWriterLease: () => acquireRunWriterLease,
9394
9395
  autopsyWriterLock: () => autopsyWriterLock,
9395
9396
  clearCurrentCourt: () => clearCurrentCourt,
@@ -15580,24 +15581,48 @@ var init_post_admission = __esm({
15580
15581
  });
15581
15582
 
15582
15583
  // src/public-cli/seat-ticket-binding.ts
15584
+ import { join as join24 } from "node:path";
15585
+ function resumedSessionIdMissing(result, runId) {
15586
+ if (typeof result !== "object" || result === null) return false;
15587
+ const terminal = result.terminal;
15588
+ if (typeof terminal !== "object" || terminal === null) return false;
15589
+ if (terminal.runId !== runId) return false;
15590
+ const outcome = terminal.roleOutcome;
15591
+ if (typeof outcome !== "object" || outcome === null) return false;
15592
+ if (outcome.kind !== "failure") return false;
15593
+ const facts = outcome.decisiveFacts;
15594
+ if (typeof facts !== "object" || facts === null) return false;
15595
+ return facts.errorCode === SESSION_ID_MISSING;
15596
+ }
15597
+ async function writerLockBlocksFreshSummons(runDirectory) {
15598
+ const autopsy = await autopsyWriterLock(join24(runDirectory, WRITER_LOCK_FILE));
15599
+ return autopsy.verdict === "alive" || autopsy.verdict === "unknown";
15600
+ }
15583
15601
  async function tryResumeSameTicketSeatRun(input) {
15584
15602
  if (input.freshSummons === true) return void 0;
15585
15603
  if (input.parentRunPath.trim() === "") return void 0;
15604
+ const bookKey = resolveBookKeyFromGit(input.projectRoot);
15586
15605
  const previous = await findLatestRunForSeatTicket({
15587
15606
  home: input.home,
15588
- bookKey: resolveBookKeyFromGit(input.projectRoot),
15607
+ bookKey,
15589
15608
  role: input.role,
15590
15609
  parentRunPath: input.parentRunPath,
15591
15610
  ...input.ticketNumber === void 0 ? {} : { ticketNumber: input.ticketNumber }
15592
15611
  });
15593
15612
  if (previous === void 0) return void 0;
15594
- return await input.resume(previous.runId, input.summons, previous.runDirectory);
15613
+ const resumed = await input.resume(previous.runId, input.summons, previous.runDirectory);
15614
+ if (!resumedSessionIdMissing(resumed, previous.runId)) return resumed;
15615
+ const live = await resolveLiveRunDirectoryPath(previous.runDirectory, input.home, bookKey);
15616
+ if (live !== void 0 && await writerLockBlocksFreshSummons(live)) return resumed;
15617
+ return void 0;
15595
15618
  }
15619
+ var SESSION_ID_MISSING;
15596
15620
  var init_seat_ticket_binding = __esm({
15597
15621
  "src/public-cli/seat-ticket-binding.ts"() {
15598
15622
  "use strict";
15599
15623
  init_activation_ledger_git();
15600
15624
  init_run_lifecycle();
15625
+ SESSION_ID_MISSING = "session-id-missing";
15601
15626
  }
15602
15627
  });
15603
15628
 
@@ -17256,10 +17281,10 @@ var init_analyst_book_key = __esm({
17256
17281
  // src/atomic-write.ts
17257
17282
  import { randomUUID as randomUUID7 } from "node:crypto";
17258
17283
  import { rename as rename2, rm, writeFile as writeFile4 } from "node:fs/promises";
17259
- import { dirname as dirname15, join as join24 } from "node:path";
17284
+ import { dirname as dirname15, join as join25 } from "node:path";
17260
17285
  async function writeFileAtomically(destination, contents, options) {
17261
17286
  const parent = dirname15(destination);
17262
- const temporary = join24(parent, `.atomic-write-${randomUUID7()}.tmp`);
17287
+ const temporary = join25(parent, `.atomic-write-${randomUUID7()}.tmp`);
17263
17288
  try {
17264
17289
  await writeFile4(temporary, contents, options);
17265
17290
  await rename2(temporary, destination);
@@ -17276,12 +17301,12 @@ var init_atomic_write = __esm({
17276
17301
 
17277
17302
  // src/analyst-index.ts
17278
17303
  import { open as open2, readFile as readFile11, unlink as unlink2 } from "node:fs/promises";
17279
- import { dirname as dirname16, join as join25 } from "node:path";
17304
+ import { dirname as dirname16, join as join26 } from "node:path";
17280
17305
  import { setTimeout as sleep } from "node:timers/promises";
17281
17306
  async function withAnalystLibraryIndexLock(ledgerHome, fn) {
17282
17307
  const indexPath = analystLibraryIndexPath(ledgerHome);
17283
17308
  ensureRealDirectoryTree(ledgerHome, dirname16(indexPath));
17284
- const lockPath = join25(dirname16(indexPath), LIBRARY_INDEX_LOCK_NAME);
17309
+ const lockPath = join26(dirname16(indexPath), LIBRARY_INDEX_LOCK_NAME);
17285
17310
  assertLedgerFileInsideHome(lockPath, ledgerHome);
17286
17311
  const startedAt = Date.now();
17287
17312
  while (true) {
@@ -17308,7 +17333,7 @@ async function withAnalystLibraryIndexLock(ledgerHome, fn) {
17308
17333
  }
17309
17334
  }
17310
17335
  function analystLibraryIndexPath(ledgerHome) {
17311
- return join25(ledgerHome, "analyst", "library-index.json");
17336
+ return join26(ledgerHome, "analyst", "library-index.json");
17312
17337
  }
17313
17338
  function rowFromIssueMetricsPage(page) {
17314
17339
  return {
@@ -17602,7 +17627,7 @@ var init_analyst_cohort = __esm({
17602
17627
  });
17603
17628
 
17604
17629
  // src/archivist-record-pointer.ts
17605
- import { basename as basename12, join as join26 } from "node:path";
17630
+ import { basename as basename12, join as join27 } from "node:path";
17606
17631
  async function resolveOfficerPointer(raw, parentRunDirectory) {
17607
17632
  const where = `${parentRunDirectory} officers`;
17608
17633
  if (!isRecord(raw) || raw.kind !== DIRECT_OFFICER_RUN_POINTER_KIND || raw.version !== 1) {
@@ -17635,7 +17660,7 @@ async function resolveOfficerPointer(raw, parentRunDirectory) {
17635
17660
  };
17636
17661
  }
17637
17662
  async function readBookedOfficerPointers(parentRunDirectory) {
17638
- const { records, diagnostics } = await readSitianRecords(join26(parentRunDirectory, RUN_HISTORY_FILE));
17663
+ const { records, diagnostics } = await readSitianRecords(join27(parentRunDirectory, RUN_HISTORY_FILE));
17639
17664
  if (diagnostics.length > 0) {
17640
17665
  throw new Error(
17641
17666
  `${RUN_HISTORY_FILE} has ${diagnostics.length} malformed row(s); officer pointers unreadable: ${parentRunDirectory}`
@@ -17918,7 +17943,7 @@ var init_analyst_gate_cycles_read = __esm({
17918
17943
 
17919
17944
  // src/analyst-ledger.ts
17920
17945
  import { readdir as readdir4 } from "node:fs/promises";
17921
- import { basename as basename13, join as join27 } from "node:path";
17946
+ import { basename as basename13, join as join28 } from "node:path";
17922
17947
  async function readExistingRunLifecycleState(runDirectory) {
17923
17948
  return (await readRoleRunIdentity(runDirectory))?.state;
17924
17949
  }
@@ -17938,7 +17963,7 @@ function ghostLeaseCheckFromAutopsy(autopsy) {
17938
17963
  }
17939
17964
  }
17940
17965
  async function classifyGhostCandidate(input) {
17941
- const autopsy = await autopsyWriterLock(join27(input.runDirectory, "writer.lock"));
17966
+ const autopsy = await autopsyWriterLock(join28(input.runDirectory, "writer.lock"));
17942
17967
  const leaseCheck = ghostLeaseCheckFromAutopsy(autopsy);
17943
17968
  if (leaseCheck === void 0) return { kind: "live" };
17944
17969
  return {
@@ -18228,7 +18253,7 @@ async function classifyScopedRun(input) {
18228
18253
  async function scanAnalystIssueRuns(input) {
18229
18254
  const ledgerHome = resolveActivationLedgerHome(input.home);
18230
18255
  const scopeTicketNumber = input.ticketNumber;
18231
- const booksRoot = join27(ledgerHome, "books");
18256
+ const booksRoot = join28(ledgerHome, "books");
18232
18257
  let wholeBook = false;
18233
18258
  let scopeRootIdentity;
18234
18259
  let bookNames;
@@ -18261,7 +18286,7 @@ async function scanAnalystIssueRuns(input) {
18261
18286
  const ghostLegs = [];
18262
18287
  const scopeConflicts = [];
18263
18288
  for (const book of bookNames) {
18264
- const bookDir = join27(booksRoot, book);
18289
+ const bookDir = join28(booksRoot, book);
18265
18290
  let runDirectories;
18266
18291
  try {
18267
18292
  runDirectories = await listBookRunDirectories(bookDir);
@@ -19007,7 +19032,7 @@ var init_analyst_metric_family = __esm({
19007
19032
  });
19008
19033
 
19009
19034
  // src/analyst-page.ts
19010
- import { dirname as dirname17, join as join28 } from "node:path";
19035
+ import { dirname as dirname17, join as join29 } from "node:path";
19011
19036
  function analystIssuePageKey(address) {
19012
19037
  const parts = ["book", address.bookKey];
19013
19038
  if (address.issueNumber !== void 0) {
@@ -19018,7 +19043,7 @@ function analystIssuePageKey(address) {
19018
19043
  return sha256Hex(parts.join("\0")).slice(0, 32);
19019
19044
  }
19020
19045
  function analystIssuePagePath(ledgerHome, address) {
19021
- return join28(ledgerHome, "analyst", "issues", `${analystIssuePageKey(address)}.json`);
19046
+ return join29(ledgerHome, "analyst", "issues", `${analystIssuePageKey(address)}.json`);
19022
19047
  }
19023
19048
  function analystIssuePageAddressFromPage(page) {
19024
19049
  return {
@@ -20582,7 +20607,7 @@ __export(public_role_summons_exports, {
20582
20607
  import { execFile as execFile3 } from "node:child_process";
20583
20608
  import { existsSync as existsSync13 } from "node:fs";
20584
20609
  import { realpath as realpath6 } from "node:fs/promises";
20585
- import { join as join29 } from "node:path";
20610
+ import { join as join30 } from "node:path";
20586
20611
  import { promisify as promisify3 } from "node:util";
20587
20612
  function createCapturingIo() {
20588
20613
  const chunks = [];
@@ -20606,7 +20631,7 @@ function parentDir(path) {
20606
20631
  function walkPackageRoot(start) {
20607
20632
  let dir = start;
20608
20633
  for (let i = 0; i < 12; i += 1) {
20609
- if (existsSync13(join29(dir, "package.json")) && existsSync13(join29(dir, "souls"))) {
20634
+ if (existsSync13(join30(dir, "package.json")) && existsSync13(join30(dir, "souls"))) {
20610
20635
  return dir;
20611
20636
  }
20612
20637
  const parent = parentDir(dir);
@@ -21214,7 +21239,7 @@ init_submission_ledger();
21214
21239
  init_filed_submission();
21215
21240
  init_report_ticket_tool();
21216
21241
  import { writeSync as writeSync2 } from "node:fs";
21217
- import { join as join30 } from "node:path";
21242
+ import { join as join31 } from "node:path";
21218
21243
  import { Value as Value2 } from "typebox/value";
21219
21244
 
21220
21245
  // src/activation-trace.ts
@@ -22832,7 +22857,7 @@ function readRoleRunCoordinates(ctx, label) {
22832
22857
  if (runDirectory === void 0) throw new Error(`${label} requires AK_ROLE_RUN_DIR`);
22833
22858
  const admitted = readPageSync(runDirectory, "admitted");
22834
22859
  if (typeof admitted?.projectRoot !== "string" || admitted.projectRoot.trim() === "") {
22835
- throw new Error(`${label} state.jsonl admitted-request row missing projectRoot (${join30(runDirectory, RUN_STATE_FILE)})`);
22860
+ throw new Error(`${label} state.jsonl admitted-request row missing projectRoot (${join31(runDirectory, RUN_STATE_FILE)})`);
22836
22861
  }
22837
22862
  return {
22838
22863
  runDirectory,
@@ -24225,7 +24250,7 @@ import { randomUUID as randomUUID10 } from "node:crypto";
24225
24250
  import { existsSync as existsSync15 } from "node:fs";
24226
24251
  import { mkdtemp, rm as rm2, writeFile as writeFile6 } from "node:fs/promises";
24227
24252
  import { tmpdir } from "node:os";
24228
- import { dirname as dirname19, isAbsolute as isAbsolute11, join as join31, resolve as resolve16 } from "node:path";
24253
+ import { dirname as dirname19, isAbsolute as isAbsolute11, join as join32, resolve as resolve16 } from "node:path";
24229
24254
 
24230
24255
  // src/agent-start-materials.ts
24231
24256
  function renderAgentStartMaterials(body, materials) {
@@ -24611,7 +24636,7 @@ function formatCodexFailurePayload(payload) {
24611
24636
  function cwdIsGitWorkTree(cwd) {
24612
24637
  let dir = cwd;
24613
24638
  for (; ; ) {
24614
- if (existsSync15(join31(dir, ".git"))) return true;
24639
+ if (existsSync15(join32(dir, ".git"))) return true;
24615
24640
  const parent = dirname19(dir);
24616
24641
  if (parent === dir) return false;
24617
24642
  dir = parent;
@@ -24828,8 +24853,8 @@ function createHeadlessRoleTurnHost(config) {
24828
24853
  }
24829
24854
  const env = { ...process.env, ...config.env ?? {} };
24830
24855
  const liveSessionParent = () => config.sessionIdentity.resolveSessionFile(request.principal);
24831
- const inputsDirectory = await mkdtemp(join31(tmpdir(), "ak-role-headless-"));
24832
- const systemPromptPath = join31(inputsDirectory, "system-prompt.txt");
24856
+ const inputsDirectory = await mkdtemp(join32(tmpdir(), "ak-role-headless-"));
24857
+ const systemPromptPath = join32(inputsDirectory, "system-prompt.txt");
24833
24858
  let mcpConfigPath;
24834
24859
  let outputSchemaPath;
24835
24860
  let startedWithSchema;
@@ -24837,12 +24862,12 @@ function createHeadlessRoleTurnHost(config) {
24837
24862
  await writeFile6(systemPromptPath, systemPrompt, "utf8");
24838
24863
  let deliveredSchema;
24839
24864
  if (codex) {
24840
- outputSchemaPath = join31(inputsDirectory, "output-schema.json");
24865
+ outputSchemaPath = join32(inputsDirectory, "output-schema.json");
24841
24866
  deliveredSchema = closeJsonSchemaForCodex(prepared.jsonSchema);
24842
24867
  await writeFile6(outputSchemaPath, `${JSON.stringify(deliveredSchema, null, 2)}
24843
24868
  `, "utf8");
24844
24869
  } else {
24845
- mcpConfigPath = join31(inputsDirectory, "mcp-config.json");
24870
+ mcpConfigPath = join32(inputsDirectory, "mcp-config.json");
24846
24871
  await writeFile6(
24847
24872
  mcpConfigPath,
24848
24873
  `${JSON.stringify(headlessMcpConfigDocument(prepared.mcpServers), null, 2)}
@@ -703,7 +703,8 @@ async function runOmittedLensBatch(argv, parsed, env, io) {
703
703
  }
704
704
  /**
705
705
  * One same-parent lookup. Seat branches still build their own summons.
706
- * A found child resumes that child; no prior run returns undefined so the caller mints.
706
+ * A found child resumes that child. No prior run, explicit new, or a typed
707
+ * session-id-missing resume (失败照记,记完再起新) returns undefined so the caller mints.
707
708
  */
708
709
  function resumeSameParentInstructionSeat(input) {
709
710
  return tryResumeSameTicketSeatRun({
@@ -19492,24 +19492,48 @@ var init_auto_resume = __esm({
19492
19492
  });
19493
19493
 
19494
19494
  // src/public-cli/seat-ticket-binding.ts
19495
+ import { join as join21 } from "node:path";
19496
+ function resumedSessionIdMissing(result, runId) {
19497
+ if (typeof result !== "object" || result === null) return false;
19498
+ const terminal = result.terminal;
19499
+ if (typeof terminal !== "object" || terminal === null) return false;
19500
+ if (terminal.runId !== runId) return false;
19501
+ const outcome = terminal.roleOutcome;
19502
+ if (typeof outcome !== "object" || outcome === null) return false;
19503
+ if (outcome.kind !== "failure") return false;
19504
+ const facts = outcome.decisiveFacts;
19505
+ if (typeof facts !== "object" || facts === null) return false;
19506
+ return facts.errorCode === SESSION_ID_MISSING;
19507
+ }
19508
+ async function writerLockBlocksFreshSummons(runDirectory) {
19509
+ const autopsy = await autopsyWriterLock(join21(runDirectory, WRITER_LOCK_FILE));
19510
+ return autopsy.verdict === "alive" || autopsy.verdict === "unknown";
19511
+ }
19495
19512
  async function tryResumeSameTicketSeatRun(input) {
19496
19513
  if (input.freshSummons === true) return void 0;
19497
19514
  if (input.parentRunPath.trim() === "") return void 0;
19515
+ const bookKey = resolveBookKeyFromGit(input.projectRoot);
19498
19516
  const previous = await findLatestRunForSeatTicket({
19499
19517
  home: input.home,
19500
- bookKey: resolveBookKeyFromGit(input.projectRoot),
19518
+ bookKey,
19501
19519
  role: input.role,
19502
19520
  parentRunPath: input.parentRunPath,
19503
19521
  ...input.ticketNumber === void 0 ? {} : { ticketNumber: input.ticketNumber }
19504
19522
  });
19505
19523
  if (previous === void 0) return void 0;
19506
- return await input.resume(previous.runId, input.summons, previous.runDirectory);
19524
+ const resumed = await input.resume(previous.runId, input.summons, previous.runDirectory);
19525
+ if (!resumedSessionIdMissing(resumed, previous.runId)) return resumed;
19526
+ const live = await resolveLiveRunDirectoryPath(previous.runDirectory, input.home, bookKey);
19527
+ if (live !== void 0 && await writerLockBlocksFreshSummons(live)) return resumed;
19528
+ return void 0;
19507
19529
  }
19530
+ var SESSION_ID_MISSING;
19508
19531
  var init_seat_ticket_binding = __esm({
19509
19532
  "src/public-cli/seat-ticket-binding.ts"() {
19510
19533
  "use strict";
19511
19534
  init_activation_ledger_git();
19512
19535
  init_run_lifecycle();
19536
+ SESSION_ID_MISSING = "session-id-missing";
19513
19537
  }
19514
19538
  });
19515
19539
 
@@ -21208,7 +21232,7 @@ __export(public_role_summons_exports, {
21208
21232
  import { execFile as execFile3 } from "node:child_process";
21209
21233
  import { existsSync as existsSync10 } from "node:fs";
21210
21234
  import { realpath as realpath5 } from "node:fs/promises";
21211
- import { join as join21 } from "node:path";
21235
+ import { join as join22 } from "node:path";
21212
21236
  import { promisify as promisify3 } from "node:util";
21213
21237
  function createCapturingIo() {
21214
21238
  const chunks = [];
@@ -21232,7 +21256,7 @@ function parentDir(path) {
21232
21256
  function walkPackageRoot(start) {
21233
21257
  let dir = start;
21234
21258
  for (let i = 0; i < 12; i += 1) {
21235
- if (existsSync10(join21(dir, "package.json")) && existsSync10(join21(dir, "souls"))) {
21259
+ if (existsSync10(join22(dir, "package.json")) && existsSync10(join22(dir, "souls"))) {
21236
21260
  return dir;
21237
21261
  }
21238
21262
  const parent = parentDir(dir);
@@ -21775,7 +21799,7 @@ var init_compliance_transport = __esm({
21775
21799
  });
21776
21800
 
21777
21801
  // src/role-run-relocation.ts
21778
- import { dirname as dirname14, join as join22 } from "node:path";
21802
+ import { dirname as dirname14, join as join23 } from "node:path";
21779
21803
  async function retainedRunPathsMatch(recordedPath, currentPath, currentRunDirectory) {
21780
21804
  if (recordedPath === currentPath) return true;
21781
21805
  if (typeof recordedPath !== "string") return false;
@@ -21821,6 +21845,7 @@ var run_lifecycle_exports = {};
21821
21845
  __export(run_lifecycle_exports, {
21822
21846
  AUTO_RESUME_LIMIT: () => AUTO_RESUME_LIMIT,
21823
21847
  RunWriterLeaseHeldError: () => RunWriterLeaseHeldError,
21848
+ WRITER_LOCK_FILE: () => WRITER_LOCK_FILE,
21824
21849
  acquireRunWriterLease: () => acquireRunWriterLease,
21825
21850
  autopsyWriterLock: () => autopsyWriterLock,
21826
21851
  clearCurrentCourt: () => clearCurrentCourt,
@@ -21843,7 +21868,7 @@ __export(run_lifecycle_exports, {
21843
21868
  });
21844
21869
  import { existsSync as existsSync11 } from "node:fs";
21845
21870
  import { chmod, lstat as lstat3, open, readdir as readdir3, readFile as readFile10, unlink } from "node:fs/promises";
21846
- import { basename as basename11, join as join23 } from "node:path";
21871
+ import { basename as basename11, join as join24 } from "node:path";
21847
21872
  async function writeRoleRunState(runDirectory, record) {
21848
21873
  const payload = { ...record, runDirectory };
21849
21874
  writeSectionSync(runDirectory, "runState", { ...payload });
@@ -22138,7 +22163,7 @@ async function createWriterLease(lockPath, runDirectory, reportCleanupFailure) {
22138
22163
  },
22139
22164
  relocate(nextRunDirectory) {
22140
22165
  currentRunDirectory = nextRunDirectory;
22141
- currentLockPath = join23(nextRunDirectory, WRITER_LOCK_FILE);
22166
+ currentLockPath = join24(nextRunDirectory, WRITER_LOCK_FILE);
22142
22167
  },
22143
22168
  async release() {
22144
22169
  if (released) return;
@@ -22177,10 +22202,10 @@ async function acquireRunWriterLease(runDirectory, onCleanupFailure) {
22177
22202
  };
22178
22203
  const reportReadFailure = (error) => {
22179
22204
  reportDiagnostic(
22180
- `writer lease lock read failed (holder liveness unverifiable; lock left in place) at ${join23(runDirectory, WRITER_LOCK_FILE)}: ${describeErrorIdentity(error)}`
22205
+ `writer lease lock read failed (holder liveness unverifiable; lock left in place) at ${join24(runDirectory, WRITER_LOCK_FILE)}: ${describeErrorIdentity(error)}`
22181
22206
  );
22182
22207
  };
22183
- const lockPath = join23(runDirectory, WRITER_LOCK_FILE);
22208
+ const lockPath = join24(runDirectory, WRITER_LOCK_FILE);
22184
22209
  let lastAutopsy = { verdict: "absent" };
22185
22210
  let lastReclaimFailure;
22186
22211
  for (let reclaimsLeft = WRITER_LEASE_RECLAIM_ROUNDS; ; reclaimsLeft -= 1) {
@@ -22246,7 +22271,7 @@ async function findRunDirectoryById(home, runId, onlyBookKey, onlyRole, knownRun
22246
22271
  }
22247
22272
  if (runId.trim() === "") return void 0;
22248
22273
  const ledgerHome = resolveActivationLedgerHome(home);
22249
- const booksRoot = join23(ledgerHome, "books");
22274
+ const booksRoot = join24(ledgerHome, "books");
22250
22275
  let bookKeys;
22251
22276
  try {
22252
22277
  bookKeys = await readdir3(booksRoot);
@@ -22296,7 +22321,7 @@ async function findLatestRunForSeatTicket(input) {
22296
22321
  let runDirectories;
22297
22322
  try {
22298
22323
  runDirectories = await listBookRunDirectories(
22299
- input.ticketNumber === void 0 ? bookDir : join23(bookDir, String(input.ticketNumber))
22324
+ input.ticketNumber === void 0 ? bookDir : join24(bookDir, String(input.ticketNumber))
22300
22325
  );
22301
22326
  } catch (error) {
22302
22327
  if (errorCodeOf(error) === "ENOENT") return void 0;
@@ -30068,10 +30093,10 @@ var init_analyst_book_key = __esm({
30068
30093
  // src/atomic-write.ts
30069
30094
  import { randomUUID as randomUUID7 } from "node:crypto";
30070
30095
  import { rename as rename2, rm, writeFile as writeFile4 } from "node:fs/promises";
30071
- import { dirname as dirname15, join as join24 } from "node:path";
30096
+ import { dirname as dirname15, join as join25 } from "node:path";
30072
30097
  async function writeFileAtomically(destination, contents, options) {
30073
30098
  const parent = dirname15(destination);
30074
- const temporary = join24(parent, `.atomic-write-${randomUUID7()}.tmp`);
30099
+ const temporary = join25(parent, `.atomic-write-${randomUUID7()}.tmp`);
30075
30100
  try {
30076
30101
  await writeFile4(temporary, contents, options);
30077
30102
  await rename2(temporary, destination);
@@ -30088,12 +30113,12 @@ var init_atomic_write = __esm({
30088
30113
 
30089
30114
  // src/analyst-index.ts
30090
30115
  import { open as open2, readFile as readFile11, unlink as unlink2 } from "node:fs/promises";
30091
- import { dirname as dirname16, join as join25 } from "node:path";
30116
+ import { dirname as dirname16, join as join26 } from "node:path";
30092
30117
  import { setTimeout as sleep } from "node:timers/promises";
30093
30118
  async function withAnalystLibraryIndexLock(ledgerHome, fn) {
30094
30119
  const indexPath = analystLibraryIndexPath(ledgerHome);
30095
30120
  ensureRealDirectoryTree(ledgerHome, dirname16(indexPath));
30096
- const lockPath = join25(dirname16(indexPath), LIBRARY_INDEX_LOCK_NAME);
30121
+ const lockPath = join26(dirname16(indexPath), LIBRARY_INDEX_LOCK_NAME);
30097
30122
  assertLedgerFileInsideHome(lockPath, ledgerHome);
30098
30123
  const startedAt = Date.now();
30099
30124
  while (true) {
@@ -30120,7 +30145,7 @@ async function withAnalystLibraryIndexLock(ledgerHome, fn) {
30120
30145
  }
30121
30146
  }
30122
30147
  function analystLibraryIndexPath(ledgerHome) {
30123
- return join25(ledgerHome, "analyst", "library-index.json");
30148
+ return join26(ledgerHome, "analyst", "library-index.json");
30124
30149
  }
30125
30150
  function rowFromIssueMetricsPage(page) {
30126
30151
  return {
@@ -30414,7 +30439,7 @@ var init_analyst_cohort = __esm({
30414
30439
  });
30415
30440
 
30416
30441
  // src/archivist-record-pointer.ts
30417
- import { basename as basename12, join as join26 } from "node:path";
30442
+ import { basename as basename12, join as join27 } from "node:path";
30418
30443
  async function resolveOfficerPointer(raw, parentRunDirectory) {
30419
30444
  const where = `${parentRunDirectory} officers`;
30420
30445
  if (!isRecord(raw) || raw.kind !== DIRECT_OFFICER_RUN_POINTER_KIND || raw.version !== 1) {
@@ -30447,7 +30472,7 @@ async function resolveOfficerPointer(raw, parentRunDirectory) {
30447
30472
  };
30448
30473
  }
30449
30474
  async function readBookedOfficerPointers(parentRunDirectory) {
30450
- const { records, diagnostics } = await readSitianRecords(join26(parentRunDirectory, RUN_HISTORY_FILE));
30475
+ const { records, diagnostics } = await readSitianRecords(join27(parentRunDirectory, RUN_HISTORY_FILE));
30451
30476
  if (diagnostics.length > 0) {
30452
30477
  throw new Error(
30453
30478
  `${RUN_HISTORY_FILE} has ${diagnostics.length} malformed row(s); officer pointers unreadable: ${parentRunDirectory}`
@@ -30730,7 +30755,7 @@ var init_analyst_gate_cycles_read = __esm({
30730
30755
 
30731
30756
  // src/analyst-ledger.ts
30732
30757
  import { readdir as readdir4 } from "node:fs/promises";
30733
- import { basename as basename13, join as join27 } from "node:path";
30758
+ import { basename as basename13, join as join28 } from "node:path";
30734
30759
  async function readExistingRunLifecycleState(runDirectory) {
30735
30760
  return (await readRoleRunIdentity(runDirectory))?.state;
30736
30761
  }
@@ -30750,7 +30775,7 @@ function ghostLeaseCheckFromAutopsy(autopsy) {
30750
30775
  }
30751
30776
  }
30752
30777
  async function classifyGhostCandidate(input) {
30753
- const autopsy = await autopsyWriterLock(join27(input.runDirectory, "writer.lock"));
30778
+ const autopsy = await autopsyWriterLock(join28(input.runDirectory, "writer.lock"));
30754
30779
  const leaseCheck = ghostLeaseCheckFromAutopsy(autopsy);
30755
30780
  if (leaseCheck === void 0) return { kind: "live" };
30756
30781
  return {
@@ -31040,7 +31065,7 @@ async function classifyScopedRun(input) {
31040
31065
  async function scanAnalystIssueRuns(input) {
31041
31066
  const ledgerHome = resolveActivationLedgerHome(input.home);
31042
31067
  const scopeTicketNumber = input.ticketNumber;
31043
- const booksRoot = join27(ledgerHome, "books");
31068
+ const booksRoot = join28(ledgerHome, "books");
31044
31069
  let wholeBook = false;
31045
31070
  let scopeRootIdentity;
31046
31071
  let bookNames;
@@ -31073,7 +31098,7 @@ async function scanAnalystIssueRuns(input) {
31073
31098
  const ghostLegs = [];
31074
31099
  const scopeConflicts = [];
31075
31100
  for (const book of bookNames) {
31076
- const bookDir = join27(booksRoot, book);
31101
+ const bookDir = join28(booksRoot, book);
31077
31102
  let runDirectories;
31078
31103
  try {
31079
31104
  runDirectories = await listBookRunDirectories(bookDir);
@@ -31819,7 +31844,7 @@ var init_analyst_metric_family = __esm({
31819
31844
  });
31820
31845
 
31821
31846
  // src/analyst-page.ts
31822
- import { dirname as dirname17, join as join28 } from "node:path";
31847
+ import { dirname as dirname17, join as join29 } from "node:path";
31823
31848
  function analystIssuePageKey(address) {
31824
31849
  const parts = ["book", address.bookKey];
31825
31850
  if (address.issueNumber !== void 0) {
@@ -31830,7 +31855,7 @@ function analystIssuePageKey(address) {
31830
31855
  return sha256Hex(parts.join("\0")).slice(0, 32);
31831
31856
  }
31832
31857
  function analystIssuePagePath(ledgerHome, address) {
31833
- return join28(ledgerHome, "analyst", "issues", `${analystIssuePageKey(address)}.json`);
31858
+ return join29(ledgerHome, "analyst", "issues", `${analystIssuePageKey(address)}.json`);
31834
31859
  }
31835
31860
  function analystIssuePageAddressFromPage(page) {
31836
31861
  return {
@@ -33384,12 +33409,12 @@ var init_cli = __esm({
33384
33409
 
33385
33410
  // src/public-cli/main.ts
33386
33411
  import { existsSync as existsSync12 } from "node:fs";
33387
- import { dirname as dirname18, join as join29 } from "node:path";
33412
+ import { dirname as dirname18, join as join30 } from "node:path";
33388
33413
  import { fileURLToPath as fileURLToPath2 } from "node:url";
33389
33414
  var here = dirname18(fileURLToPath2(import.meta.url));
33390
33415
  function resolvePackageRoot(binDir) {
33391
- const canonical = join29(binDir, "..", "..");
33392
- if (existsSync12(join29(canonical, "package.json"))) {
33416
+ const canonical = join30(binDir, "..", "..");
33417
+ if (existsSync12(join30(canonical, "package.json"))) {
33393
33418
  return canonical;
33394
33419
  }
33395
33420
  return binDir;
@@ -23,7 +23,7 @@ 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
- const WRITER_LOCK_FILE = "writer.lock";
26
+ export const WRITER_LOCK_FILE = "writer.lock";
27
27
  export async function writeRoleRunState(runDirectory, record) {
28
28
  const payload = { ...record, runDirectory };
29
29
  writeSectionSync(runDirectory, "runState", { ...payload });
@@ -1,38 +1,66 @@
1
1
  /**
2
- * Shared same-parent resume seam for gate / officer seats (#635 / #637 / #747 / #987).
3
- *
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
7
- * a fresh mint. Returns undefined when the caller declared an explicit fresh
8
- * summons (`ak-role new`) or when no prior run exists; both mint new.
9
- * freshSummons is required so no seat can drift back into its own skip branch.
2
+ * Shared same-parent resume seam for gate / officer seats (#635 / #637 / #747 / #987 / #1253).
3
+ * Contract is on tryResumeSameTicketSeatRun.
10
4
  */
5
+ import { join } from "node:path";
11
6
  import { resolveBookKeyFromGit } from "../activation-ledger-git.js";
12
- import { findLatestRunForSeatTicket, } from "./run-lifecycle.js";
7
+ import { autopsyWriterLock, findLatestRunForSeatTicket, resolveLiveRunDirectoryPath, WRITER_LOCK_FILE, } from "./run-lifecycle.js";
8
+ const SESSION_ID_MISSING = "session-id-missing";
9
+ function resumedSessionIdMissing(result, runId) {
10
+ if (typeof result !== "object" || result === null)
11
+ return false;
12
+ const terminal = result.terminal;
13
+ if (typeof terminal !== "object" || terminal === null)
14
+ return false;
15
+ if (terminal.runId !== runId)
16
+ return false;
17
+ const outcome = terminal.roleOutcome;
18
+ if (typeof outcome !== "object" || outcome === null)
19
+ return false;
20
+ if (outcome.kind !== "failure")
21
+ return false;
22
+ const facts = outcome.decisiveFacts;
23
+ if (typeof facts !== "object" || facts === null)
24
+ return false;
25
+ return facts.errorCode === SESSION_ID_MISSING;
26
+ }
27
+ /** Live or unverifiable holder: same fail-closed verdict as lease acquire. */
28
+ async function writerLockBlocksFreshSummons(runDirectory) {
29
+ const autopsy = await autopsyWriterLock(join(runDirectory, WRITER_LOCK_FILE));
30
+ return autopsy.verdict === "alive" || autopsy.verdict === "unknown";
31
+ }
13
32
  /**
14
- * Sole same-seat → resume decision by parent run path (#637 / #724 / #747 / #987).
33
+ * Sole same-seat → resume decision by parent run path (#637 / #724 / #747 / #987 / #1253).
15
34
  * A supplied typed ticket only narrows the prior-run search. When a prior run is
16
35
  * found, resume carries this summons' materials and the located runDirectory so
17
- * load does not re-walk every book for the bare runId (#1195). Lookup/resume
18
- * failures propagate (失败诚实) — never wash into a fresh mint. Returns undefined
19
- * when the caller declared an explicit fresh summons (`ak-role new`), the parent
20
- * path is blank, or no prior run exists; those mint new. freshSummons is
21
- * required so no seat can drift back into its own skip branch.
36
+ * load does not re-walk every book for the bare runId (#1195). Typed
37
+ * session-id-missing is already recorded on that run (失败照记,记完再起新);
38
+ * return undefined so the caller mints once. Resume runs first; this seam does
39
+ * not look up a session id. A held or unverifiable writer lock, and every other
40
+ * result, returns as-is. Also returns undefined for explicit `ak-role new`, a
41
+ * blank parent path, or no prior run. freshSummons is required so no seat can
42
+ * drift back into its own skip branch.
22
43
  */
23
44
  export async function tryResumeSameTicketSeatRun(input) {
24
45
  if (input.freshSummons === true)
25
46
  return undefined;
26
47
  if (input.parentRunPath.trim() === "")
27
48
  return undefined;
49
+ const bookKey = resolveBookKeyFromGit(input.projectRoot);
28
50
  const previous = await findLatestRunForSeatTicket({
29
51
  home: input.home,
30
- bookKey: resolveBookKeyFromGit(input.projectRoot),
52
+ bookKey,
31
53
  role: input.role,
32
54
  parentRunPath: input.parentRunPath,
33
55
  ...(input.ticketNumber === undefined ? {} : { ticketNumber: input.ticketNumber }),
34
56
  });
35
57
  if (previous === undefined)
36
58
  return undefined;
37
- return await input.resume(previous.runId, input.summons, previous.runDirectory);
59
+ const resumed = await input.resume(previous.runId, input.summons, previous.runDirectory);
60
+ if (!resumedSessionIdMissing(resumed, previous.runId))
61
+ return resumed;
62
+ const live = await resolveLiveRunDirectoryPath(previous.runDirectory, input.home, bookKey);
63
+ if (live !== undefined && await writerLockBlocksFreshSummons(live))
64
+ return resumed;
65
+ return undefined;
38
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.5941",
3
+ "version": "0.1.5946",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -973,7 +973,8 @@ async function runOmittedLensBatch(
973
973
 
974
974
  /**
975
975
  * One same-parent lookup. Seat branches still build their own summons.
976
- * A found child resumes that child; no prior run returns undefined so the caller mints.
976
+ * A found child resumes that child. No prior run, explicit new, or a typed
977
+ * session-id-missing resume (失败照记,记完再起新) returns undefined so the caller mints.
977
978
  */
978
979
  function resumeSameParentInstructionSeat(input: {
979
980
  readonly env: InstructionSeatRunEnv;
@@ -130,7 +130,7 @@ export type SameTicketSummonsMaterials = {
130
130
  readonly sourceRun?: NotarySourceRunLocator;
131
131
  };
132
132
 
133
- const WRITER_LOCK_FILE = "writer.lock";
133
+ export const WRITER_LOCK_FILE = "writer.lock";
134
134
 
135
135
  export async function writeRoleRunState(
136
136
  runDirectory: string,
@@ -1,29 +1,51 @@
1
1
  /**
2
- * Shared same-parent resume seam for gate / officer seats (#635 / #637 / #747 / #987).
3
- *
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
7
- * a fresh mint. Returns undefined when the caller declared an explicit fresh
8
- * summons (`ak-role new`) or when no prior run exists; both mint new.
9
- * freshSummons is required so no seat can drift back into its own skip branch.
2
+ * Shared same-parent resume seam for gate / officer seats (#635 / #637 / #747 / #987 / #1253).
3
+ * Contract is on tryResumeSameTicketSeatRun.
10
4
  */
5
+ import { join } from "node:path";
6
+
11
7
  import { resolveBookKeyFromGit } from "../activation-ledger-git.ts";
12
8
  import {
9
+ autopsyWriterLock,
13
10
  findLatestRunForSeatTicket,
11
+ resolveLiveRunDirectoryPath,
12
+ WRITER_LOCK_FILE,
14
13
  type RoleRunRecord,
15
14
  type SameTicketSummonsMaterials,
16
15
  } from "./run-lifecycle.ts";
17
16
 
17
+ const SESSION_ID_MISSING = "session-id-missing";
18
+
19
+ function resumedSessionIdMissing(result: unknown, runId: string): boolean {
20
+ if (typeof result !== "object" || result === null) return false;
21
+ const terminal = (result as { terminal?: unknown }).terminal;
22
+ if (typeof terminal !== "object" || terminal === null) return false;
23
+ if ((terminal as { runId?: unknown }).runId !== runId) return false;
24
+ const outcome = (terminal as { roleOutcome?: unknown }).roleOutcome;
25
+ if (typeof outcome !== "object" || outcome === null) return false;
26
+ if ((outcome as { kind?: unknown }).kind !== "failure") return false;
27
+ const facts = (outcome as { decisiveFacts?: unknown }).decisiveFacts;
28
+ if (typeof facts !== "object" || facts === null) return false;
29
+ return (facts as { errorCode?: unknown }).errorCode === SESSION_ID_MISSING;
30
+ }
31
+
32
+ /** Live or unverifiable holder: same fail-closed verdict as lease acquire. */
33
+ async function writerLockBlocksFreshSummons(runDirectory: string): Promise<boolean> {
34
+ const autopsy = await autopsyWriterLock(join(runDirectory, WRITER_LOCK_FILE));
35
+ return autopsy.verdict === "alive" || autopsy.verdict === "unknown";
36
+ }
37
+
18
38
  /**
19
- * Sole same-seat → resume decision by parent run path (#637 / #724 / #747 / #987).
39
+ * Sole same-seat → resume decision by parent run path (#637 / #724 / #747 / #987 / #1253).
20
40
  * A supplied typed ticket only narrows the prior-run search. When a prior run is
21
41
  * found, resume carries this summons' materials and the located runDirectory so
22
- * load does not re-walk every book for the bare runId (#1195). Lookup/resume
23
- * failures propagate (失败诚实) — never wash into a fresh mint. Returns undefined
24
- * when the caller declared an explicit fresh summons (`ak-role new`), the parent
25
- * path is blank, or no prior run exists; those mint new. freshSummons is
26
- * required so no seat can drift back into its own skip branch.
42
+ * load does not re-walk every book for the bare runId (#1195). Typed
43
+ * session-id-missing is already recorded on that run (失败照记,记完再起新);
44
+ * return undefined so the caller mints once. Resume runs first; this seam does
45
+ * not look up a session id. A held or unverifiable writer lock, and every other
46
+ * result, returns as-is. Also returns undefined for explicit `ak-role new`, a
47
+ * blank parent path, or no prior run. freshSummons is required so no seat can
48
+ * drift back into its own skip branch.
27
49
  */
28
50
  export async function tryResumeSameTicketSeatRun<T>(input: {
29
51
  readonly home: string;
@@ -41,13 +63,18 @@ export async function tryResumeSameTicketSeatRun<T>(input: {
41
63
  }): Promise<T | undefined> {
42
64
  if (input.freshSummons === true) return undefined;
43
65
  if (input.parentRunPath.trim() === "") return undefined;
66
+ const bookKey = resolveBookKeyFromGit(input.projectRoot);
44
67
  const previous = await findLatestRunForSeatTicket({
45
68
  home: input.home,
46
- bookKey: resolveBookKeyFromGit(input.projectRoot),
69
+ bookKey,
47
70
  role: input.role,
48
71
  parentRunPath: input.parentRunPath,
49
72
  ...(input.ticketNumber === undefined ? {} : { ticketNumber: input.ticketNumber }),
50
73
  });
51
74
  if (previous === undefined) return undefined;
52
- return await input.resume(previous.runId, input.summons, previous.runDirectory);
75
+ const resumed = await input.resume(previous.runId, input.summons, previous.runDirectory);
76
+ if (!resumedSessionIdMissing(resumed, previous.runId)) return resumed;
77
+ const live = await resolveLiveRunDirectoryPath(previous.runDirectory, input.home, bookKey);
78
+ if (live !== undefined && await writerLockBlocksFreshSummons(live)) return resumed;
79
+ return undefined;
53
80
  }