@akagilnc/pi-workflow-roles 0.1.2517 → 0.1.2525

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.
@@ -16111,14 +16111,19 @@ async function createWriterLease(lockPath, runDirectory, reportCleanupFailure) {
16111
16111
  };
16112
16112
  }
16113
16113
  async function acquireRunWriterLease(runDirectory, onCleanupFailure) {
16114
- const reportCleanupFailure = (error) => {
16114
+ const reportDiagnostic = (diagnostic) => {
16115
+ const line2 = diagnostic.endsWith("\n") ? diagnostic : `${diagnostic}
16116
+ `;
16115
16117
  try {
16116
- onCleanupFailure?.(
16117
- `writer lease lock cleanup failed (best-effort continue; stale lock is reclaimed by the next acquire's holder autopsy) at ${join7(runDirectory, WRITER_LOCK_FILE)}: ${describeErrorIdentity(error)}`
16118
- );
16118
+ onCleanupFailure?.(line2);
16119
16119
  } catch {
16120
16120
  }
16121
16121
  };
16122
+ const reportCleanupFailure = (error) => {
16123
+ reportDiagnostic(
16124
+ `writer lease lock cleanup failed (best-effort continue; stale lock is reclaimed by the next acquire's holder autopsy) at ${join7(runDirectory, WRITER_LOCK_FILE)}: ${describeErrorIdentity(error)}`
16125
+ );
16126
+ };
16122
16127
  const lockPath = join7(runDirectory, WRITER_LOCK_FILE);
16123
16128
  let lastAutopsy = { verdict: "absent" };
16124
16129
  for (let reclaimsLeft = WRITER_LEASE_RECLAIM_ROUNDS; ; reclaimsLeft -= 1) {
@@ -16149,6 +16154,9 @@ async function acquireRunWriterLease(runDirectory, onCleanupFailure) {
16149
16154
  `stale writer lease reclaim failed at ${lockPath} (autopsy: ${describeAutopsy(lastAutopsy)}): ${describeErrorIdentity(reclaimError)}`
16150
16155
  );
16151
16156
  }
16157
+ reportDiagnostic(
16158
+ `stale writer lease reclaimed at ${lockPath} (holder pid ${lastAutopsy.pid} verified dead): the killed holder may have left an orphaned pi child still writing this run \u2014 check for a surviving pi process on this run before continuing`
16159
+ );
16152
16160
  }
16153
16161
  throw new RunWriterLeaseHeldError(
16154
16162
  `role run writer lease stayed contested at ${lockPath} after ${WRITER_LEASE_RECLAIM_ROUNDS} reclaims (last autopsy: ${describeAutopsy(lastAutopsy)})`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.2517",
3
+ "version": "0.1.2525",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -42,11 +42,11 @@
42
42
  "scripts": {
43
43
  "build": "tsx scripts/generate-tool-execution-observation-schema.ts && tsc -p tsconfig.build.json && node scripts/build-package.mjs",
44
44
  "prepack": "npm run build",
45
- "test": "node --import tsx --test test/unit/**/*.test.ts test/contract/**/*.test.ts",
46
- "test:fast": "node --import tsx --test test/unit/**/*.test.ts test/contract/**/*.test.ts",
47
- "test:integration": "node --import tsx --test test/unit/**/*.test.ts test/contract/**/*.test.ts test/integration/**/*.test.ts",
45
+ "test": "node --import tsx --import ./scripts/test-process-env-preload.mjs --test test/unit/**/*.test.ts test/contract/**/*.test.ts",
46
+ "test:fast": "node --import tsx --import ./scripts/test-process-env-preload.mjs --test test/unit/**/*.test.ts test/contract/**/*.test.ts",
47
+ "test:integration": "node --import tsx --import ./scripts/test-process-env-preload.mjs --test test/unit/**/*.test.ts test/contract/**/*.test.ts test/integration/**/*.test.ts",
48
48
  "test:all": "node scripts/run-test-all.mjs",
49
- "test:adjudication": "node --import tsx --test test/adjudication/**/*.test.ts",
49
+ "test:adjudication": "node --import tsx --import ./scripts/test-process-env-preload.mjs --test test/adjudication/**/*.test.ts",
50
50
  "typecheck": "tsc --noEmit"
51
51
  },
52
52
  "pi": {
@@ -523,7 +523,8 @@ const WRITER_LEASE_RECLAIM_ROUNDS = 3;
523
523
  * race repeatedly lost) surfaces the same typed error instead of spinning.
524
524
  *
525
525
  * `onCleanupFailure` receives a non-terminal diagnostic line when release-time
526
- * lock cleanup fails or a contested lock cannot be read. Release stays
526
+ * lock cleanup fails, a contested lock cannot be read, or a stale lock is
527
+ * reclaimed (the #556 orphan-pi residual declaration). Release stays
527
528
  * best-effort (a stale lock is reclaimed by the next acquire's autopsy), but
528
529
  * the true error identity must still land somewhere observable — silent
529
530
  * swallowing is forbidden.
@@ -532,18 +533,24 @@ export async function acquireRunWriterLease(
532
533
  runDirectory: string,
533
534
  onCleanupFailure?: (diagnostic: string) => void,
534
535
  ): Promise<RunWriterLease> {
535
- const reportCleanupFailure = (error: unknown): void => {
536
- // Sink isolation: a throwing onCleanupFailure must not propagate through
537
- // release() — release stays best-effort by contract. The true cleanup
538
- // cause has already been handed to the sink as its argument.
536
+ const reportDiagnostic = (diagnostic: string): void => {
537
+ // Single sink exit for every non-terminal writer-lease diagnostic (#556):
538
+ // a throwing onCleanupFailure must not propagate through release() or
539
+ // acquire() — both are best-effort by contract. Template wording stays at
540
+ // the call sites, never inside this wrapper. The default io.stderr writes
541
+ // raw, so this exit guarantees the line delimiter.
542
+ const line = diagnostic.endsWith("\n") ? diagnostic : `${diagnostic}\n`;
539
543
  try {
540
- onCleanupFailure?.(
541
- `writer lease lock cleanup failed (best-effort continue; stale lock is reclaimed by the next acquire's holder autopsy) at ${join(runDirectory, WRITER_LOCK_FILE)}: ${describeErrorIdentity(error)}`,
542
- );
544
+ onCleanupFailure?.(line);
543
545
  } catch {
544
- // diagnostic-sink failure is itself best-effort; never break release().
546
+ // diagnostic-sink failure is itself best-effort; never break acquire()/release().
545
547
  }
546
548
  };
549
+ const reportCleanupFailure = (error: unknown): void => {
550
+ reportDiagnostic(
551
+ `writer lease lock cleanup failed (best-effort continue; stale lock is reclaimed by the next acquire's holder autopsy) at ${join(runDirectory, WRITER_LOCK_FILE)}: ${describeErrorIdentity(error)}`,
552
+ );
553
+ };
547
554
  const lockPath = join(runDirectory, WRITER_LOCK_FILE);
548
555
  let lastAutopsy: WriterLockAutopsy = { verdict: "absent" };
549
556
  // The reclaim budget: every successful reclaim is immediately followed by a
@@ -585,6 +592,12 @@ export async function acquireRunWriterLease(
585
592
  `stale writer lease reclaim failed at ${lockPath} (autopsy: ${describeAutopsy(lastAutopsy)}): ${describeErrorIdentity(reclaimError)}`,
586
593
  );
587
594
  }
595
+ // #556 residual declaration, facts only: the pid-only autopsy proves the
596
+ // CLI holder died; its pi child may outlive it and keep writing this run.
597
+ // Declare — do not guard.
598
+ reportDiagnostic(
599
+ `stale writer lease reclaimed at ${lockPath} (holder pid ${lastAutopsy.pid} verified dead): the killed holder may have left an orphaned pi child still writing this run — check for a surviving pi process on this run before continuing`,
600
+ );
588
601
  }
589
602
  throw new RunWriterLeaseHeldError(
590
603
  `role run writer lease stayed contested at ${lockPath} after ${WRITER_LEASE_RECLAIM_ROUNDS} reclaims (last autopsy: ${describeAutopsy(lastAutopsy)})`,