@akagilnc/pi-workflow-roles 0.1.5079 → 0.1.5122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/acp-host/production-host.js +545 -334
  2. package/dist/atomic-write.js +13 -19
  3. package/dist/auditor-soul.js +2 -1
  4. package/dist/doctor-contracts.js +1 -1
  5. package/dist/gatekeeper-role.js +2 -16
  6. package/dist/headless-host/production-host.js +577 -366
  7. package/dist/merger-contracts.js +1 -1
  8. package/dist/migrate-book-topology.js +18 -14
  9. package/dist/navigator-attendance.js +28 -143
  10. package/dist/navigator-invocation-identity.js +2 -0
  11. package/dist/navigator-public-session.js +13 -1
  12. package/dist/navigator-work-base.js +113 -0
  13. package/dist/notary-contracts.js +0 -2
  14. package/dist/package-contracts/fixer-output.js +1 -1
  15. package/dist/package-contracts/gatekeeper-output.js +1 -1
  16. package/dist/package-contracts/navigator-output.js +1 -1
  17. package/dist/package-contracts/terminating-infrastructure.js +3 -3
  18. package/dist/packaged-role-registry.js +9 -5
  19. package/dist/public-cli/cli.js +24 -1
  20. package/dist/public-cli/countersign-run.js +1 -1
  21. package/dist/public-cli/instruction-seat-run.js +8 -6
  22. package/dist/public-cli/invocation.js +6 -3
  23. package/dist/public-cli/main.js +6711 -6523
  24. package/dist/public-cli/post-admission.js +125 -26
  25. package/dist/public-cli/settlement.js +48 -5
  26. package/dist/receipt-delivery-policy.js +9 -2
  27. package/dist/review-submission.js +3 -3
  28. package/docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md +9 -0
  29. package/extensions/role-runtime.ts +0 -27
  30. package/package.json +2 -1
  31. package/resources/836-deleted-machine-instruction-inventory.md +3 -3
  32. package/resources/collector-bot-handbook.md +4 -0
  33. package/resources/navigator-route-playbook.md +4 -6
  34. package/scripts/build-package.mjs +4 -0
  35. package/souls/coder-output-guide.md +1 -0
  36. package/src/auditor-soul.ts +2 -1
  37. package/src/collector-role.ts +6 -7
  38. package/src/collector-tool-schemas.ts +5 -5
  39. package/src/diarist-role.ts +2 -2
  40. package/src/doctor-contracts.ts +1 -1
  41. package/src/gatekeeper-role.ts +1 -18
  42. package/src/gleaner-left-role.ts +2 -2
  43. package/src/inspector-role.ts +1 -1
  44. package/src/judge-role.ts +1 -1
  45. package/src/merger-contracts.ts +1 -1
  46. package/src/navigator-attendance.ts +38 -164
  47. package/src/navigator-invocation-identity.ts +2 -0
  48. package/src/navigator-public-session.ts +17 -1
  49. package/src/navigator-role.ts +4 -0
  50. package/src/navigator-session-contracts.ts +2 -0
  51. package/src/navigator-work-base.ts +123 -0
  52. package/src/notary-contracts.ts +0 -4
  53. package/src/package-contracts/fixer-output.ts +1 -1
  54. package/src/package-contracts/gatekeeper-output.ts +3 -3
  55. package/src/package-contracts/navigator-output.ts +1 -1
  56. package/src/package-contracts/terminating-infrastructure.ts +3 -6
  57. package/src/packaged-role-registry.ts +5 -1
  58. package/src/public-cli/cli-io.ts +2 -0
  59. package/src/public-cli/cli.ts +38 -4
  60. package/src/public-cli/countersign-run.ts +1 -1
  61. package/src/public-cli/instruction-seat-run.ts +10 -6
  62. package/src/public-cli/invocation.ts +12 -4
  63. package/src/public-cli/post-admission.ts +189 -34
  64. package/src/public-cli/settlement.ts +58 -4
  65. package/src/receipt-delivery-policy.ts +9 -1
  66. package/src/review-submission.ts +3 -3
  67. package/src/reviewer-role.ts +3 -3
  68. package/src/role-runtime-dependencies.ts +1 -5
  69. package/src/role-runtime.ts +52 -38
  70. package/src/secretariat-role.ts +3 -3
  71. package/src/worker-role.ts +3 -3
@@ -8,13 +8,13 @@
8
8
  import { randomUUID } from "node:crypto";
9
9
  import { writeFile } from "node:fs/promises";
10
10
  import { isAbsolute, join, resolve } from "node:path";
11
- import { buildAutoResumeContinuationPrompt, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
11
+ import { buildAutoResumeContinuationPrompt, findRunDirectoryById, readRoleRunState, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
12
12
  import { CliUsageError } from "./cli-errors.js";
13
13
  import { bindAdmittedTicketNumber, buildInstructionTransportPrompt, freezeAttachmentsIntoRun, relocateAdmittedRunToTicket, } from "./invocation.js";
14
14
  import { readRecordedSubmissionRows } from "../submission-ledger.js";
15
15
  import { pathContainedIn } from "../activation-ledger-topology.js";
16
16
  import { pickEngineAxis } from "../package-resources/engine-material.js";
17
- import { readStoredHostSessionId, resolveHostAwareSessionAvailability } from "../session-identity.js";
17
+ import { readStoredHostSessionId, resolveHostAwareSessionAvailability, } from "../session-identity.js";
18
18
  import { rewriteRunDirectoryPathValue } from "../role-run-relocation.js";
19
19
  import { isOfficerReviewSeat } from "../packaged-role-registry.js";
20
20
  import { deliverCaseDossierAsAttachment } from "./case-dossier-delivery.js";
@@ -39,7 +39,7 @@ import { projectHostTransitionPriorNative } from "../host-transition-prior-nativ
39
39
  import { clearCurrentCourt, clearTypedProviderHttpObservation, describeErrorIdentity, markRunRunning, readCurrentCourt, recordCurrentCourt, renderResumeCommand, } from "./run-lifecycle.js";
40
40
  import { processCancelDiagnostic, processCancelSignalName, } from "./process-cancel.js";
41
41
  import { homeFromRunDirectory } from "../activation-ledger-topology.js";
42
- import { attemptProducedFreshSubmission, classifyPostAdmissionFailure, controlledFailureInputFromResolution, exitCodeForTerminalOutcome, explicitInternalKnownFailureClassificationInput, formatCliDiagnostic, formatTerminalResult, inspectJudgeSession, isLawfulTypedTerminalOutcome, presentFailureTerminal, presentStructuralRejection, resolveAuditedRunnerFailureResolution, resolveControlledFailureResumeObservation, settleFailureTerminalResult, settleHostEndedNoReceipt, attachRecordedSubmissions, } from "./settlement.js";
42
+ import { attemptProducedFreshSubmission, classifyPostAdmissionFailure, controlledFailureInputFromResolution, exitCodeForTerminalOutcome, explicitInternalKnownFailureClassificationInput, formatCliDiagnostic, formatErrorCauseDetail, formatTerminalResult, inspectJudgeSession, isLawfulTypedTerminalOutcome, presentFailureTerminal, publishedFailureErrorPath, presentStructuralRejection, resolveAuditedRunnerFailureResolution, resolveControlledFailureResumeObservation, settleFailureTerminalResult, settleHostEndedNoReceipt, attachRecordedSubmissions, rewritePublishedFailureErrorPath, } from "./settlement.js";
43
43
  import {} from "./terminal.js";
44
44
  import { ensureRealArtifactsDirectory, persistReturnedRunState, runWithAutoResumeLoop, TurnDispatchedFailure, } from "./auto-resume.js";
45
45
  function projectRelocatedTurnIdentity(request, result, admitted, relocation) {
@@ -56,6 +56,7 @@ function projectRelocatedTurnIdentity(request, result, admitted, relocation) {
56
56
  }
57
57
  }
58
58
  if (result.terminal !== undefined) {
59
+ rewritePublishedFailureErrorPath(result.terminal, relocation.oldRunDirectory, relocation.newRunDirectory);
59
60
  for (const artifact of result.terminal.artifacts) {
60
61
  artifact.path = rewrite(artifact.path);
61
62
  }
@@ -175,7 +176,7 @@ export async function resolveResumeMethodMaterialAdapters(input) {
175
176
  stderr: "",
176
177
  thrown: error,
177
178
  ...(input.knownCause === undefined ? {} : { knownCause: input.knownCause }),
178
- }, input.emptyAdapters, input.authority, input.io);
179
+ }, input.emptyAdapters, input.authority, { ...input.io, omitFailureStderrDiagnostic: true });
179
180
  return { kind: "terminal", ...terminal };
180
181
  }
181
182
  }
@@ -233,15 +234,27 @@ export async function presentControlledFailure(admitted, failureInput, adapters,
233
234
  if (persistRunState && !failureInput.skipRunStateWrite) {
234
235
  await persistReturnedRunState(admitted, authority);
235
236
  }
236
- const terminal = await attachRecordedSubmissions(admitted, await settleFailureTerminalResult(admitted, failure, authority, {
237
- ...(resumable
238
- ? { resume: { command: renderResumeCommand(admitted.runId) } }
239
- : {}),
240
- ...(failureInput.invocationScopeId === undefined ||
241
- failureInput.invocationScopeId.length === 0
242
- ? {}
243
- : { invocationScopeId: failureInput.invocationScopeId }),
244
- }));
237
+ let publishedErrorPath;
238
+ let terminal;
239
+ try {
240
+ const settled = await settleFailureTerminalResult(admitted, failure, authority, {
241
+ ...(resumable
242
+ ? { resume: { command: renderResumeCommand(admitted.runId) } }
243
+ : {}),
244
+ ...(failureInput.invocationScopeId === undefined ||
245
+ failureInput.invocationScopeId.length === 0
246
+ ? {}
247
+ : { invocationScopeId: failureInput.invocationScopeId }),
248
+ onErrorPublished: (path) => { publishedErrorPath = path; },
249
+ });
250
+ terminal = await attachRecordedSubmissions(admitted, settled);
251
+ }
252
+ catch (error) {
253
+ if (io.omitFailureStderrDiagnostic && publishedErrorPath !== undefined) {
254
+ writeResumeFailurePointer(io, publishedErrorPath);
255
+ }
256
+ throw error;
257
+ }
245
258
  presentFailureTerminal(terminal, io);
246
259
  return {
247
260
  exitCode: exitCodeForTerminalOutcome(terminal.roleOutcome),
@@ -359,8 +372,12 @@ export async function dispatchPostAdmissionTurn(input) {
359
372
  if (relocation !== undefined) {
360
373
  projectRelocatedTurnIdentity(request, result, admitted, relocation);
361
374
  }
362
- if (adapters.afterDispatch !== undefined)
363
- await adapters.afterDispatch(admitted, lease);
375
+ const afterDispatchRelocation = adapters.afterDispatch === undefined
376
+ ? undefined
377
+ : await adapters.afterDispatch(admitted, lease);
378
+ if (afterDispatchRelocation !== undefined) {
379
+ projectRelocatedTurnIdentity(request, result, admitted, afterDispatchRelocation);
380
+ }
364
381
  return result;
365
382
  }
366
383
  catch (error) {
@@ -968,16 +985,24 @@ export async function runPostAdmissionSeatResume(input) {
968
985
  loaded = await input.load(request);
969
986
  }
970
987
  catch (error) {
971
- if (error instanceof CliUsageError) {
972
- presentStructuralRejection(error, input.io);
973
- return { exitCode: 2 };
988
+ const usageError = error instanceof CliUsageError;
989
+ // Unknown usage failures retain the structural fallback. Other load
990
+ // failures keep the CLI's outer handling unless an existing run is found.
991
+ const recorded = await pointExistingRunFailure(input.env.home, request.runId, input.env.principalAuthority, input.io, error);
992
+ if (!recorded) {
993
+ if (usageError) {
994
+ presentStructuralRejection(error, input.io);
995
+ return { exitCode: 2 };
996
+ }
997
+ throw error;
974
998
  }
975
- throw error;
999
+ return { exitCode: usageError ? 2 : 1 };
976
1000
  }
977
1001
  let adapters = input.adapters;
978
1002
  if (input.afterAdmittedLoad !== undefined) {
979
1003
  const prepared = await input.afterAdmittedLoad(loaded.admitted);
980
1004
  if (prepared.kind === "terminal") {
1005
+ showResumeErrorPointer(input.io, prepared.exitCode, prepared.terminal);
981
1006
  return {
982
1007
  exitCode: prepared.exitCode,
983
1008
  admitted: prepared.admitted,
@@ -1116,26 +1141,100 @@ export async function runPostAdmissionSeatResume(input) {
1116
1141
  }),
1117
1142
  });
1118
1143
  }
1119
- return await runPostAdmissionManualResume({
1144
+ const resumed = await runPostAdmissionManualResume({
1120
1145
  admitted: loaded.admitted,
1121
1146
  env,
1122
- io: input.io,
1147
+ io: { ...input.io, omitFailureStderrDiagnostic: true },
1123
1148
  adapters,
1124
1149
  ...(input.effectiveEngine === undefined
1125
1150
  ? {}
1126
1151
  : { effectiveEngine: input.effectiveEngine }),
1127
1152
  buildRequestAfterLease,
1128
1153
  });
1154
+ showResumeErrorPointer(input.io, resumed.exitCode, resumed.terminal);
1155
+ return resumed;
1129
1156
  }
1130
1157
  catch (error) {
1131
- // Turn construction may still surface structural rejection.
1132
- if (error instanceof CliUsageError) {
1133
- presentStructuralRejection(error, input.io);
1134
- return { exitCode: 2 };
1158
+ if (error instanceof TurnDispatchedFailure)
1159
+ throw error;
1160
+ await presentResumeFailurePointer(input.io, error, (failure) => writeResumeDiagnosticFile(loaded.admitted.runDirectory, loaded.admitted.runId, failure, loaded.admitted.role));
1161
+ return { exitCode: error instanceof CliUsageError ? 2 : 1 };
1162
+ }
1163
+ }
1164
+ async function pointExistingRunFailure(home, runId, authority, io, thrown) {
1165
+ let foundRunDirectory;
1166
+ try {
1167
+ foundRunDirectory = await findRunDirectoryById(home, runId);
1168
+ }
1169
+ catch {
1170
+ return false;
1171
+ }
1172
+ if (foundRunDirectory === undefined)
1173
+ return false;
1174
+ const runDirectory = foundRunDirectory;
1175
+ let record;
1176
+ try {
1177
+ record = await readRoleRunState(runDirectory, authority);
1178
+ }
1179
+ catch {
1180
+ await presentResumeFailurePointer(io, thrown, (failure) => writeResumeDiagnosticFile(runDirectory, runId, failure));
1181
+ return true;
1182
+ }
1183
+ if (record === undefined)
1184
+ return false;
1185
+ await presentResumeFailurePointer(io, thrown, (failure) => writeResumeDiagnosticFile(runDirectory, runId, failure, record.role));
1186
+ return true;
1187
+ }
1188
+ export async function presentLocatedResumeFailure(home, runId, authority, io, thrown) {
1189
+ return await pointExistingRunFailure(home, runId, authority, io, thrown);
1190
+ }
1191
+ function writeResumeFailurePointer(io, errorPath) {
1192
+ io.stderr(formatCliDiagnostic(`续跑失败,当次错误记录:${errorPath}`));
1193
+ }
1194
+ export function showResumeErrorPointer(io, exitCode, terminal) {
1195
+ if (exitCode === 0 || terminal === undefined)
1196
+ return;
1197
+ const publishedError = terminal.artifacts.find((artifact) => artifact.kind === "error")?.path
1198
+ ?? publishedFailureErrorPath(terminal);
1199
+ if (publishedError !== undefined)
1200
+ writeResumeFailurePointer(io, publishedError);
1201
+ }
1202
+ async function presentResumeFailurePointer(io, thrown, publish) {
1203
+ const failure = classifyPostAdmissionFailure({
1204
+ timedOut: false,
1205
+ code: null,
1206
+ stderr: "",
1207
+ thrown,
1208
+ });
1209
+ try {
1210
+ writeResumeFailurePointer(io, await publish(failure));
1211
+ }
1212
+ catch (publishError) {
1213
+ const cause = {
1214
+ publishError: formatErrorCauseDetail(publishError),
1215
+ };
1216
+ if (thrown instanceof Error && thrown.cause !== undefined) {
1217
+ cause.thrownCause = formatErrorCauseDetail(thrown.cause);
1135
1218
  }
1136
- throw error;
1219
+ presentStructuralRejection({
1220
+ message: thrown instanceof Error ? thrown.message : String(thrown),
1221
+ cause,
1222
+ }, io);
1137
1223
  }
1138
1224
  }
1225
+ async function writeResumeDiagnosticFile(runDirectory, runId, failure, role) {
1226
+ const dir = await ensureRealArtifactsDirectory(runDirectory);
1227
+ const path = join(dir, `resume-diagnostic-${randomUUID()}.json`);
1228
+ await writeFile(path, `${JSON.stringify({
1229
+ runId,
1230
+ ...(role === undefined ? {} : { role }),
1231
+ diagnostic: failure.diagnostic,
1232
+ ...(failure.cause === undefined ? {} : { cause: failure.cause }),
1233
+ ...(failure.identity === undefined ? {} : { identity: failure.identity }),
1234
+ ...(failure.details === undefined ? {} : { details: failure.details }),
1235
+ }, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
1236
+ return path;
1237
+ }
1139
1238
  /**
1140
1239
  * Shared post-admission one-shot path: folds into runPostAdmissionResumable (#840 / #416).
1141
1240
  * All callable roles share the single auto-resume loop.
@@ -28,9 +28,10 @@ import { DOCTOR_CANDIDATE_ENTRY_TYPE } from "../dossier-resolution.js";
28
28
  import { REVIEWER_OUTPUT_TOOL_NAME, } from "../package-contracts/reviewer-output.js";
29
29
  import { packagedAuditToolName, packagedDurableOfficerEntry, packagedProjectsTargetBindRejection, packagedRoleAcceptedOutputTool, packagedRoleMetadata, packagedSkipsGateOnInfrastructureStage, } from "../packaged-role-registry.js";
30
30
  import { SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY, SECRETARIAT_GATE_OFFICER_ENTRY_TYPE, } from "../secretariat-contracts.js";
31
- import { classifyPackagedRoleTerminalResult, findLatestDurablePackagedRoleTerminal, hasNavigatorInfrastructureFailureBase, isAcceptedPackagedRoleTerminalResult, isReceiptSettlementBindingClear, NAVIGATOR_INVOCATION_ENTRY, parseInvocationMarkerIdentity, } from "../navigator-invocation-identity.js";
31
+ import { classifyPackagedRoleTerminalResult, findLatestDurablePackagedRoleTerminal, hasNavigatorInfrastructureFailureBase, isAcceptedPackagedRoleTerminalResult, isReceiptSettlementBindingClear, NAVIGATOR_INVOCATION_ENTRY, NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY, parseInvocationMarkerIdentity, } from "../navigator-invocation-identity.js";
32
32
  import { NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_TURN_LIMIT, noReceiptLifecycleFacts, parseNoReceiptLifecycleFacts, } from "../receipt-delivery-policy.js";
33
33
  import { roleRunArtifactsDirectory } from "../role-run-placement.js";
34
+ import { rewriteRunDirectoryPathValue } from "../role-run-relocation.js";
34
35
  import { listSeamOwnedUniqueErrorFacePaths, RUN_TERMINAL_ARTIFACT_FILES, RUN_TERMINAL_ERROR_FALLBACK_RELATIVE_PATHS, } from "../run-terminal-artifacts.js";
35
36
  import { ensureRunArtifactsDir, homeFromRunDirectory, } from "./invocation.js";
36
37
  /** Ledger reads use the run's machine home — not ambient process HOME (child write vs parent settle). */
@@ -127,7 +128,11 @@ export async function attachRecordedSubmissions(admitted, terminal, scope) {
127
128
  // pass courtAttemptId here — roleOutcome already carries this-court payloads
128
129
  // from sealedLedgerOutcome when scoped; withSubmissions keeps them.
129
130
  void scope;
130
- return withSubmissions(terminal, await recordedSubmissionPayloads(admitted, undefined));
131
+ const result = withSubmissions(terminal, await recordedSubmissionPayloads(admitted, undefined));
132
+ const errorPath = privateFailureErrorPaths.get(terminal);
133
+ if (errorPath !== undefined)
134
+ privateFailureErrorPaths.set(result, errorPath);
135
+ return result;
131
136
  }
132
137
  /**
133
138
  * Host ended with no recorded submission — lawful no_receipt (exit 0).
@@ -163,6 +168,18 @@ function coordinatesFromAdmitted(authority, admitted) {
163
168
  }
164
169
  import { coalesceSubmissionRows, exitCodeForTerminalOutcome, formatTerminalResult, isLawfulTypedTerminalOutcome, adviceNavigatorFact, } from "./terminal.js";
165
170
  export { exitCodeForTerminalOutcome, formatTerminalResult, isLawfulTypedTerminalOutcome, };
171
+ // A resumable public Terminal omits artifact paths (#108); its actual error
172
+ // publication remains available only to the CLI's resume presentation seam.
173
+ const privateFailureErrorPaths = new WeakMap();
174
+ export function publishedFailureErrorPath(terminal) {
175
+ return privateFailureErrorPaths.get(terminal);
176
+ }
177
+ export function rewritePublishedFailureErrorPath(terminal, oldRunDirectory, newRunDirectory) {
178
+ const errorPath = privateFailureErrorPaths.get(terminal);
179
+ if (errorPath !== undefined) {
180
+ privateFailureErrorPaths.set(terminal, rewriteRunDirectoryPathValue(errorPath, oldRunDirectory, newRunDirectory));
181
+ }
182
+ }
166
183
  /**
167
184
  * Host stderr as recorded — full bytes, no flood filter, no char clip (#836).
168
185
  */
@@ -1610,7 +1627,26 @@ async function withOptionalGateProjection(base, sessionDirectory, gateContext =
1610
1627
  }
1611
1628
  return attachEngineDetourToolUsage(next, sessionDirectory, gateContext);
1612
1629
  }
1630
+ function routePlaybookFailureMessage(entries) {
1631
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
1632
+ const entry = entries[i];
1633
+ if (entry?.type !== "custom" || entry.customType !== NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY)
1634
+ continue;
1635
+ const data = entry.data;
1636
+ if (!isRecord(data) || typeof data.message !== "string" || data.message.trim() === "")
1637
+ return undefined;
1638
+ return data.message;
1639
+ }
1640
+ return undefined;
1641
+ }
1613
1642
  export function extractNavigatorFact(entries) {
1643
+ const fact = extractNavigatorAttendanceFact(entries);
1644
+ if (fact.advisoryDiagnostic !== undefined)
1645
+ return fact;
1646
+ const message = routePlaybookFailureMessage(entries);
1647
+ return message === undefined ? fact : { ...fact, advisoryDiagnostic: message };
1648
+ }
1649
+ function extractNavigatorAttendanceFact(entries) {
1614
1650
  // Affirmative attendance only. Missing / uncorrelated / unparseable is never no-advice.
1615
1651
  // Minimal provenance: latest durable terminal + nearest preceding marker +
1616
1652
  // invocationId + post-terminal order. Marker↔terminal cardinality belongs to
@@ -2263,7 +2299,7 @@ async function writeFailureJsonRetainingCause(preferredCandidates, uniqueFallbac
2263
2299
  error.publicationAttempts = issues;
2264
2300
  throw error;
2265
2301
  }
2266
- export async function publishFailureArtifacts(admitted, failure, authority) {
2302
+ export async function publishFailureArtifacts(admitted, failure, authority, onErrorPublished) {
2267
2303
  const { sessionDirectory, sessionFile } = coordinatesFromAdmitted(authority, admitted);
2268
2304
  const { baseDir, attempt: baseAttempt } = await resolveFailureArtifactsBase(admitted.runDirectory);
2269
2305
  const priorIssues = baseAttempt === undefined ? [] : [baseAttempt];
@@ -2327,6 +2363,7 @@ export async function publishFailureArtifacts(admitted, failure, authority) {
2327
2363
  ...(failure.details === undefined ? {} : { details: failure.details }),
2328
2364
  };
2329
2365
  const errorWrite = await writeFailureJsonRetainingCause(errorCandidates, uniqueFallbackDirs, "error", errorPayloadBase, priorIssues);
2366
+ onErrorPublished?.(errorWrite.path);
2330
2367
  const evidencePayload = {
2331
2368
  runId: admitted.runId,
2332
2369
  sessionDirectory: sessionDirectory,
@@ -2418,7 +2455,8 @@ export async function settleFailureTerminalResult(admitted, failure, authority,
2418
2455
  // Exact-session attendance only — never infer no-advice from caller omission.
2419
2456
  const navigator = await extractNavigatorFactFromAdmittedSession(sessionFile);
2420
2457
  // Private durable artifacts retain the original diagnostic identity (including run ID).
2421
- const artifacts = await publishFailureArtifacts(admitted, failure, authority);
2458
+ const artifacts = await publishFailureArtifacts(admitted, failure, authority, options.onErrorPublished);
2459
+ const errorPath = artifacts.find((artifact) => artifact.kind === "error")?.path;
2422
2460
  const decisiveFacts = {
2423
2461
  ...(failure.cause === undefined ? {} : { cause: failure.cause }),
2424
2462
  diagnostic: failure.diagnostic,
@@ -2444,12 +2482,15 @@ export async function settleFailureTerminalResult(admitted, failure, authority,
2444
2482
  diagnostic: failure.diagnostic,
2445
2483
  decisiveFacts,
2446
2484
  };
2447
- return withOptionalGateProjection({
2485
+ const terminal = await withOptionalGateProjection({
2448
2486
  roleOutcome,
2449
2487
  navigator,
2450
2488
  artifacts: [],
2451
2489
  resume: options.resume,
2452
2490
  }, sessionDirectory, detourGateContext(admitted, options));
2491
+ if (errorPath !== undefined)
2492
+ privateFailureErrorPaths.set(terminal, errorPath);
2493
+ return terminal;
2453
2494
  }
2454
2495
  const roleOutcome = {
2455
2496
  kind: "failure",
@@ -2480,6 +2521,8 @@ export function presentFailureTerminal(terminal, io) {
2480
2521
  }
2481
2522
  io.stdout(formatTerminalResult(terminal));
2482
2523
  if (terminal.roleOutcome.kind === "failure") {
2524
+ if (io.omitFailureStderrDiagnostic)
2525
+ return;
2483
2526
  io.stderr(formatFailureStderrDiagnostic({
2484
2527
  ...(terminal.roleOutcome.cause === undefined ? {} : { cause: terminal.roleOutcome.cause }),
2485
2528
  diagnostic: terminal.roleOutcome.diagnostic,
@@ -1,5 +1,4 @@
1
1
  const RECEIPT_DELIVERY_TURN_LIMIT = 2;
2
- const RECEIPT_DELIVERY_PROMPT = "\u672C\u4F1A\u8BDD\u5C1A\u65E0\u5DF2\u63A5\u53D7\u7684 typed \u56DE\u6267\u3002";
3
2
  const NO_RECEIPT_LIFECYCLE_ENTRY_TYPE = "ak-no-receipt-lifecycle";
4
3
  function isRecord(value) {
5
4
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -74,6 +73,15 @@ function createReceiptDeliveryPolicy() {
74
73
  if (accepted) return "accepted";
75
74
  return deliveryTurns < RECEIPT_DELIVERY_TURN_LIMIT ? "request-delivery" : "no-receipt";
76
75
  },
76
+ /** Current typed delivery facts. No prose. */
77
+ deliveryState() {
78
+ return {
79
+ terminalToolCalled,
80
+ rejectedReceipts: rejectedReceipts.map((item) => ({ ...item })),
81
+ deliveryTurns,
82
+ acceptedReceipt: false
83
+ };
84
+ },
77
85
  facts(binding) {
78
86
  return noReceiptLifecycleFacts({ terminalToolCalled, rejectedReceipts: [...rejectedReceipts], deliveryTurns, ...binding });
79
87
  }
@@ -81,7 +89,6 @@ function createReceiptDeliveryPolicy() {
81
89
  }
82
90
  export {
83
91
  NO_RECEIPT_LIFECYCLE_ENTRY_TYPE,
84
- RECEIPT_DELIVERY_PROMPT,
85
92
  RECEIPT_DELIVERY_TURN_LIMIT,
86
93
  createReceiptDeliveryPolicy,
87
94
  noReceiptLifecycleFacts,
@@ -8,8 +8,8 @@ export const REVIEW_QUEUE_STATUSES = new Set(REVIEW_QUEUE_WORDS);
8
8
  /**
9
9
  * One object root. Codex structured output rejects a root anyOf, and status
10
10
  * stays a required three-state enum with no omitted-status failure branch.
11
- * A review seat that can still submit records a real external-dependency
12
- * failure as escalate plus the existing infrastructureFailure field.
11
+ * External-dependency failure while the seat can still submit:
12
+ * docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md
13
13
  */
14
14
  export const reviewSubmissionSchema = withTerminatingOutputDeclarations(Type.Object({
15
15
  status: Type.Union([
@@ -17,7 +17,7 @@ export const reviewSubmissionSchema = withTerminatingOutputDeclarations(Type.Obj
17
17
  Type.Literal(REVIEW_QUEUE_WORDS[1]),
18
18
  Type.Literal(REVIEW_QUEUE_WORDS[2]),
19
19
  ], {
20
- description: "审核队列三态枚举,必填。审核席仍能交卷而完成审核所需的外部依赖发生真实基础设施失败时,status 取 escalate,细节写入 infrastructureFailure。不得省略 status,不得用另两个枚举值充当放行或封驳。包侧不因格式拒收。",
20
+ description: "审核席本轮裁决的判别状态。",
21
21
  }),
22
22
  findings: Type.Optional(Type.Unknown({ description: "审核发现,原样留存" })),
23
23
  reason: Type.Optional(Type.Unknown({ description: "上呈理由" })),
@@ -0,0 +1,9 @@
1
+ # 交卷 schema 不把格式当拒收权
2
+
3
+ Status: accepted
4
+
5
+ 交卷工具 schema 保留字段声明与 description;非审核席维持零 required(含记账位)、顶层开放额外属性的原决定。审核席的判别字段 status 则为三态枚举且必填,没有省略 status 的失败例外。审核席仍能交卷、而完成审核所需的外部依赖发生真实基础设施失败时,status 取上呈值,失败细节写入既有 infrastructureFailure;这不是放行或封驳。执行或宿主已失败、审核席无法交卷时,仍由宿主如实失败终局。
6
+
7
+ 包侧收卷:审核席除判别字段外,其余字段不必填、不限制,允许额外属性;声明不是包侧校验授权,包不因格式拒收或判死;判别字段没填或读不出时只说读不出并附收到的值,不在重交提示里另写一套状态名。key 名字不必逐字相同,合不合格由审刑院按是否读得懂来判。
8
+
9
+ 宿主生成端:使用宿主原生能力;Codex strict 结构化输出要求根节点为 object、对象关闭额外属性,不能以该出口承诺模型可生成未声明字段。此项宿主缺口如实报告,不另造适配层或出口;生成端的限制不变成包侧拒收权。(#1055。三态枚举必填并撤销无 status 例外:源卷 e4b50c8e-15c7-4c92-9263-959efadb24eb.jsonl,owner uuid ddff7ae0-0b1e-44eb-b80c-96d2a3d3d239、0ddc3a4e-9a53-4565-b1cc-7ba3471d11e5、1707c6ec-ac17-4876-97d1-73b57c734259。根节点须为 object:[Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs))
@@ -14,14 +14,12 @@ import type { Message } from "@earendil-works/pi-ai";
14
14
  import {
15
15
  registerNavigatorModelCommand,
16
16
  resolveNavigatorAuthorityMaterial,
17
- type NavigatorTargetRole,
18
17
  } from "../src/navigator-attendance.ts";
19
18
  import { loadNavigatorWorkContext as loadHostNeutralNavigatorWorkContext } from "../src/navigator-work-context.ts";
20
19
  export { resolveNavigatorAuthorityMaterial };
21
20
  import { JUDGE_OUTPUT_TOOL_NAME } from "../src/package-contracts/judge-output.ts";
22
21
  import { readOAuthKeepaliveProviders } from "../src/oauth-keepalive.ts";
23
22
  import {
24
- formatNavigatorRoleHelp,
25
23
  type RoleRuntimeDependencies,
26
24
  } from "../src/role-runtime.ts";
27
25
  import { createRoleRuntimeDependencies } from "../src/role-runtime-dependencies.ts";
@@ -31,31 +29,6 @@ if (process.env.AK_ROLE_PACKAGE_ROOT === undefined || process.env.AK_ROLE_PACKAG
31
29
  process.env.AK_ROLE_PACKAGE_ROOT = packageRoot;
32
30
  }
33
31
 
34
- // Cold `pi -e <extension> --help` must cover installed-package process startup under CI load.
35
- // This bound is process-startup budget only — not settlement-to-visible presentation latency.
36
- export const NAVIGATOR_LIVE_HELP_TIMEOUT_MS = 30_000;
37
-
38
- /**
39
- * Subprocess live help (disk-reread via fresh pi -e). Kept for cold-install proofs.
40
- * Production Navigator prepare must not call this on the post-role grace path — under
41
- * concurrent CI load each role's pi --help alone can exceed the accepted 3s grace.
42
- */
43
- export async function loadNavigatorRoleHelp(
44
- pi: Pick<ExtensionAPI, "exec">,
45
- extensionPath: string,
46
- cwd: string,
47
- role: NavigatorTargetRole,
48
- ): Promise<string> {
49
- const result = await pi.exec("pi", ["--no-extensions", "--no-skills", "--no-prompt-templates", "--no-themes", "--no-context-files", "-e", extensionPath, "--ak-role", role, "--help"], { cwd, timeout: NAVIGATOR_LIVE_HELP_TIMEOUT_MS });
50
- if (result.killed) {
51
- throw new Error(`live help unavailable for ${role}: process did not settle`, { cause: result });
52
- }
53
- if (result.code !== 0) throw new Error(`live help unavailable for ${role}: process exited with code ${result.code}`, { cause: result });
54
- return result.stdout || result.stderr;
55
- }
56
-
57
- export { formatNavigatorRoleHelp as formatInProcessNavigatorRoleHelp };
58
-
59
32
  function projectJudgeTranscriptForAudit(messages: Message[]): Message[] {
60
33
  return messages.map((message) => {
61
34
  if (message.role !== "assistant") return message;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.5079",
3
+ "version": "0.1.5122",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -36,6 +36,7 @@
36
36
  "schemas",
37
37
  "packets",
38
38
  "resources",
39
+ "docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md",
39
40
  "README.md",
40
41
  "LICENSE",
41
42
  "THIRD_PARTY_NOTICES.md",
@@ -13,8 +13,8 @@
13
13
  | `run-lifecycle.ts` A4.1 | `[ak-role:resume-continue]` | 生产码不再含此字面量。结算只跳过空轮与引擎指针续跑。 |
14
14
  | `run-lifecycle.ts` A4.2 | `重新读 <path>` | 前轮已删改写。 |
15
15
  | `submission-correctable-error.ts` A4.5 | `终局交卷并非本轮唯一工具调用。` | 删。Pi/ACP `deliverSubmissionRejection` 不再注入该句。 |
16
- | `role-runtime.ts` 催交 | `本会话尚无已接受的 typed 回执。` | **留**(轮次/预算,Q2)。 |
17
- | `navigator-attendance.ts` early prepare 开场 | `父衙门进行中。请在本宿主会话熟悉下列材料并待命;结算结果送达后,再给出下一步建议。本轮不要提交最终路线建议。` | #959 迁入 `resources/navigator-route-playbook.md`「到场阶段」;代码只留中立指针句「本轮为待命轮。」 |
18
- | `navigator-attendance.ts` settlement feed 开场 | `父衙门结算已送达。请根据下列材料给出下一步建议。` | #959 迁入同手册;代码只留中立指针句「本轮为结算投喂轮。」 |
16
+ | `role-runtime.ts` 催交 | `本会话尚无已接受的 typed 回执。` | #1021 改为递当前 typed delivery 事实,不写催交句。 |
17
+ | `navigator-attendance.ts` early prepare 开场 | `父衙门进行中。请在本宿主会话熟悉下列材料并待命;结算结果送达后,再给出下一步建议。本轮不要提交最终路线建议。` | #959 迁入 `resources/navigator-route-playbook.md`「到场阶段」。#1021 删掉代码指针句「本轮为待命轮。」:待命不调模型,只记到场。 |
18
+ | `navigator-attendance.ts` settlement feed 开场 | `父衙门结算已送达。请根据下列材料给出下一步建议。` | #959 迁入同手册。#1021 删掉代码指针句「本轮为结算投喂轮。」:结算轮只递当前 typed 三态及指针。 |
19
19
 
20
20
  催交句与 2.1–2.3 打回文、重问三句不在删除列。
@@ -27,6 +27,10 @@
27
27
  - 官方:https://prod.cursor.com/help/ai-features/bugbot 与 automations 文档
28
28
  - 内置 Bugbot 与同账号自定义任务须区分;某任务额度耗尽不推断其他任务失效
29
29
 
30
+ ## 工序
31
+
32
+ 判定任务材料后绑定本仓唯一 PR(prNumber,或由 issueNumber 解析到唯一 PR)。已有显式 PR 绑定时不必再绑。多义或无法确定时不猜,交给调用方明确目标。未绑定前不观察。观察把证据存成不可变快照;上下文里的正文只到头部,其余按 evidenceId 开卷。请求发在所引最新快照 HEAD 上;清单外的 requestId 要带上正文。交卷交 findings 指针,不把模板通知当成 finding;没有 finding 就不要编一条。未完成写现场原因,不写成没问题。
33
+
30
34
  ## 修正规则
31
35
 
32
36
  - 优先引用官方文档;现场差异记入仓库差异条并附 PR/证据指针
@@ -1,11 +1,9 @@
1
- ## 到场阶段(#959 / ADR 0073)
1
+ ## 到场阶段(#959 / ADR 0073 / #1021)
2
2
 
3
- 代码只传中立阶段指针句与 typed 材料;阶段行为说明住本手册,随案附卷。
3
+ 阶段行为说明住本手册。代码不写阶段句,也不把本手册再拼进用户消息。
4
4
 
5
- - **待命轮**:父衙门进行中。请在本宿主会话熟悉下列材料并待命;结算结果送达后,再给出下一步建议。本轮不要提交最终路线建议。
6
- - **结算投喂轮**:父衙门结算已送达。请根据下列材料给出下一步建议。
7
-
8
- Early 待命轮的输出由执行机制丢弃;最终路线建议只在结算投喂轮给出。
5
+ - **待命**:父衙门进行中时,代码只记到场,不调用模型。这一轮没有输出。
6
+ - **结算**:父衙门结算送达后,用户消息只有该次 typed 三态及指针。Soul 与本手册已在系统提示里。根据这份结算和本手册给出下一步建议。
9
7
 
10
8
  ## 宿主轴(与角色命令面)
11
9
 
@@ -15,6 +15,10 @@ const entries = [
15
15
  // (#590: published non-bundle graph must stay closed under its own relative edges).
16
16
  "navigator-public-session",
17
17
  "navigator-session-contracts",
18
+ // Static import of navigator-attendance and navigator-public-session.
19
+ "navigator-work-base",
20
+ // Static import of navigator-work-base.
21
+ "atomic-write",
18
22
  "public-role-summons",
19
23
  "pi/in-process-session",
20
24
  "activation-ledger-git",
@@ -2,4 +2,5 @@
2
2
 
3
3
  提交:标题冠 ak-roles: 在最前;forward commit,严禁 amend——改错就再提交一笔,不改历史。
4
4
  交卷回执:改动清单与票面逐项对得上;验证证据附命令与结果原文。
5
+ completed 回执含四项证据:TDD、同模式、引入回归、行为事实。
5
6
  新造通用件:回执写明仓内、语言标准库、包生态各搜了什么、为什么不用。
@@ -46,8 +46,9 @@ export const AUDITOR_SESSION_MATERIALS = {
46
46
  "souls/judge-auditor.md",
47
47
  "souls/audit-law.md",
48
48
  "souls/quality-law.md",
49
+ "docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md",
49
50
  ],
50
- doctor: ["CLAUDE.md", "souls/doctor-auditor.md"],
51
+ doctor: ["CLAUDE.md", "souls/doctor-auditor.md", "docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md"],
51
52
  } as const satisfies Record<
52
53
  AuditorSoulRole,
53
54
  readonly [string, string, ...(readonly string[])]
@@ -351,8 +351,7 @@ export function createCollectorRoleRuntime(
351
351
  pi.registerTool({
352
352
  name: COLLECTOR_BIND_TARGET_TOOL,
353
353
  label: "通进司认票绑定",
354
- description:
355
- "角色判定任务材料后绑定本仓唯一 PR 目标。可提交 prNumber 或 issueNumber(线上关联唯一 PR);显式 --pr 已绑定时无需再调。多义或无法确定时会正确驳回,要求调用方明确 --pr。",
354
+ description: "绑定本仓唯一 PR 目标。",
356
355
  promptSnippet: "绑定角色判定的 issue/PR 目标",
357
356
  parameters: bindSchema,
358
357
  async execute(toolCallId: string, params: BindParams, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
@@ -433,7 +432,7 @@ export function createCollectorRoleRuntime(
433
432
  pi.registerTool({
434
433
  name: COLLECTOR_OBSERVE_TOOL,
435
434
  label: "通进司观察",
436
- description: "抓取配置目标的完整 GitHub PR 证据,存不可变快照入卷。正文在上下文中只给头部摘录加指针;需要头部之外的正文时,用 ak_collector_read 按 evidenceId 开卷;findings 的拆分与归类由你在交件时完成。目标未绑定前须先 ak_collector_bind_target。",
435
+ description: "抓取配置目标的 GitHub PR 证据。",
437
436
  promptSnippet: "抓取配置目标 PR 证据",
438
437
  parameters: observeSchema,
439
438
  async execute(toolCallId: string, _params: unknown, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
@@ -464,7 +463,7 @@ export function createCollectorRoleRuntime(
464
463
  pi.registerTool({
465
464
  name: COLLECTOR_READ_TOOL,
466
465
  label: "通进司开卷",
467
- description: "按 evidenceId 开卷读取一条已观测材料的全量正文与指针;只在观察头部摘录不足以判读时调用。",
466
+ description: "按 evidenceId 开卷读取一条已观测材料的全量正文与指针。",
468
467
  promptSnippet: "按指针开卷读材料",
469
468
  parameters: readSchema,
470
469
  async execute(toolCallId: string, params: ReadParams, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
@@ -499,7 +498,7 @@ export function createCollectorRoleRuntime(
499
498
  pi.registerTool({
500
499
  name: COLLECTOR_REQUEST_TOOL,
501
500
  label: "通进司请求",
502
- description: "在所引最新快照 HEAD 发一次请求。requestId 可取配置清单,或角色依手册/现场判定的稳定 id;后者须同时提供 body。",
501
+ description: "发一次请求。",
503
502
  promptSnippet: "发一次评审请求",
504
503
  parameters: requestSchema,
505
504
  async execute(toolCallId: string, params: RequestParams, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
@@ -625,7 +624,7 @@ export function createCollectorRoleRuntime(
625
624
  pi.registerTool({
626
625
  name: COLLECTOR_WAIT_TOOL,
627
626
  label: "通进司等待",
628
- description: "再观察前等待;实际睡眠不超过剩余等待窗。",
627
+ description: "等待。实际睡眠不超过剩余等待窗。",
629
628
  promptSnippet: "等待窗内等待",
630
629
  parameters: waitSchema,
631
630
  async execute(toolCallId: string, params: WaitParams, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
@@ -656,7 +655,7 @@ export function createCollectorRoleRuntime(
656
655
  pi.registerTool({
657
656
  name: COLLECTOR_OUTPUT_TOOL,
658
657
  label: "通进司输出",
659
- description: "观察完成后提交原交卷。正常完工提交空对象 {}(如需报 finding,填 findings 指针数组);仅在基础设施真实失败时才可填 infrastructureFailure,无失败时必须省略该字段。",
658
+ description: "提交通进司回执。",
660
659
  promptSnippet: "提交通进司回执",
661
660
  parameters: outputSchema,
662
661
  async execute(toolCallId: string, params: OutputParams, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {