@akagilnc/pi-workflow-roles 0.1.1774 → 0.1.1792

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.
@@ -16718,13 +16718,149 @@ var init_invocation = __esm({
16718
16718
  }
16719
16719
  });
16720
16720
 
16721
+ // src/reviewer-git-snapshot.ts
16722
+ var init_reviewer_git_snapshot = __esm({
16723
+ "src/reviewer-git-snapshot.ts"() {
16724
+ "use strict";
16725
+ }
16726
+ });
16727
+
16728
+ // src/reviewer-preflight-error.ts
16729
+ var init_reviewer_preflight_error = __esm({
16730
+ "src/reviewer-preflight-error.ts"() {
16731
+ "use strict";
16732
+ }
16733
+ });
16734
+
16735
+ // src/reviewer-pinned-git.ts
16736
+ import { execFile as execFile2 } from "node:child_process";
16737
+ import { promisify as promisify2 } from "node:util";
16738
+ var execFileAsync2;
16739
+ var init_reviewer_pinned_git = __esm({
16740
+ "src/reviewer-pinned-git.ts"() {
16741
+ "use strict";
16742
+ init_reviewer_git_snapshot();
16743
+ init_sha256();
16744
+ init_reviewer_preflight_error();
16745
+ execFileAsync2 = promisify2(execFile2);
16746
+ }
16747
+ });
16748
+
16749
+ // src/reviewer-prompt-identity.ts
16750
+ var init_reviewer_prompt_identity = __esm({
16751
+ "src/reviewer-prompt-identity.ts"() {
16752
+ "use strict";
16753
+ }
16754
+ });
16755
+
16756
+ // src/reviewer-scope-prompt.ts
16757
+ var init_reviewer_scope_prompt = __esm({
16758
+ "src/reviewer-scope-prompt.ts"() {
16759
+ "use strict";
16760
+ }
16761
+ });
16762
+
16763
+ // src/reviewer-construction.ts
16764
+ var REVIEWER_CONSTRUCTION_RECIPE, REVIEWER_AXIS_OUTPUT_ADAPTER, REVIEWER_STANDARDS_CONCLUSION_KEYS, REVIEWER_STANDARDS_CONCLUSION_LABELS;
16765
+ var init_reviewer_construction = __esm({
16766
+ "src/reviewer-construction.ts"() {
16767
+ "use strict";
16768
+ init_sha256();
16769
+ init_reviewer_scope_prompt();
16770
+ REVIEWER_CONSTRUCTION_RECIPE = Object.freeze({
16771
+ recipeId: "reviewer-common-bundle",
16772
+ version: 1,
16773
+ runtimeVersion: "1",
16774
+ implementationSha256: sha256Hex("reviewer-common-bundle:v1:direct-text-prompts")
16775
+ });
16776
+ REVIEWER_AXIS_OUTPUT_ADAPTER = Object.freeze({
16777
+ adapterId: "reviewer-axis-output",
16778
+ version: 1,
16779
+ implementationSha256: sha256Hex("reviewer-axis-output:v1:single-axis-verbatim-report+standards-three-priorities")
16780
+ });
16781
+ REVIEWER_STANDARDS_CONCLUSION_KEYS = Object.freeze([
16782
+ "constitutionality",
16783
+ "minimum-necessary-test-cost",
16784
+ "complexity"
16785
+ ]);
16786
+ REVIEWER_STANDARDS_CONCLUSION_LABELS = Object.freeze({
16787
+ constitutionality: "constitutionality",
16788
+ "minimum-necessary-test-cost": "minimum-necessary test cost",
16789
+ complexity: "complexity"
16790
+ });
16791
+ }
16792
+ });
16793
+
16794
+ // src/reviewer-dispatch.ts
16795
+ var REVIEWER_PREFLIGHT_VIOLATIONS;
16796
+ var init_reviewer_dispatch = __esm({
16797
+ "src/reviewer-dispatch.ts"() {
16798
+ "use strict";
16799
+ init_reviewer_git_snapshot();
16800
+ init_reviewer_pinned_git();
16801
+ init_reviewer_pinned_git();
16802
+ init_reviewer_prompt_identity();
16803
+ init_sha256();
16804
+ init_reviewer_construction();
16805
+ init_reviewer_preflight_error();
16806
+ init_sha256();
16807
+ init_reviewer_prompt_identity();
16808
+ REVIEWER_PREFLIGHT_VIOLATIONS = ["base-invalid", "range-invalid", "prompt-identity-invalid", "target-drift"];
16809
+ }
16810
+ });
16811
+
16721
16812
  // src/public-cli/explicit-internal.ts
16722
- import { execFile as execFile2, spawn } from "node:child_process";
16723
- import { constants } from "node:fs";
16724
- import { access, realpath as realpath3 } from "node:fs/promises";
16813
+ import { execFile as execFile3, spawn } from "node:child_process";
16814
+ import { constants, writeFileSync } from "node:fs";
16815
+ import { access, readFile as readFile5, realpath as realpath3, unlink } from "node:fs/promises";
16725
16816
  import { delimiter as delimiter2, isAbsolute as isAbsolute4, join as join5, resolve as resolve5 } from "node:path";
16726
16817
  import { platform } from "node:process";
16727
- import { promisify as promisify2 } from "node:util";
16818
+ import { promisify as promisify3 } from "node:util";
16819
+ function isReviewerPreflightViolation(value) {
16820
+ return typeof value === "string" && REVIEWER_PREFLIGHT_VIOLATIONS.includes(value);
16821
+ }
16822
+ function reviewerDispatchRejectionPath(runDirectory) {
16823
+ return join5(runDirectory, REVIEWER_DISPATCH_REJECTION_FILE);
16824
+ }
16825
+ async function clearReviewerDispatchRejection(runDirectory) {
16826
+ try {
16827
+ await unlink(reviewerDispatchRejectionPath(runDirectory));
16828
+ } catch (error) {
16829
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
16830
+ return;
16831
+ }
16832
+ throw error;
16833
+ }
16834
+ }
16835
+ async function readReviewerDispatchRejection(runDirectory) {
16836
+ let raw;
16837
+ try {
16838
+ raw = await readFile5(reviewerDispatchRejectionPath(runDirectory), "utf8");
16839
+ } catch (error) {
16840
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
16841
+ return void 0;
16842
+ }
16843
+ throw error;
16844
+ }
16845
+ const parsed = JSON.parse(raw);
16846
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
16847
+ const error = new Error("Reviewer dispatch rejection page must be a JSON object");
16848
+ error.name = "ReviewerDispatchRejectionContractError";
16849
+ throw error;
16850
+ }
16851
+ const record4 = parsed;
16852
+ if (typeof record4.diagnostic !== "string" || record4.diagnostic.trim() === "" || !Array.isArray(record4.violations) || record4.violations.length === 0 || record4.violations.some((value) => !isReviewerPreflightViolation(value))) {
16853
+ const error = new Error("Reviewer dispatch rejection page has unusable required fields");
16854
+ error.name = "ReviewerDispatchRejectionContractError";
16855
+ throw error;
16856
+ }
16857
+ return {
16858
+ cause: "activation",
16859
+ diagnostic: record4.diagnostic,
16860
+ identity: { name: "ReviewerDispatchRejectionError" },
16861
+ details: { violations: Object.freeze([...record4.violations]) }
16862
+ };
16863
+ }
16728
16864
  function resolveInternalRoleEntrypoint(packageRoot2) {
16729
16865
  return join5(packageRoot2, INTERNAL_ROLE_ENTRYPOINT_RELATIVE);
16730
16866
  }
@@ -16765,7 +16901,7 @@ async function resolveSelectedPi(command, cwd, env) {
16765
16901
  }
16766
16902
  async function selectedPiIdentity(command, cwd, env) {
16767
16903
  const executable = await resolveSelectedPi(command, cwd, env);
16768
- const { stdout } = await execFileAsync2(executable, ["--version"], {
16904
+ const { stdout } = await execFileAsync3(executable, ["--version"], {
16769
16905
  cwd,
16770
16906
  env,
16771
16907
  encoding: "utf8"
@@ -16802,13 +16938,15 @@ async function runExplicitInternalActivation(options) {
16802
16938
  env
16803
16939
  });
16804
16940
  }
16805
- var execFileAsync2, defaultExplicitInternalPiRunner;
16941
+ var REVIEWER_DISPATCH_REJECTION_FILE, execFileAsync3, defaultExplicitInternalPiRunner;
16806
16942
  var init_explicit_internal = __esm({
16807
16943
  "src/public-cli/explicit-internal.ts"() {
16808
16944
  "use strict";
16809
16945
  init_invocation();
16810
16946
  init_registry2();
16811
- execFileAsync2 = promisify2(execFile2);
16947
+ init_reviewer_dispatch();
16948
+ REVIEWER_DISPATCH_REJECTION_FILE = "typed-known-failure.json";
16949
+ execFileAsync3 = promisify3(execFile3);
16812
16950
  defaultExplicitInternalPiRunner = async (args, options) => {
16813
16951
  const command = options.env.PI_BINARY ?? "pi";
16814
16952
  const piIdentity = await selectedPiIdentity(command, options.cwd, options.env);
@@ -16863,7 +17001,7 @@ var init_explicit_internal = __esm({
16863
17001
 
16864
17002
  // src/package-resources/method-skill.ts
16865
17003
  import { createHash as createHash3 } from "node:crypto";
16866
- import { readFile as readFile5, realpath as realpath4 } from "node:fs/promises";
17004
+ import { readFile as readFile6, realpath as realpath4 } from "node:fs/promises";
16867
17005
  import { join as join6 } from "node:path";
16868
17006
  function gitBlobOid(bytes) {
16869
17007
  const body = typeof bytes === "string" ? Buffer.from(bytes, "utf8") : Buffer.from(bytes);
@@ -16982,7 +17120,7 @@ async function loadPackagedMethodSkillMaterial(packageRoot2, name) {
16982
17120
  const provenancePath = join6(rootDirectory, "provenance.json");
16983
17121
  let provenanceRaw;
16984
17122
  try {
16985
- provenanceRaw = await readFile5(provenancePath, "utf8");
17123
+ provenanceRaw = await readFile6(provenancePath, "utf8");
16986
17124
  } catch (error) {
16987
17125
  throw new PackagedMethodSkillUnavailableError(name, provenancePath, error);
16988
17126
  }
@@ -16999,7 +17137,7 @@ async function loadPackagedMethodSkillMaterial(packageRoot2, name) {
16999
17137
  const absolute = join6(rootDirectory, rel);
17000
17138
  let bytes;
17001
17139
  try {
17002
- bytes = await readFile5(absolute);
17140
+ bytes = await readFile6(absolute);
17003
17141
  } catch (error) {
17004
17142
  throw new PackagedMethodSkillUnavailableError(name, absolute, error);
17005
17143
  }
@@ -17015,7 +17153,7 @@ async function loadPackagedMethodSkillMaterial(packageRoot2, name) {
17015
17153
  let raw;
17016
17154
  try {
17017
17155
  skillPath = await realpath4(skillPathConfigured);
17018
- raw = await readFile5(skillPath, "utf8");
17156
+ raw = await readFile6(skillPath, "utf8");
17019
17157
  } catch (error) {
17020
17158
  throw new PackagedMethodSkillUnavailableError(name, skillPathConfigured, error);
17021
17159
  }
@@ -17130,7 +17268,7 @@ var init_public_run_credentials = __esm({
17130
17268
  });
17131
17269
 
17132
17270
  // src/public-cli/run-lifecycle.ts
17133
- import { lstat as lstat2, open, readdir as readdir2, readFile as readFile6, unlink, writeFile as writeFile3 } from "node:fs/promises";
17271
+ import { lstat as lstat2, open, readdir as readdir2, readFile as readFile7, unlink as unlink2, writeFile as writeFile3 } from "node:fs/promises";
17134
17272
  import { join as join7 } from "node:path";
17135
17273
  function isV1ResumableProvider(provider) {
17136
17274
  return V1_RESUMABLE_PROVIDERS.includes(provider);
@@ -17140,7 +17278,7 @@ function typedProviderHttpPath(runDirectory) {
17140
17278
  }
17141
17279
  async function clearTypedProviderHttpObservation(runDirectory) {
17142
17280
  try {
17143
- await unlink(typedProviderHttpPath(runDirectory));
17281
+ await unlink2(typedProviderHttpPath(runDirectory));
17144
17282
  } catch (error) {
17145
17283
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
17146
17284
  return;
@@ -17151,7 +17289,7 @@ async function clearTypedProviderHttpObservation(runDirectory) {
17151
17289
  async function readTypedHttp429Observation(runDirectory) {
17152
17290
  try {
17153
17291
  const raw = JSON.parse(
17154
- await readFile6(typedProviderHttpPath(runDirectory), "utf8")
17292
+ await readFile7(typedProviderHttpPath(runDirectory), "utf8")
17155
17293
  );
17156
17294
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
17157
17295
  return void 0;
@@ -17184,7 +17322,7 @@ async function writeRoleRunState(runDirectory, record4) {
17184
17322
  async function readRoleRunState(runDirectory) {
17185
17323
  try {
17186
17324
  const raw = JSON.parse(
17187
- await readFile6(join7(runDirectory, RUN_STATE_FILE), "utf8")
17325
+ await readFile7(join7(runDirectory, RUN_STATE_FILE), "utf8")
17188
17326
  );
17189
17327
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
17190
17328
  return void 0;
@@ -17308,7 +17446,7 @@ async function acquireRunWriterLease(runDirectory) {
17308
17446
  `, "utf8");
17309
17447
  } catch (error) {
17310
17448
  await handle.close().catch(() => void 0);
17311
- await unlink(lockPath).catch(() => void 0);
17449
+ await unlink2(lockPath).catch(() => void 0);
17312
17450
  throw error;
17313
17451
  }
17314
17452
  let released = false;
@@ -17318,7 +17456,7 @@ async function acquireRunWriterLease(runDirectory) {
17318
17456
  if (released) return;
17319
17457
  released = true;
17320
17458
  await handle.close().catch(() => void 0);
17321
- await unlink(lockPath).catch(() => void 0);
17459
+ await unlink2(lockPath).catch(() => void 0);
17322
17460
  }
17323
17461
  };
17324
17462
  } catch (error) {
@@ -17387,7 +17525,7 @@ async function loadResumableRunRecord(home, runId) {
17387
17525
  let derived;
17388
17526
  try {
17389
17527
  const raw = JSON.parse(
17390
- await readFile6(run.admittedRequestPath, "utf8")
17528
+ await readFile7(run.admittedRequestPath, "utf8")
17391
17529
  );
17392
17530
  if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
17393
17531
  const record4 = raw;
@@ -18293,7 +18431,7 @@ var init_terminal = __esm({
18293
18431
 
18294
18432
  // src/public-cli/settlement.ts
18295
18433
  import { randomUUID } from "node:crypto";
18296
- import { lstat as lstat3, mkdir as mkdir3, open as open2, readFile as readFile7, readdir as readdir3, writeFile as writeFile4 } from "node:fs/promises";
18434
+ import { lstat as lstat3, mkdir as mkdir3, open as open2, readFile as readFile8, readdir as readdir3, writeFile as writeFile4 } from "node:fs/promises";
18297
18435
  import { dirname as dirname6, join as join8 } from "node:path";
18298
18436
  function isChildDiagnosticFloodLine(line2) {
18299
18437
  if (/^at\s+/.test(line2)) return true;
@@ -18349,7 +18487,7 @@ function presentStructuralRejection(error, io) {
18349
18487
  }
18350
18488
  async function inspectJudgeSession(sessionFile) {
18351
18489
  try {
18352
- await readFile7(sessionFile, "utf8");
18490
+ await readFile8(sessionFile, "utf8");
18353
18491
  return { state: "present" };
18354
18492
  } catch (error) {
18355
18493
  if (isMissingPathError2(error)) return { state: "missing" };
@@ -18385,8 +18523,7 @@ function classifyPostAdmissionFailure(input) {
18385
18523
  return {
18386
18524
  cause: error.knownCause,
18387
18525
  diagnostic: error.message || error.name || "unrecognized exception",
18388
- identity,
18389
- ...error.details === void 0 ? {} : { details: error.details }
18526
+ identity
18390
18527
  };
18391
18528
  }
18392
18529
  if (error instanceof Error) {
@@ -18490,7 +18627,7 @@ function sessionReadFailure(error, fallbackMessage) {
18490
18627
  return failed;
18491
18628
  }
18492
18629
  async function readBoundSessionEntries(sessionFile) {
18493
- const text = await readFile7(sessionFile, "utf8");
18630
+ const text = await readFile8(sessionFile, "utf8");
18494
18631
  const entries = [];
18495
18632
  for (const line2 of text.trim().split("\n").filter(Boolean)) {
18496
18633
  try {
@@ -18678,6 +18815,19 @@ function typedFailedTerminatingToolKnownFailure(entries) {
18678
18815
  }
18679
18816
  async function resolveAuditedRunnerKnownFailure(input) {
18680
18817
  if (input.runner !== void 0) return input.runner;
18818
+ if (input.runDirectory !== void 0) {
18819
+ try {
18820
+ const rejection = await readReviewerDispatchRejection(input.runDirectory);
18821
+ if (rejection !== void 0) return rejection;
18822
+ } catch (error) {
18823
+ const failure = error instanceof Error ? error : new Error(String(error));
18824
+ return {
18825
+ cause: "activation",
18826
+ identity: thrownIdentity(failure),
18827
+ diagnostic: failure.message || failure.name
18828
+ };
18829
+ }
18830
+ }
18681
18831
  try {
18682
18832
  const auditorFailure = await readBoundAuditorKnownFailure(input.sessionFile);
18683
18833
  if (auditorFailure !== void 0) return auditorFailure;
@@ -22897,6 +23047,7 @@ async function dispatchAdmittedReviewer(input) {
22897
23047
  }
22898
23048
  await markRunRunning(admitted.runDirectory);
22899
23049
  await clearTypedProviderHttpObservation(admitted.runDirectory);
23050
+ await clearReviewerDispatchRejection(admitted.runDirectory);
22900
23051
  const childEnv = {
22901
23052
  ...process.env,
22902
23053
  HOME: env.home,
@@ -22991,7 +23142,8 @@ async function dispatchAdmittedReviewer(input) {
22991
23142
  const knownFailure = await resolveAuditedRunnerKnownFailure({
22992
23143
  runner: result2.knownFailure,
22993
23144
  sessionFile: admitted.sessionFile,
22994
- credential: credentialFailure
23145
+ credential: credentialFailure,
23146
+ runDirectory: admitted.runDirectory
22995
23147
  });
22996
23148
  return await presentControlledFailure7(
22997
23149
  admitted,
@@ -28,11 +28,5 @@ export function immutableReviewerRefs(refs) {
28
28
  }
29
29
  export function sameReviewerPinnedTarget(actual, expected) {
30
30
  return actual.repositoryRoot === expected.repositoryRoot && actual.objectFormat === expected.objectFormat &&
31
- actual.targetHead === expected.targetHead && sameReviewerRefs(actual.refs, expected.refs);
32
- }
33
- export function sameReviewerRefs(actual, expected) {
34
- const actualEntries = Object.entries(actual).sort(([left], [right]) => left.localeCompare(right));
35
- const expectedEntries = Object.entries(expected).sort(([left], [right]) => left.localeCompare(right));
36
- return actualEntries.length === expectedEntries.length && actualEntries.every(([name, value], index) => name === expectedEntries[index]?.[0] && value.objectId === expectedEntries[index]?.[1].objectId &&
37
- value.peeledCommitId === expectedEntries[index]?.[1].peeledCommitId);
31
+ actual.targetHead === expected.targetHead;
38
32
  }
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
2
2
  import { mkdtemp, rm } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
- import { parseReviewerRefSnapshot, reviewerRefSnapshotArgs, sameReviewerPinnedTarget, sameReviewerRefs } from "./reviewer-git-snapshot.js";
5
+ import { sameReviewerPinnedTarget } from "./reviewer-git-snapshot.js";
6
6
  export class ReviewerProcessError extends Error {
7
7
  command;
8
8
  args;
@@ -44,20 +44,13 @@ async function runCommand(command, args, options = {}) {
44
44
  });
45
45
  }
46
46
  async function git(cwd, args, signal, allowedCodes) { return runCommand("git", ["-C", cwd, ...args], { ...(signal === undefined ? {} : { signal }), ...(allowedCodes === undefined ? {} : { allowedCodes }) }); }
47
- async function readRefs(cwd, signal) { return parseReviewerRefSnapshot((await git(cwd, reviewerRefSnapshotArgs(), signal)).stdout.trim()); }
48
47
  async function verifySnapshot(cwd, snapshot, signal) {
49
48
  if ((await git(cwd, ["rev-parse", "--show-object-format"], signal)).stdout.trim() !== snapshot.objectFormat)
50
49
  throw new Error("Review clone object format does not match the pinned session snapshot");
51
50
  const head = (await git(cwd, ["rev-parse", "HEAD^{commit}"], signal)).stdout.trim();
52
51
  if (head !== snapshot.targetHead)
53
52
  throw new Error(`Review clone target mismatch: expected ${snapshot.targetHead}, got ${head}`);
54
- if (!sameReviewerRefs(await readRefs(cwd, signal), snapshot.refs))
55
- throw new Error("Review clone ref map does not match the pinned session snapshot");
56
- for (const entry of Object.values(snapshot.refs)) {
57
- await git(cwd, ["cat-file", "-e", `${entry.objectId}^{object}`], signal);
58
- if (entry.peeledCommitId !== null)
59
- await git(cwd, ["cat-file", "-e", `${entry.peeledCommitId}^{commit}`], signal);
60
- }
53
+ await git(cwd, ["cat-file", "-e", `${snapshot.targetHead}^{commit}`], signal);
61
54
  }
62
55
  function workspaceError(error, failure, disposition, target) {
63
56
  const wrapped = error instanceof Error ? error : new Error(String(error), { cause: error });
@@ -68,31 +61,20 @@ async function prepareSnapshot(accepted, signal, dependencies) {
68
61
  try {
69
62
  dependencies.fault?.("snapshot.head");
70
63
  const objectFormat = (await git(accepted.repositoryRoot, ["rev-parse", "--show-object-format"], signal)).stdout.trim();
71
- if (objectFormat !== accepted.objectFormat)
72
- throw new Error("Accepted Reviewer object format no longer matches the repository");
73
64
  const targetHead = (await git(accepted.repositoryRoot, ["rev-parse", "HEAD^{commit}"], signal)).stdout.trim();
74
- dependencies.fault?.("snapshot.refs");
75
- const refs = await readRefs(accepted.repositoryRoot, signal);
76
- if (!sameReviewerPinnedTarget({ repositoryRoot: accepted.repositoryRoot, objectFormat: accepted.objectFormat, targetHead, refs }, accepted))
77
- throw new Error("Accepted Reviewer target/ref identity no longer matches the repository");
65
+ if (!sameReviewerPinnedTarget({ repositoryRoot: accepted.repositoryRoot, objectFormat: objectFormat, targetHead }, accepted))
66
+ throw new Error("Accepted Reviewer target identity no longer matches the repository");
67
+ await git(accepted.repositoryRoot, ["cat-file", "-e", `${targetHead}^{commit}`], signal);
78
68
  dependencies.fault?.("mirror.before-create");
79
69
  mirrorRoot = await mkdtemp(join(tmpdir(), "ak-reviewer-snapshot-"));
80
70
  const mirrorPath = join(mirrorRoot, "repository.git");
81
71
  dependencies.fault?.("mirror.create");
82
- await runCommand("git", ["clone", "--mirror", "--no-hardlinks", accepted.repositoryRoot, mirrorPath], signal === undefined ? {} : { signal });
83
- const present = await git(mirrorPath, ["cat-file", "-e", `${targetHead}^{commit}`], signal, [0, 1, 128]);
84
- if (present.code !== 0) {
85
- await git(mirrorPath, ["fetch", "--no-tags", accepted.repositoryRoot, targetHead], signal);
86
- await git(mirrorPath, ["update-ref", "refs/ak-reviewer/target", targetHead], signal);
87
- }
72
+ await runCommand("git", ["init", "--bare", `--object-format=${accepted.objectFormat}`, mirrorPath], signal === undefined ? {} : { signal });
73
+ await git(mirrorPath, ["fetch", "--no-tags", accepted.repositoryRoot, targetHead], signal);
74
+ await git(mirrorPath, ["update-ref", "refs/ak-reviewer/target", targetHead], signal);
88
75
  dependencies.fault?.("mirror.verify");
89
- if (!sameReviewerRefs(await readRefs(mirrorPath, signal), refs))
90
- throw new Error("Bare review mirror ref map changed while the snapshot was prepared");
91
- const objects = Object.values(refs).flatMap(e => e.peeledCommitId === null ? [e.objectId] : [e.objectId, e.peeledCommitId]);
92
- for (const object of new Set([targetHead, ...objects]))
93
- await git(mirrorPath, ["cat-file", "-e", `${object}^{object}`], signal);
94
- await git(mirrorPath, ["config", "--remove-section", "remote.origin"], signal, [0, 5, 128]);
95
- return { ...accepted, targetHead, refs, mirrorRoot, mirrorPath };
76
+ await git(mirrorPath, ["cat-file", "-e", `${targetHead}^{commit}`], signal);
77
+ return { ...accepted, targetHead, mirrorRoot, mirrorPath };
96
78
  }
97
79
  catch (error) {
98
80
  throw workspaceError(error, "snapshot", mirrorRoot === undefined ? "not-created" : { retained: mirrorRoot }, accepted);
@@ -107,8 +89,7 @@ async function prepareClone(snapshot, signal, dependencies) {
107
89
  dependencies.fault?.("workspace.init");
108
90
  await git(workspace, ["init", `--object-format=${snapshot.objectFormat}`, "--initial-branch=ak-reviewer-unborn"], signal);
109
91
  dependencies.fault?.("workspace.fetch");
110
- await git(workspace, ["fetch", "--no-tags", "--force", "--update-shallow", snapshot.mirrorPath, snapshot.targetHead, "+refs/heads/*:refs/heads/*", "+refs/tags/*:refs/tags/*", "+refs/remotes/*:refs/remotes/*"], signal);
111
- await git(workspace, ["config", "--remove-section", "remote.origin"], signal, [0, 5, 128]);
92
+ await git(workspace, ["fetch", "--no-tags", snapshot.mirrorPath, snapshot.targetHead], signal);
112
93
  await git(workspace, ["checkout", "--detach", snapshot.targetHead], signal);
113
94
  dependencies.fault?.("workspace.verify");
114
95
  await verifySnapshot(workspace, snapshot, signal);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.1774",
3
+ "version": "0.1.1792",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -4,8 +4,8 @@
4
4
  * this adapter (or an intentional developer `pi -e`) crosses that boundary.
5
5
  */
6
6
  import { execFile, spawn } from "node:child_process";
7
- import { constants } from "node:fs";
8
- import { access, realpath } from "node:fs/promises";
7
+ import { constants, writeFileSync } from "node:fs";
8
+ import { access, readFile, realpath, unlink } from "node:fs/promises";
9
9
  import { delimiter, isAbsolute, join, resolve } from "node:path";
10
10
  import { platform } from "node:process";
11
11
  import { promisify } from "node:util";
@@ -16,8 +16,106 @@ import {
16
16
  recordLaunchedRolePackageIdentity,
17
17
  } from "./invocation.ts";
18
18
  import { INTERNAL_ROLE_ENTRYPOINT_RELATIVE } from "./registry.ts";
19
+ import {
20
+ REVIEWER_PREFLIGHT_VIOLATIONS,
21
+ type ReviewerPreflightViolation,
22
+ } from "../reviewer-dispatch.ts";
19
23
  import type { ControlledFailureCause } from "./terminal.ts";
20
24
 
25
+ /** Durable Reviewer-rejection child→parent page under AK_ROLE_RUN_DIR. */
26
+ const REVIEWER_DISPATCH_REJECTION_FILE = "typed-known-failure.json";
27
+
28
+ function isReviewerPreflightViolation(value: unknown): value is ReviewerPreflightViolation {
29
+ return (
30
+ typeof value === "string" &&
31
+ (REVIEWER_PREFLIGHT_VIOLATIONS as readonly string[]).includes(value)
32
+ );
33
+ }
34
+
35
+ function reviewerDispatchRejectionPath(runDirectory: string): string {
36
+ return join(runDirectory, REVIEWER_DISPATCH_REJECTION_FILE);
37
+ }
38
+
39
+ /**
40
+ * Clear any prior attempt's Reviewer rejection page so resume/retry cannot
41
+ * inherit a stale knownFailure.details.
42
+ */
43
+ export async function clearReviewerDispatchRejection(runDirectory: string): Promise<void> {
44
+ try {
45
+ await unlink(reviewerDispatchRejectionPath(runDirectory));
46
+ } catch (error) {
47
+ if (
48
+ error instanceof Error &&
49
+ "code" in error &&
50
+ (error as { code?: unknown }).code === "ENOENT"
51
+ ) {
52
+ return;
53
+ }
54
+ throw error;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Synchronous durable write for Reviewer dispatch rejection (child process exit is sync).
60
+ * Parent public CLI recovers via readReviewerDispatchRejection into knownFailure.
61
+ */
62
+ export function recordReviewerDispatchRejectionSync(
63
+ runDirectory: string,
64
+ rejection: Readonly<{
65
+ diagnostic: string;
66
+ violations: readonly ReviewerPreflightViolation[];
67
+ }>,
68
+ ): void {
69
+ writeFileSync(
70
+ reviewerDispatchRejectionPath(runDirectory),
71
+ `${JSON.stringify(rejection)}\n`,
72
+ "utf8",
73
+ );
74
+ }
75
+
76
+ /** Recover a child-written Reviewer dispatch rejection through a fixed mapping. */
77
+ export async function readReviewerDispatchRejection(
78
+ runDirectory: string,
79
+ ): Promise<ExplicitInternalKnownFailure | undefined> {
80
+ let raw: string;
81
+ try {
82
+ raw = await readFile(reviewerDispatchRejectionPath(runDirectory), "utf8");
83
+ } catch (error) {
84
+ if (
85
+ error instanceof Error &&
86
+ "code" in error &&
87
+ (error as { code?: unknown }).code === "ENOENT"
88
+ ) {
89
+ return undefined;
90
+ }
91
+ throw error;
92
+ }
93
+ const parsed: unknown = JSON.parse(raw);
94
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
95
+ const error = new Error("Reviewer dispatch rejection page must be a JSON object");
96
+ error.name = "ReviewerDispatchRejectionContractError";
97
+ throw error;
98
+ }
99
+ const record = parsed as Record<string, unknown>;
100
+ if (
101
+ typeof record.diagnostic !== "string" ||
102
+ record.diagnostic.trim() === "" ||
103
+ !Array.isArray(record.violations) ||
104
+ record.violations.length === 0 ||
105
+ record.violations.some((value) => !isReviewerPreflightViolation(value))
106
+ ) {
107
+ const error = new Error("Reviewer dispatch rejection page has unusable required fields");
108
+ error.name = "ReviewerDispatchRejectionContractError";
109
+ throw error;
110
+ }
111
+ return {
112
+ cause: "activation",
113
+ diagnostic: record.diagnostic,
114
+ identity: { name: "ReviewerDispatchRejectionError" },
115
+ details: { violations: Object.freeze([...record.violations]) },
116
+ };
117
+ }
118
+
21
119
  export function resolveInternalRoleEntrypoint(packageRoot: string): string {
22
120
  return join(packageRoot, INTERNAL_ROLE_ENTRYPOINT_RELATIVE);
23
121
  }
@@ -14,6 +14,7 @@ import {
14
14
  type PackagedMethodSkillProvenance,
15
15
  } from "../package-resources/method-skill.ts";
16
16
  import {
17
+ clearReviewerDispatchRejection,
17
18
  runExplicitInternalActivation,
18
19
  type ExplicitInternalKnownFailure,
19
20
  type ExplicitInternalPiRunner,
@@ -267,6 +268,7 @@ async function dispatchAdmittedReviewer(input: {
267
268
  }
268
269
  await markRunRunning(admitted.runDirectory);
269
270
  await clearTypedProviderHttpObservation(admitted.runDirectory);
271
+ await clearReviewerDispatchRejection(admitted.runDirectory);
270
272
 
271
273
  const childEnv: NodeJS.ProcessEnv = {
272
274
  ...process.env,
@@ -369,6 +371,7 @@ async function dispatchAdmittedReviewer(input: {
369
371
  runner: result.knownFailure,
370
372
  sessionFile: admitted.sessionFile,
371
373
  credential: credentialFailure,
374
+ runDirectory: admitted.runDirectory,
372
375
  });
373
376
  return await presentControlledFailure(
374
377
  admitted,
@@ -12,7 +12,7 @@ import { AUDITOR_SOUL_ROLES } from "../auditor-soul.ts";
12
12
  import { DOCTOR_AUDIT_TOOL_NAME } from "../doctor-auditor.ts";
13
13
  import { JUDGE_AUDIT_TOOL_NAME } from "../judge-auditor.ts";
14
14
  import { REVIEWER_AUDIT_TOOL_NAME } from "../reviewer-auditor.ts";
15
- import { knownFailureFromProviderStop, type ExplicitInternalKnownFailure } from "./explicit-internal.ts";
15
+ import { knownFailureFromProviderStop, type ExplicitInternalKnownFailure, readReviewerDispatchRejection } from "./explicit-internal.ts";
16
16
  import {
17
17
  AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE,
18
18
  AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE,
@@ -338,7 +338,6 @@ export function classifyPostAdmissionFailure(input: {
338
338
  cause: error.knownCause,
339
339
  diagnostic: error.message || error.name || "unrecognized exception",
340
340
  identity,
341
- ...(error.details === undefined ? {} : { details: error.details }),
342
341
  };
343
342
  }
344
343
  if (error instanceof Error) {
@@ -783,8 +782,23 @@ export async function resolveAuditedRunnerKnownFailure(input: {
783
782
  runner: ExplicitInternalKnownFailure | undefined;
784
783
  sessionFile: string;
785
784
  credential: ExplicitInternalKnownFailure | undefined;
785
+ /** Reviewer only: recover child-written rejection page into knownFailure.details. */
786
+ runDirectory?: string;
786
787
  }): Promise<ExplicitInternalKnownFailure | undefined> {
787
788
  if (input.runner !== undefined) return input.runner;
789
+ if (input.runDirectory !== undefined) {
790
+ try {
791
+ const rejection = await readReviewerDispatchRejection(input.runDirectory);
792
+ if (rejection !== undefined) return rejection;
793
+ } catch (error) {
794
+ const failure = error instanceof Error ? error : new Error(String(error));
795
+ return {
796
+ cause: "activation",
797
+ identity: thrownIdentity(failure),
798
+ diagnostic: failure.message || failure.name,
799
+ };
800
+ }
801
+ }
788
802
  // Bound auditor evidence outranks a parent failure that the auditor path itself
789
803
  // caused (retention EISDIR race). A typed terminating-tool host failure is next:
790
804
  // it outranks provider/credential and nonzero fallbacks, but not its recorded cause.
@@ -37,17 +37,9 @@ export function immutableReviewerRefs(refs: ReviewerRefMap): ReviewerRefMap {
37
37
  }
38
38
 
39
39
  export function sameReviewerPinnedTarget(
40
- actual: Readonly<{ repositoryRoot: string; objectFormat: "sha1" | "sha256"; targetHead: string; refs: ReviewerRefMap }>,
41
- expected: Readonly<{ repositoryRoot: string; objectFormat: "sha1" | "sha256"; targetHead: string; refs: ReviewerRefMap }>,
40
+ actual: Readonly<{ repositoryRoot: string; objectFormat: "sha1" | "sha256"; targetHead: string; refs?: ReviewerRefMap }>,
41
+ expected: Readonly<{ repositoryRoot: string; objectFormat: "sha1" | "sha256"; targetHead: string; refs?: ReviewerRefMap }>,
42
42
  ): boolean {
43
43
  return actual.repositoryRoot === expected.repositoryRoot && actual.objectFormat === expected.objectFormat &&
44
- actual.targetHead === expected.targetHead && sameReviewerRefs(actual.refs, expected.refs);
45
- }
46
-
47
- export function sameReviewerRefs(actual: ReviewerRefMap, expected: ReviewerRefMap): boolean {
48
- const actualEntries = Object.entries(actual).sort(([left], [right]) => left.localeCompare(right));
49
- const expectedEntries = Object.entries(expected).sort(([left], [right]) => left.localeCompare(right));
50
- return actualEntries.length === expectedEntries.length && actualEntries.every(([name, value], index) =>
51
- name === expectedEntries[index]?.[0] && value.objectId === expectedEntries[index]?.[1].objectId &&
52
- value.peeledCommitId === expectedEntries[index]?.[1].peeledCommitId);
44
+ actual.targetHead === expected.targetHead;
53
45
  }
@@ -3,11 +3,11 @@ import { mkdtemp, rm } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
 
6
- import { parseReviewerRefSnapshot, reviewerRefSnapshotArgs, sameReviewerPinnedTarget, sameReviewerRefs, type ReviewerRefEntry } from "./reviewer-git-snapshot.ts";
6
+ import { sameReviewerPinnedTarget } from "./reviewer-git-snapshot.ts";
7
7
  import type { ReviewerTargetSnapshot, ReviewerWorkspaceDisposition } from "./reviewer-execution-ledger.ts";
8
8
 
9
9
  export type ReviewerWorkspaceFaultPoint =
10
- | "snapshot.head" | "snapshot.refs"
10
+ | "snapshot.head"
11
11
  | "mirror.before-create" | "mirror.create" | "mirror.verify"
12
12
  | "workspace.before-create" | "workspace.init" | "workspace.fetch" | "workspace.verify";
13
13
  export type ReviewerWorkspaceDependencies = Readonly<{ fault?(operation: ReviewerWorkspaceFaultPoint): void }>;
@@ -40,13 +40,11 @@ async function runCommand(command: string, args: string[], options: { cwd?: stri
40
40
  });
41
41
  }
42
42
  async function git(cwd: string, args: string[], signal?: AbortSignal, allowedCodes?: readonly number[]) { return runCommand("git", ["-C", cwd, ...args], { ...(signal === undefined ? {} : { signal }), ...(allowedCodes === undefined ? {} : { allowedCodes }) }); }
43
- async function readRefs(cwd: string, signal?: AbortSignal): Promise<Record<string, ReviewerRefEntry>> { return parseReviewerRefSnapshot((await git(cwd, reviewerRefSnapshotArgs(), signal)).stdout.trim()); }
44
43
  async function verifySnapshot(cwd: string, snapshot: ReviewerTargetSnapshot, signal?: AbortSignal) {
45
44
  if ((await git(cwd, ["rev-parse", "--show-object-format"], signal)).stdout.trim() !== snapshot.objectFormat) throw new Error("Review clone object format does not match the pinned session snapshot");
46
45
  const head = (await git(cwd, ["rev-parse", "HEAD^{commit}"], signal)).stdout.trim();
47
46
  if (head !== snapshot.targetHead) throw new Error(`Review clone target mismatch: expected ${snapshot.targetHead}, got ${head}`);
48
- if (!sameReviewerRefs(await readRefs(cwd, signal), snapshot.refs)) throw new Error("Review clone ref map does not match the pinned session snapshot");
49
- for (const entry of Object.values(snapshot.refs)) { await git(cwd, ["cat-file", "-e", `${entry.objectId}^{object}`], signal); if (entry.peeledCommitId !== null) await git(cwd, ["cat-file", "-e", `${entry.peeledCommitId}^{commit}`], signal); }
47
+ await git(cwd, ["cat-file", "-e", `${snapshot.targetHead}^{commit}`], signal);
50
48
  }
51
49
  function workspaceError(error: unknown, failure: "snapshot" | "workspace", disposition: ReviewerWorkspaceDisposition, target: ReviewerTargetSnapshot): ReviewerWorkspaceError {
52
50
  const wrapped = error instanceof Error ? error : new Error(String(error), { cause: error });
@@ -57,19 +55,15 @@ async function prepareSnapshot(accepted: ReviewerTargetSnapshot, signal: AbortSi
57
55
  try {
58
56
  dependencies.fault?.("snapshot.head");
59
57
  const objectFormat = (await git(accepted.repositoryRoot, ["rev-parse", "--show-object-format"], signal)).stdout.trim();
60
- if (objectFormat !== accepted.objectFormat) throw new Error("Accepted Reviewer object format no longer matches the repository");
61
58
  const targetHead = (await git(accepted.repositoryRoot, ["rev-parse", "HEAD^{commit}"], signal)).stdout.trim();
62
- dependencies.fault?.("snapshot.refs"); const refs = await readRefs(accepted.repositoryRoot, signal);
63
- if (!sameReviewerPinnedTarget({ repositoryRoot: accepted.repositoryRoot, objectFormat: accepted.objectFormat, targetHead, refs }, accepted)) throw new Error("Accepted Reviewer target/ref identity no longer matches the repository");
59
+ if (!sameReviewerPinnedTarget({ repositoryRoot: accepted.repositoryRoot, objectFormat: objectFormat as "sha1" | "sha256", targetHead }, accepted)) throw new Error("Accepted Reviewer target identity no longer matches the repository");
60
+ await git(accepted.repositoryRoot, ["cat-file", "-e", `${targetHead}^{commit}`], signal);
64
61
  dependencies.fault?.("mirror.before-create"); mirrorRoot = await mkdtemp(join(tmpdir(), "ak-reviewer-snapshot-")); const mirrorPath = join(mirrorRoot, "repository.git");
65
- dependencies.fault?.("mirror.create"); await runCommand("git", ["clone", "--mirror", "--no-hardlinks", accepted.repositoryRoot, mirrorPath], signal === undefined ? {} : { signal });
66
- const present = await git(mirrorPath, ["cat-file", "-e", `${targetHead}^{commit}`], signal, [0, 1, 128]);
67
- if (present.code !== 0) { await git(mirrorPath, ["fetch", "--no-tags", accepted.repositoryRoot, targetHead], signal); await git(mirrorPath, ["update-ref", "refs/ak-reviewer/target", targetHead], signal); }
68
- dependencies.fault?.("mirror.verify"); if (!sameReviewerRefs(await readRefs(mirrorPath, signal), refs)) throw new Error("Bare review mirror ref map changed while the snapshot was prepared");
69
- const objects = Object.values(refs).flatMap(e => e.peeledCommitId === null ? [e.objectId] : [e.objectId, e.peeledCommitId]);
70
- for (const object of new Set([targetHead, ...objects])) await git(mirrorPath, ["cat-file", "-e", `${object}^{object}`], signal);
71
- await git(mirrorPath, ["config", "--remove-section", "remote.origin"], signal, [0, 5, 128]);
72
- return { ...accepted, targetHead, refs, mirrorRoot, mirrorPath };
62
+ dependencies.fault?.("mirror.create"); await runCommand("git", ["init", "--bare", `--object-format=${accepted.objectFormat}`, mirrorPath], signal === undefined ? {} : { signal });
63
+ await git(mirrorPath, ["fetch", "--no-tags", accepted.repositoryRoot, targetHead], signal);
64
+ await git(mirrorPath, ["update-ref", "refs/ak-reviewer/target", targetHead], signal);
65
+ dependencies.fault?.("mirror.verify"); await git(mirrorPath, ["cat-file", "-e", `${targetHead}^{commit}`], signal);
66
+ return { ...accepted, targetHead, mirrorRoot, mirrorPath };
73
67
  } catch (error) { throw workspaceError(error, "snapshot", mirrorRoot === undefined ? "not-created" : { retained: mirrorRoot }, accepted); }
74
68
  }
75
69
  async function prepareClone(snapshot: GitSnapshot, signal: AbortSignal | undefined, dependencies: ReviewerWorkspaceDependencies): Promise<string> {
@@ -77,8 +71,8 @@ async function prepareClone(snapshot: GitSnapshot, signal: AbortSignal | undefin
77
71
  try {
78
72
  dependencies.fault?.("workspace.before-create"); workspace = await mkdtemp(join(tmpdir(), "ak-reviewer-leg-"));
79
73
  dependencies.fault?.("workspace.init"); await git(workspace, ["init", `--object-format=${snapshot.objectFormat}`, "--initial-branch=ak-reviewer-unborn"], signal);
80
- dependencies.fault?.("workspace.fetch"); await git(workspace, ["fetch", "--no-tags", "--force", "--update-shallow", snapshot.mirrorPath, snapshot.targetHead, "+refs/heads/*:refs/heads/*", "+refs/tags/*:refs/tags/*", "+refs/remotes/*:refs/remotes/*"], signal);
81
- await git(workspace, ["config", "--remove-section", "remote.origin"], signal, [0, 5, 128]); await git(workspace, ["checkout", "--detach", snapshot.targetHead], signal);
74
+ dependencies.fault?.("workspace.fetch"); await git(workspace, ["fetch", "--no-tags", snapshot.mirrorPath, snapshot.targetHead], signal);
75
+ await git(workspace, ["checkout", "--detach", snapshot.targetHead], signal);
82
76
  dependencies.fault?.("workspace.verify"); await verifySnapshot(workspace, snapshot, signal); return workspace;
83
77
  } catch (error) { throw workspaceError(error, "workspace", workspace === undefined ? "not-created" : { retained: workspace }, target); }
84
78
  }
@@ -44,6 +44,10 @@ import {
44
44
  resolveLifecycleInvocationPrincipal,
45
45
  } from "./navigator-invocation-identity.ts";
46
46
  import { recordTypedProviderHttpStatus } from "./public-cli/run-lifecycle.ts";
47
+ import {
48
+ ExplicitInternalActivationError,
49
+ recordReviewerDispatchRejectionSync,
50
+ } from "./public-cli/explicit-internal.ts";
47
51
  import { NAVIGATOR_POST_ROLE_GRACE_MS, raceNavigatorGrace } from "./public-cli/settlement.ts";
48
52
  import { PACKAGED_ROLE_REGISTRY, packagedRoleMetadata, packagedRoleOutputTool, packagedRolePhaseFlag, type PackagedRole } from "./packaged-role-registry.ts";
49
53
  import { isAuditEscalationProjection } from "./audit-escalation.ts";
@@ -223,7 +227,25 @@ function activationStage(role: PackagedRole, runtime: ActivationRuntime): { id:
223
227
  case "reviewer": return { id: "load-install-and-dispatch", run: async () => {
224
228
  const activation = await runtime.reviewer.activate(runtime.context);
225
229
  const result = await activation.dispatcher.dispatch(activation.fixedBaseRevision, { context: runtime.context });
226
- if (result.status !== "accepted") throw new Error(`Fixed Reviewer dispatch was not accepted: ${result.status}`);
230
+ if (result.status !== "accepted") {
231
+ const rejection = new ExplicitInternalActivationError(
232
+ `Fixed Reviewer dispatch was not accepted: ${result.status}: ${result.diagnostic}`,
233
+ {
234
+ knownCause: "activation",
235
+ name: "ReviewerDispatchRejectionError",
236
+ },
237
+ );
238
+ // Pi stderr cannot carry the structured violations. Do not mask a durable
239
+ // write failure: its infrastructure cause is truer than the unwritten page.
240
+ const runDir = process.env.AK_ROLE_RUN_DIR;
241
+ if (typeof runDir === "string" && runDir.trim() !== "") {
242
+ recordReviewerDispatchRejectionSync(runDir, {
243
+ diagnostic: rejection.message,
244
+ violations: result.violations,
245
+ });
246
+ }
247
+ throw rejection;
248
+ }
227
249
  } };
228
250
  case "collector": return { id: "load-and-install", run: async () => runtime.collector.activate(runtime.context, runtime.event) };
229
251
  case "doctor": return { id: "load-and-install", run: async () => runtime.doctor.activate() };