@akagilnc/pi-workflow-roles 0.1.5714 → 0.1.5753

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 (63) hide show
  1. package/dist/acp-host/production-host.js +517 -516
  2. package/dist/analyst-gate-cycles-read.js +7 -6
  3. package/dist/archivist-record-pointer.js +9 -45
  4. package/dist/auditor-soul.js +7 -5
  5. package/dist/external-host-turn-loop.js +2 -16
  6. package/dist/gatekeeper-role.js +14 -1
  7. package/dist/headless-host/production-host.js +517 -516
  8. package/dist/judge-role.js +1 -1
  9. package/dist/migrate-book-topology.js +61 -51
  10. package/dist/navigator-public-session.js +17 -8
  11. package/dist/notary-contracts.js +2 -2
  12. package/dist/notary-source-run.js +2 -2
  13. package/dist/packaged-role-registry.js +0 -10
  14. package/dist/public-cli/cli.js +13 -6
  15. package/dist/public-cli/instruction-seat-run.js +157 -138
  16. package/dist/public-cli/invocation.js +35 -0
  17. package/dist/public-cli/main.js +497 -505
  18. package/dist/public-cli/post-admission.js +11 -12
  19. package/dist/public-cli/run-lifecycle.js +56 -34
  20. package/dist/public-cli/seat-ticket-binding.js +8 -7
  21. package/dist/public-cli/settlement.js +4 -126
  22. package/dist/public-cli/terminal.js +3 -3
  23. package/dist/public-role-summons.js +14 -6
  24. package/dist/review-submission.js +47 -14
  25. package/dist/role-run-relocation.js +4 -3
  26. package/dist/role-submission-declarations.js +5 -5
  27. package/dist/run-dossier.js +6 -21
  28. package/dist/run-terminal-artifacts.js +11 -0
  29. package/dist/secretariat-contracts.js +0 -4
  30. package/dist/submission-gate.js +58 -42
  31. package/dist/submission-ledger.js +3 -2
  32. package/package.json +1 -1
  33. package/souls/notary.md +6 -5
  34. package/src/analyst-gate-cycles-read.ts +7 -6
  35. package/src/analyst-ledger.ts +2 -2
  36. package/src/archivist-record-pointer.ts +10 -64
  37. package/src/auditor-soul.ts +7 -5
  38. package/src/countersign-role.ts +5 -5
  39. package/src/external-host-turn-loop.ts +2 -23
  40. package/src/gatekeeper-role.ts +24 -3
  41. package/src/judge-role.ts +1 -1
  42. package/src/navigator-public-session.ts +22 -10
  43. package/src/notary-contracts.ts +3 -2
  44. package/src/notary-source-run.ts +2 -7
  45. package/src/packaged-role-registry.ts +0 -13
  46. package/src/public-cli/cli.ts +18 -4
  47. package/src/public-cli/instruction-seat-run.ts +213 -155
  48. package/src/public-cli/invocation.ts +39 -0
  49. package/src/public-cli/post-admission.ts +11 -9
  50. package/src/public-cli/run-lifecycle.ts +80 -33
  51. package/src/public-cli/seat-ticket-binding.ts +9 -7
  52. package/src/public-cli/settlement.ts +3 -167
  53. package/src/public-cli/terminal.ts +3 -3
  54. package/src/public-role-summons.ts +40 -12
  55. package/src/review-submission.ts +72 -21
  56. package/src/role-run-relocation.ts +4 -3
  57. package/src/role-runtime.ts +3 -3
  58. package/src/role-submission-declarations.ts +5 -5
  59. package/src/run-dossier.ts +7 -19
  60. package/src/run-terminal-artifacts.ts +13 -0
  61. package/src/secretariat-contracts.ts +0 -19
  62. package/src/submission-gate.ts +75 -62
  63. package/src/submission-ledger.ts +3 -6
@@ -6,9 +6,8 @@
6
6
  * Role runners supply only turn request projection and narrow settlement adapters.
7
7
  */
8
8
  import { randomUUID } from "node:crypto";
9
- import { existsSync } from "node:fs";
10
9
  import { writeFile } from "node:fs/promises";
11
- import { buildAutoResumeContinuationPrompt, findRunDirectoryById, readRoleRunState, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
10
+ import { buildAutoResumeContinuationPrompt, findRunDirectoryById, resolveLiveRunDirectoryPath, readRoleRunState, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
12
11
  import { CliUsageError } from "./cli-errors.js";
13
12
  import { projectPublicTurnAxes } from "./turn-request.js";
14
13
  import { appendCallerFileFlagPaths, bindAdmittedTicketNumber, relocateAdmittedRunToTicket, } from "./invocation.js";
@@ -65,7 +64,7 @@ function projectRelocatedTurnIdentity(request, result, admitted, relocation) {
65
64
  }
66
65
  }
67
66
  /**
68
- * #1171: after the host returns, re-locate the leg by run id (report-ticket may
67
+ * #1171: after the host returns, follow the leg's placement (report-ticket may
69
68
  * have moved it mid-turn in the tool process). Settlement and render use the
70
69
  * live path; lease cleanup follows when the held path changed.
71
70
  * Failure is not best-effort: a missing former path with no replacement must not
@@ -73,14 +72,14 @@ function projectRelocatedTurnIdentity(request, result, admitted, relocation) {
73
72
  */
74
73
  export async function refreshAdmittedPlacementAfterHostTurn(admitted, authority, heldLease) {
75
74
  const home = homeFromRunDirectory(admitted.runDirectory);
76
- const found = await findRunDirectoryById(home, admitted.runId, admitted.bookKey, admitted.role);
77
- if (found === undefined && !existsSync(admitted.runDirectory)) {
75
+ const found = await resolveLiveRunDirectoryPath(admitted.runDirectory, home);
76
+ if (found === undefined) {
78
77
  throw new Error(`admitted run ${admitted.runId} missing after host turn; not found under ${home}`);
79
78
  }
80
79
  // Live identity first: later board-ticket read must not leave settlement on the
81
80
  // pre-relocate path when facts are damaged (#1171 F4-R1).
82
81
  let relocation;
83
- if (found !== undefined && found !== admitted.runDirectory) {
82
+ if (found !== admitted.runDirectory) {
84
83
  const oldRunDirectory = admitted.runDirectory;
85
84
  const coords = { ...authority.decode(admitted.principal) };
86
85
  rewritePrincipalSessionPaths(coords, oldRunDirectory, found);
@@ -92,11 +91,11 @@ export async function refreshAdmittedPlacementAfterHostTurn(admitted, authority,
92
91
  }
93
92
  // Board re-read discovers a ticket written mid-turn while still unbound
94
93
  // (report-ticket board page). Already-bound identity needs no discovery —
95
- // path relocate already used findRunDirectoryById. This is not damage
94
+ // placement refresh already followed the retained path. This is not damage
96
95
  // recovery: when unbound, readBoardTicketNumber damage/non-ENOENT IO still
97
96
  // propagates (失败诚实; notary: no damaged-control-plane → no-ticket wash).
98
97
  if (admitted.ticketNumber === undefined) {
99
- const boardTicket = await readBoardTicketNumber(found ?? admitted.runDirectory);
98
+ const boardTicket = await readBoardTicketNumber(found);
100
99
  if (boardTicket !== undefined) {
101
100
  admitted.ticketNumber = boardTicket;
102
101
  }
@@ -1384,10 +1383,10 @@ export async function runPostAdmissionSeatResume(input) {
1384
1383
  return { exitCode: error instanceof CliUsageError ? 2 : 1 };
1385
1384
  }
1386
1385
  }
1387
- async function pointExistingRunFailure(home, runId, authority, io, thrown) {
1386
+ async function pointExistingRunFailure(home, runId, authority, io, thrown, knownRunDirectory) {
1388
1387
  let foundRunDirectory;
1389
1388
  try {
1390
- foundRunDirectory = await findRunDirectoryById(home, runId);
1389
+ foundRunDirectory = await findRunDirectoryById(home, runId, undefined, undefined, knownRunDirectory);
1391
1390
  }
1392
1391
  catch {
1393
1392
  return false;
@@ -1408,8 +1407,8 @@ async function pointExistingRunFailure(home, runId, authority, io, thrown) {
1408
1407
  await presentResumeFailurePointer(io, thrown, (failure) => writeResumeDiagnostic(runDirectory, runId, failure, record.role));
1409
1408
  return true;
1410
1409
  }
1411
- export async function presentLocatedResumeFailure(home, runId, authority, io, thrown) {
1412
- return await pointExistingRunFailure(home, runId, authority, io, thrown);
1410
+ export async function presentLocatedResumeFailure(home, runId, authority, io, thrown, knownRunDirectory) {
1411
+ return await pointExistingRunFailure(home, runId, authority, io, thrown, knownRunDirectory);
1413
1412
  }
1414
1413
  function writeResumeFailurePointer(io, errorPath) {
1415
1414
  io.stderr(formatCliDiagnostic(`续跑失败,当次错误记录:${errorPath}`));
@@ -5,16 +5,17 @@
5
5
  * any existing run with an available Pi session principal may be resumed; caller decides.
6
6
  * Prose is never regex-classified as quota evidence.
7
7
  */
8
+ import { existsSync } from "node:fs";
8
9
  import { chmod, lstat, open, readdir, readFile, unlink, writeFile } from "node:fs/promises";
9
10
  import { basename, isAbsolute, join } from "node:path";
10
- import { activationBookDirectory, resolveActivationLedgerHome, } from "../activation-ledger-topology.js";
11
+ import { activationBookDirectory, resolveActivationLedgerHome, tryBookKeyFromAkRolesPath, } from "../activation-ledger-topology.js";
11
12
  import { findRoleRunDirectory, listBookRunDirectories, parseRunLeaf, sessionFileIn } from "../role-run-placement.js";
12
13
  import { isSafePositiveTicketNumber } from "../run-ticket-number.js";
13
14
  import { CliUsageError } from "./cli-errors.js";
14
15
  import { parseCollectorRepository } from "../collector-config.js";
15
16
  import { retainedRunPathsMatch, rewriteAdmittedRoleRunPage, rewriteRunDirectoryPathValue, } from "../role-run-relocation.js";
16
17
  import { packagedResumeSourcePath, packagedRoleMetadata, } from "../packaged-role-registry.js";
17
- import { admittedReviewerBaseFault, recordEffectiveInvocationModel, requireAuthorityRef, isReviewerLens, normalizeAdmittedAttachment, } from "./invocation.js";
18
+ import { admittedReviewerBaseFault, admittedSourceRunPath, recordEffectiveInvocationModel, requireAuthorityRef, isReviewerLens, normalizeAdmittedAttachment, } from "./invocation.js";
18
19
  import { readPageSync, writeSectionSync } from "../run-dossier.js";
19
20
  import { isRecord, errorText, isEnoent } from "../unknown-value.js";
20
21
  /** Default for the #422 configurable single-call auto-resume ceiling
@@ -53,9 +54,7 @@ function parseSameTicketSummonsMaterials(raw) {
53
54
  const attachmentPaths = Array.isArray(record.attachmentPaths)
54
55
  ? record.attachmentPaths.filter((p) => typeof p === "string")
55
56
  : undefined;
56
- const sourceRunPath = typeof record.sourceRunPath === "string" && record.sourceRunPath.trim() !== ""
57
- ? record.sourceRunPath
58
- : undefined;
57
+ const sourceRunPath = admittedSourceRunPath(record);
59
58
  let sourceRun;
60
59
  if (isRecord(record.sourceRun)) {
61
60
  const sr = record.sourceRun;
@@ -607,12 +606,20 @@ export async function acquireRunWriterLease(runDirectory, onCleanupFailure) {
607
606
  }
608
607
  /**
609
608
  * Locate a Role run directory by run ID under the ledger books home.
610
- * Returns undefined when the ID is unknown.
609
+ * A supplied directory is checked against the ID and retained without a book walk.
610
+ * Returns undefined when the bare ID is unknown.
611
611
  * Walk surface = listBookRunDirectories (flat legacy + subject-tree).
612
612
  * Collects every match under the supplied book/role filters: zero → undefined,
613
613
  * one → path, many → loud ambiguity (never readdir-first).
614
614
  */
615
- export async function findRunDirectoryById(home, runId, onlyBookKey, onlyRole) {
615
+ export async function findRunDirectoryById(home, runId, onlyBookKey, onlyRole, knownRunDirectory) {
616
+ if (typeof knownRunDirectory === "string" && knownRunDirectory.trim() !== "") {
617
+ const parsed = parseRunLeaf(basename(knownRunDirectory));
618
+ if (parsed === undefined || parsed.runId !== runId) {
619
+ throw new CliUsageError(`run directory does not match run id ${runId}: ${knownRunDirectory}`);
620
+ }
621
+ return knownRunDirectory;
622
+ }
616
623
  if (runId.trim() === "")
617
624
  return undefined;
618
625
  const ledgerHome = resolveActivationLedgerHome(home);
@@ -631,6 +638,16 @@ export async function findRunDirectoryById(home, runId, onlyBookKey, onlyRole) {
631
638
  .filter((bookKey) => onlyBookKey === undefined || bookKey === onlyBookKey)
632
639
  .map((bookKey) => activationBookDirectory(ledgerHome, bookKey)), runId, onlyRole);
633
640
  }
641
+ /** Keep an existing placement; rediscover only after its recorded path disappeared. */
642
+ export async function resolveLiveRunDirectoryPath(recordedPath, home, onlyBookKey) {
643
+ if (existsSync(recordedPath))
644
+ return recordedPath;
645
+ const parsed = parseRunLeaf(basename(recordedPath));
646
+ const bookKey = onlyBookKey ?? tryBookKeyFromAkRolesPath(recordedPath);
647
+ if (parsed === undefined || bookKey === undefined)
648
+ return undefined;
649
+ return findRunDirectoryById(home, parsed.runId, bookKey, parsed.role);
650
+ }
634
651
  /**
635
652
  * Parent-run binding on a retained officer run (#747 / #1166).
636
653
  * Typed sourceRunPath only — instruction bytes are never a path protocol.
@@ -638,12 +655,7 @@ export async function findRunDirectoryById(home, runId, onlyBookKey, onlyRole) {
638
655
  */
639
656
  export async function readRunParentPath(runDirectory) {
640
657
  const record = readPageSync(runDirectory, "admitted");
641
- if (record === undefined)
642
- return undefined;
643
- if (typeof record.sourceRunPath === "string" && record.sourceRunPath.trim() !== "") {
644
- return record.sourceRunPath;
645
- }
646
- return undefined;
658
+ return admittedSourceRunPath(record);
647
659
  }
648
660
  /**
649
661
  * True when durable run-state names a session principal that has actually formed
@@ -681,6 +693,15 @@ async function runHasFormedSessionPrincipal(runDirectory) {
681
693
  * Only a truly missing book directory means no history; damage/permission errors propagate.
682
694
  */
683
695
  export async function findLatestRunIdForSeatTicket(input) {
696
+ const found = await findLatestRunForSeatTicket(input);
697
+ return found?.runId;
698
+ }
699
+ /**
700
+ * Latest retained seat run under one book/parent (and optional ticket), with its
701
+ * directory — callers that already scoped the search must not drop the path and
702
+ * re-resolve runId across every book (#1195).
703
+ */
704
+ export async function findLatestRunForSeatTicket(input) {
684
705
  if (input.parentRunPath.trim() === "") {
685
706
  return undefined;
686
707
  }
@@ -708,8 +729,8 @@ export async function findLatestRunIdForSeatTicket(input) {
708
729
  // Durable fact: never resume-select a provisional that never formed principal.
709
730
  if (!(await runHasFormedSessionPrincipal(runDirectory)))
710
731
  continue;
711
- if (best === undefined || runId > best)
712
- best = runId;
732
+ if (best === undefined || runId > best.runId)
733
+ best = { runId, runDirectory };
713
734
  }
714
735
  return best;
715
736
  }
@@ -732,8 +753,8 @@ function restoredTicketFields(fields) {
732
753
  ...(fields.ticketNumber === undefined ? {} : { ticketNumber: fields.ticketNumber }),
733
754
  };
734
755
  }
735
- async function loadResumableRunRecord(home, runId, authority) {
736
- const runDirectory = await findRunDirectoryById(home, runId);
756
+ async function loadResumableRunRecord(home, runId, authority, knownRunDirectory) {
757
+ const runDirectory = await findRunDirectoryById(home, runId, undefined, undefined, knownRunDirectory);
737
758
  if (runDirectory === undefined) {
738
759
  throw new CliUsageError(`unknown role run id: ${runId}`);
739
760
  }
@@ -842,9 +863,7 @@ async function loadResumableRunRecord(home, runId, authority) {
842
863
  }
843
864
  }
844
865
  // Notary — admitted source-run locator identity (#633 resume).
845
- if (typeof record.sourceRunPath === "string" && record.sourceRunPath.trim() !== "") {
846
- sourceRunPath = record.sourceRunPath;
847
- }
866
+ sourceRunPath = admittedSourceRunPath(record);
848
867
  if (isRecord(record.sourceRun)) {
849
868
  const sr = record.sourceRun;
850
869
  if (typeof sr.runId === "string" &&
@@ -930,11 +949,9 @@ async function loadResumableRunRecord(home, runId, authority) {
930
949
  throw new CliUsageError(`role run invocation identity is unreadable: ${runId}`, { cause: error });
931
950
  }
932
951
  }
933
- const sourceRunLeaf = parseRunLeaf(basename(sourceRunPath ?? ""));
934
- const referencedRunId = sourceRun?.runId ?? sourceRunLeaf?.runId;
935
- const referencedRole = sourceRun?.role ?? sourceRunLeaf?.role;
936
- if (referencedRunId !== undefined && referencedRunId !== "") {
937
- const currentSourceRunDirectory = await findRunDirectoryById(home, referencedRunId, run.bookKey, referencedRole);
952
+ const recordedSourceDirectory = sourceRunPath ?? sourceRun?.runDirectory;
953
+ if (recordedSourceDirectory !== undefined) {
954
+ const currentSourceRunDirectory = await resolveLiveRunDirectoryPath(recordedSourceDirectory, home, run.bookKey);
938
955
  if (currentSourceRunDirectory !== undefined) {
939
956
  sourceRunPath = currentSourceRunDirectory;
940
957
  if (sourceRun !== undefined) {
@@ -997,8 +1014,8 @@ function seatLoadedResult(loaded, admitted) {
997
1014
  run: loaded.run,
998
1015
  };
999
1016
  }
1000
- export async function peekRoleRunRole(home, runId) {
1001
- const runDirectory = await findRunDirectoryById(home, runId);
1017
+ export async function peekRoleRunRole(home, runId, options) {
1018
+ const runDirectory = await findRunDirectoryById(home, runId, undefined, undefined, options?.runDirectory);
1002
1019
  if (runDirectory === undefined)
1003
1020
  return undefined;
1004
1021
  const run = await readRoleRunIdentity(runDirectory);
@@ -1009,8 +1026,8 @@ export async function peekRoleRunRole(home, runId) {
1009
1026
  * selects the existing restore checks. The stored native host session id is
1010
1027
  * read on the public explicit resume seam, not from this load.
1011
1028
  */
1012
- export async function loadResumablePublicRole(home, runId, authority) {
1013
- const loaded = await loadResumableRunRecord(home, runId, authority);
1029
+ export async function loadResumablePublicRole(home, runId, authority, options) {
1030
+ const loaded = await loadResumableRunRecord(home, runId, authority, options?.runDirectory);
1014
1031
  return seatLoadedResult(loaded, admitResumedRole(loaded));
1015
1032
  }
1016
1033
  /**
@@ -1028,25 +1045,30 @@ function admitResumedRole(loaded) {
1028
1045
  const base = resumedBaseAdmitted(loaded);
1029
1046
  switch (record.admission) {
1030
1047
  case "instruction": {
1048
+ // Restore sourceRunPath whenever the admitted page has it (auditor/inspector/…).
1049
+ // Downstream continueParent / seal-id belonging needs this fact (#1195).
1050
+ const withSource = fields.sourceRunPath === undefined
1051
+ ? base
1052
+ : { ...base, sourceRunPath: fields.sourceRunPath };
1031
1053
  if (packagedResumeSourcePath(role)) {
1032
1054
  const admitted = {
1033
1055
  role: "inspector",
1034
- ...base,
1035
- ...(fields.sourceRunPath === undefined
1036
- ? {}
1037
- : { sourceRunPath: fields.sourceRunPath }),
1056
+ ...withSource,
1038
1057
  };
1039
1058
  return admitted;
1040
1059
  }
1041
1060
  return {
1042
1061
  role,
1043
- ...base,
1062
+ ...withSource,
1044
1063
  };
1045
1064
  }
1046
1065
  case "court-materials": {
1047
1066
  const admitted = {
1048
1067
  role: "countersign",
1049
1068
  ...base,
1069
+ ...(fields.sourceRunPath === undefined
1070
+ ? {}
1071
+ : { sourceRunPath: fields.sourceRunPath }),
1050
1072
  };
1051
1073
  return admitted;
1052
1074
  }
@@ -9,13 +9,14 @@
9
9
  * freshSummons is required so no seat can drift back into its own skip branch.
10
10
  */
11
11
  import { resolveBookKeyFromGit } from "../activation-ledger-git.js";
12
- import { findLatestRunIdForSeatTicket, } from "./run-lifecycle.js";
12
+ import { findLatestRunForSeatTicket, } from "./run-lifecycle.js";
13
13
  /**
14
14
  * Sole same-seat → resume decision by parent run path (#637 / #724 / #747 / #987).
15
15
  * A supplied typed ticket only narrows the prior-run search. When a prior run is
16
- * found, resume carries this summons' materials. Lookup/resume failures
17
- * propagate (失败诚实) — never wash into a fresh mint. Returns undefined when
18
- * the caller declared an explicit fresh summons (`ak-role new`), the parent
16
+ * 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
19
20
  * path is blank, or no prior run exists; those mint new. freshSummons is
20
21
  * required so no seat can drift back into its own skip branch.
21
22
  */
@@ -24,14 +25,14 @@ export async function tryResumeSameTicketSeatRun(input) {
24
25
  return undefined;
25
26
  if (input.parentRunPath.trim() === "")
26
27
  return undefined;
27
- const previousRunId = await findLatestRunIdForSeatTicket({
28
+ const previous = await findLatestRunForSeatTicket({
28
29
  home: input.home,
29
30
  bookKey: resolveBookKeyFromGit(input.projectRoot),
30
31
  role: input.role,
31
32
  parentRunPath: input.parentRunPath,
32
33
  ...(input.ticketNumber === undefined ? {} : { ticketNumber: input.ticketNumber }),
33
34
  });
34
- if (previousRunId === undefined)
35
+ if (previous === undefined)
35
36
  return undefined;
36
- return await input.resume(previousRunId, input.summons);
37
+ return await input.resume(previous.runId, input.summons, previous.runDirectory);
37
38
  }
@@ -18,9 +18,8 @@ import { describeErrorIdentity } from "./run-lifecycle.js";
18
18
  import { readEngineDetourToolUsage, readInvocationEngineMounted, runDirectoryFromSessionDirectory, sessionFileFromSessionDirectory, withEngineDetourToolUsageFact, } from "../engine-detour-usage.js";
19
19
  import { DOCTOR_CANDIDATE_ENTRY_TYPE } from "../dossier-resolution.js";
20
20
  import { homeFromRunDirectory, } from "./invocation.js";
21
- import { SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY, SECRETARIAT_GATE_OFFICER_ENTRY_TYPE, } from "../secretariat-contracts.js";
22
21
  import { findLatestDurablePackagedRoleTerminal, NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY, } from "../navigator-invocation-identity.js";
23
- import { packagedDurableOfficerEntry, packagedRoleAcceptedOutputTool, packagedRoleMetadata, } from "../packaged-role-registry.js";
22
+ import { packagedRoleAcceptedOutputTool, packagedRoleMetadata, } from "../packaged-role-registry.js";
24
23
  import { NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, noReceiptLifecycleFacts, parseNoReceiptLifecycleFacts, priorReceiptContinuation, receiptAttemptPointer, } from "../receipt-delivery-policy.js";
25
24
  import { readStateRowsSync, runCurrentPath } from "../run-dossier.js";
26
25
  import { reportRunRecord } from "../sitian-facade.js";
@@ -135,13 +134,12 @@ function roleOutcomeFromRows(role, rows) {
135
134
  return { outcome: { kind: "accepted", role, payloads }, ...belonging };
136
135
  }
137
136
  async function sealedLedgerOutcome(admitted, role, scope) {
138
- const home = sealedLedgerHome(admitted);
139
137
  // #879: when court scope is present, roleOutcome is this-court original only —
140
138
  // never last-wins over an undivided historical array, and never falls back to
141
139
  // full-run history when this court sealed zero rows. #836 presentation of full
142
140
  // history stays on attachRecordedSubmissions / terminal.submissions (run-scoped).
143
141
  if (scope?.courtAttemptId !== undefined && scope.courtAttemptId.length > 0) {
144
- const thisCourt = await readAttemptScopedSubmissionRows(admitted.projectRoot, admitted.runId, scope.courtAttemptId, home);
142
+ const thisCourt = await readAttemptScopedSubmissionRows(admitted.projectRoot, admitted.runId, scope.courtAttemptId, ledgerReadScope(admitted, scope));
145
143
  // Scope present + zero this-court rows → no this-court outcome (not run history).
146
144
  return roleOutcomeFromRows(role, thisCourt);
147
145
  }
@@ -1084,138 +1082,18 @@ function currentAttemptStartIndex(entries) {
1084
1082
  }
1085
1083
  return 0;
1086
1084
  }
1087
- /** Accepted-tool seats retain declaration checks and optional officer projection. */
1085
+ /** Accepted-tool seats: declaration checks only. Other-seat finals stay on their own runs. */
1088
1086
  async function trySettleAcceptedSeatTerminalResult(admitted, authority, scope) {
1089
1087
  const toolName = packagedRoleAcceptedOutputTool(admitted.role);
1090
1088
  if (toolName === undefined) {
1091
1089
  throw new Error(`accepted-seat settlement is not declared for ${admitted.role}`);
1092
1090
  }
1093
- const settled = await settleSealedSeat(admitted, authority, scope, { acceptedOnly: false });
1094
- const record = packagedRoleMetadata(admitted.role);
1095
- if (settled === undefined
1096
- || record === undefined
1097
- || !("projectCountersignTerminal" in record)
1098
- || record.projectCountersignTerminal !== true) {
1099
- return settled;
1100
- }
1101
- return applySecretariatCountersignTerminal(admitted, authority, settled, scope);
1091
+ return settleSealedSeat(admitted, authority, scope, { acceptedOnly: false });
1102
1092
  }
1103
1093
  /** One settlement dispatch. The registry `settlement` leaf selects the path. */
1104
1094
  export async function trySettlePublicSeat(admitted, authority, scope) {
1105
1095
  return settleSeat(admitted, authority, scope);
1106
1096
  }
1107
- /**
1108
- * #969 seat projection sole authority: 给事中 terminal (署|上呈) booked as a
1109
- * durable custom entry (envelope-safe) — ledger accepted stays LLM params (#836).
1110
- * Never scan toolResult rows: those are memory-only on headless/ACP (#617/#959).
1111
- * Receipt bytes stay original; nested runId rides beside them.
1112
- * Caller must only apply this when the terminal-defining submission actually
1113
- * produced the officer entry (gate-bound converged, or 给事中 audit_escalation).
1114
- */
1115
- function countersignTerminalFromEntries(entries) {
1116
- for (let index = entries.length - 1; index >= 0; index -= 1) {
1117
- const entry = entries[index];
1118
- if (entry?.type !== "custom")
1119
- continue;
1120
- if (entry.customType !== SECRETARIAT_GATE_OFFICER_ENTRY_TYPE)
1121
- continue;
1122
- const data = isRecord(entry.data)
1123
- ? entry.data
1124
- : undefined;
1125
- if (data === undefined || !packagedDurableOfficerEntry(data.officer))
1126
- continue;
1127
- if (data.receipt === undefined)
1128
- continue;
1129
- const runId = typeof data.runId === "string" && data.runId.trim() !== ""
1130
- ? data.runId
1131
- : undefined;
1132
- return {
1133
- receipt: data.receipt,
1134
- ...(runId === undefined ? {} : { runId }),
1135
- };
1136
- }
1137
- return undefined;
1138
- }
1139
- function withCountersignTerminalFact(roleOutcome, officer) {
1140
- const prior = roleOutcome.decisiveFacts !== undefined && isRecord(roleOutcome.decisiveFacts)
1141
- ? roleOutcome.decisiveFacts
1142
- : {};
1143
- return {
1144
- ...roleOutcome,
1145
- decisiveFacts: {
1146
- ...prior,
1147
- [SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY]: officer,
1148
- },
1149
- };
1150
- }
1151
- /**
1152
- * Terminal-defining secretariat submission owns officer projection.
1153
- * Last readable secretariatStatus on accepted payloads decides: only gate-bound
1154
- * converged projects a prior durable officer entry. Parent escalate bypasses the
1155
- * gate and must not inherit a stale pass entry from an earlier turn (#969).
1156
- */
1157
- function acceptedSecretariatDefinesOfficerProjection(roleOutcome) {
1158
- const payloads = roleOutcome.payloads ?? [];
1159
- for (let index = payloads.length - 1; index >= 0; index -= 1) {
1160
- const payload = payloads[index];
1161
- if (!isRecord(payload))
1162
- continue;
1163
- const status = payload.secretariatStatus;
1164
- if (typeof status !== "string")
1165
- continue;
1166
- return status === "converged";
1167
- }
1168
- return false;
1169
- }
1170
- async function applySecretariatCountersignTerminal(admitted, authority, settled, scope) {
1171
- const coordinates = coordinatesFromAdmitted(authority, admitted);
1172
- let entries = [];
1173
- try {
1174
- entries = await readBoundSessionEntries(coordinates.sessionFile);
1175
- }
1176
- catch (error) {
1177
- if (!isEnoent(error)) {
1178
- await noteSettlementFault(admitted.runDirectory, scope, `secretariat officer read failed beside lawful terminal: ${describeErrorIdentity(error)}`);
1179
- }
1180
- return settled;
1181
- }
1182
- const officer = countersignTerminalFromEntries(entries);
1183
- if (officer === undefined)
1184
- return settled;
1185
- // 给事中上呈: public payloads = officer receipt; runId + receipt fact beside.
1186
- // This kind is produced only when beforeAccept booked the officer entry.
1187
- if (settled.roleOutcome.kind === "audit_escalation") {
1188
- return {
1189
- ...settled,
1190
- roleOutcome: withCountersignTerminalFact({
1191
- ...settled.roleOutcome,
1192
- payloads: [officer.receipt],
1193
- }, officer),
1194
- };
1195
- }
1196
- // Pass (署): keep 中书省 payloads; present 给事中 判词 + runId via decisiveFacts.
1197
- // Parent escalate accepted terminal must not project a stale prior pass entry.
1198
- if (settled.roleOutcome.kind === "accepted") {
1199
- if (!acceptedSecretariatDefinesOfficerProjection(settled.roleOutcome)) {
1200
- return settled;
1201
- }
1202
- return {
1203
- ...settled,
1204
- roleOutcome: withCountersignTerminalFact(settled.roleOutcome, officer),
1205
- };
1206
- }
1207
- return settled;
1208
- }
1209
- /**
1210
- * After the audit gate returns, attach the secretariat officer fact onto the
1211
- * terminal this turn already settled. Does not publish again — a second publish
1212
- * would settle the same attempt twice.
1213
- */
1214
- export async function attachPostAuditCountersignFact(admitted, authority, terminal, scope) {
1215
- if (admitted.role !== "secretariat")
1216
- return terminal;
1217
- return applySecretariatCountersignTerminal(admitted, authority, terminal, scope);
1218
- }
1219
1097
  /**
1220
1098
  * Replace the leg's terminal with the controlled failure. A write failure
1221
1099
  * propagates: the caller notes it beside the host terminal.
@@ -49,9 +49,9 @@ export function formatTerminalResult(result) {
49
49
  if (result.roleOutcome.kind === "failure") {
50
50
  lines.push(`diagnostic\t${encodeTerminalField(result.roleOutcome.diagnostic)}`);
51
51
  }
52
- // Accepted volumes do not dump decisiveFacts unless a failed attempt is
53
- // recorded. The unsettled-direction fact still has to be visible on its own.
54
- // audit_escalation already dumps every decisiveFact below.
52
+ // Accepted volumes do not dump internal decisiveFacts unless recorded.
53
+ // Other-seat finals stay on those runs (#1195); only unsettled-direction is
54
+ // visible here. audit_escalation already dumps every decisiveFact below.
55
55
  if (result.roleOutcome.kind === "accepted"
56
56
  && result.roleOutcome.decisiveFacts?.directionUnsettled === true) {
57
57
  lines.push("fact\tdirectionUnsettled\ttrue");
@@ -248,6 +248,7 @@ async function summonPublicRole(options) {
248
248
  const stepped = await runPrepared(parse, (env) => runPublicInstructionSeatResume(
249
249
  {
250
250
  runId: resumeRunId,
251
+ ...options.resumeRunDirectory === void 0 ? {} : { runDirectory: options.resumeRunDirectory },
251
252
  summons: {
252
253
  instruction,
253
254
  instructionEmpty: instruction.length === 0
@@ -264,10 +265,13 @@ async function summonPublicRole(options) {
264
265
  result = stepped.ok;
265
266
  }
266
267
  const stderr = captured?.stderrText();
267
- const runDirectory = result.terminal !== void 0 && result.terminal.roleOutcome.role !== options.role && typeof result.terminal.runId === "string" ? await (await import("./public-cli/run-lifecycle.js")).findRunDirectoryById(
268
- home,
269
- result.terminal.runId
270
- ) : result.admitted?.runDirectory;
268
+ let runDirectory = result.admitted?.runDirectory;
269
+ if (result.terminal !== void 0 && result.terminal.roleOutcome.role !== options.role && typeof result.terminal.runId === "string" && result.admitted?.runId !== result.terminal.runId) {
270
+ runDirectory = await (await import("./public-cli/run-lifecycle.js")).findRunDirectoryById(
271
+ home,
272
+ result.terminal.runId
273
+ ) ?? runDirectory;
274
+ }
271
275
  return {
272
276
  exitCode: result.exitCode,
273
277
  ...result.terminal === void 0 ? {} : { terminal: result.terminal },
@@ -348,8 +352,12 @@ async function summonGateOfficer(options) {
348
352
  }
349
353
  let gateReviewInstruction;
350
354
  if (options.reask === void 0 && options.submission !== void 0) {
351
- const { readableGateItem } = await import("./readable-gate-item.js");
352
- gateReviewInstruction = readableGateItem(options.submission);
355
+ const { isTicketCourtCountersignSource } = await import("./run-terminal-artifacts.js");
356
+ const blindTicketCourt = options.officer === "notary" && isTicketCourtCountersignSource(options.sourceRunDirectory);
357
+ if (!blindTicketCourt) {
358
+ const { readableGateItem } = await import("./readable-gate-item.js");
359
+ gateReviewInstruction = readableGateItem(options.submission);
360
+ }
353
361
  }
354
362
  const { runIdFromRunDirectory } = await import("./run-terminal-artifacts.js");
355
363
  const parentRunId = runIdFromRunDirectory(options.sourceRunDirectory);
@@ -5,20 +5,15 @@ export const REVIEW_SUBMISSION_OUTPUT_TOOL_NAME = "ak_submission_output";
5
5
  /** Single schema copy of the review queue words. Runtime membership reads this set. */
6
6
  export const REVIEW_QUEUE_WORDS = ["converged", "continue", "escalate"];
7
7
  export const REVIEW_QUEUE_STATUSES = new Set(REVIEW_QUEUE_WORDS);
8
- /**
9
- * One object root. Codex structured output rejects a root anyOf, and status
10
- * stays a required three-state enum with no omitted-status failure branch.
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
- */
14
- export const reviewSubmissionSchema = withTerminatingOutputDeclarations(Type.Object({
15
- status: Type.Union([
16
- Type.Literal(REVIEW_QUEUE_WORDS[0]),
17
- Type.Literal(REVIEW_QUEUE_WORDS[1]),
18
- Type.Literal(REVIEW_QUEUE_WORDS[2]),
19
- ], {
20
- description: "审核席本轮裁决的判别状态。",
21
- }),
8
+ const reviewStatusField = Type.Union([
9
+ Type.Literal(REVIEW_QUEUE_WORDS[0]),
10
+ Type.Literal(REVIEW_QUEUE_WORDS[1]),
11
+ Type.Literal(REVIEW_QUEUE_WORDS[2]),
12
+ ], {
13
+ description: "审核席本轮裁决的判别状态。",
14
+ });
15
+ /** Shared optional review narrative fields (generation guidance only). */
16
+ const reviewNarrativeFields = {
22
17
  findings: Type.Optional(Type.Unknown({ description: "审核发现,原样留存" })),
23
18
  reason: Type.Optional(Type.Unknown({ description: "上呈理由" })),
24
19
  violations: Type.Optional(Type.Unknown({ description: "违规条目" })),
@@ -28,4 +23,42 @@ export const reviewSubmissionSchema = withTerminatingOutputDeclarations(Type.Obj
28
23
  note: Type.Optional(Type.Unknown({ description: "裁决附注" })),
29
24
  evidence: Type.Optional(Type.Unknown({ description: "留存证据" })),
30
25
  decisionGate: Type.Optional(Type.Unknown({ description: "需人权威处置的问题与选项;可说明 question、options,原样留存" })),
26
+ };
27
+ /**
28
+ * One object root. Codex structured output rejects a root anyOf, and status
29
+ * stays a required three-state enum with no omitted-status failure branch.
30
+ * External-dependency failure while the seat can still submit:
31
+ * docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md
32
+ */
33
+ export const reviewSubmissionSchema = withTerminatingOutputDeclarations(Type.Object({
34
+ status: reviewStatusField,
35
+ ...reviewNarrativeFields,
36
+ }, { additionalProperties: true }));
37
+ /**
38
+ * #1195 ticket-court clause row: original ticket text, owner uuid (or null),
39
+ * and one-line derivation. Dual readings of runner context live in derivation.
40
+ * Generation-required on countersign/notary only; package still records as-is.
41
+ */
42
+ export const courtClauseItemSchema = Type.Object({
43
+ clause: Type.String({
44
+ description: "票面条款原文(改变实际行为的条款;不合并、不改写)",
45
+ }),
46
+ ownerUuid: Type.Union([Type.String(), Type.Null()], {
47
+ description: "推出该条的陛下原话 uuid;无原话则 null",
48
+ }),
49
+ derivation: Type.String({
50
+ description: "一句推导:从原话哪个意思到该条款。用作上下文的 runner 句先写至少两种读法,再写票面取哪种;写不出第二种才记无歧义",
51
+ }),
52
+ }, { additionalProperties: true });
53
+ export const courtClausesField = Type.Array(courtClauseItemSchema, {
54
+ description: "票面逐条核旨表:每条改变实际行为的条款的原文、陛下原话 uuid、推导。无原话则 ownerUuid 为 null。",
55
+ });
56
+ /**
57
+ * Countersign + Notary generation schema (#1195): shared review face plus
58
+ * required top-level clauses. Package execute does not reject a missing table.
59
+ */
60
+ export const courtReviewSubmissionSchema = withTerminatingOutputDeclarations(Type.Object({
61
+ status: reviewStatusField,
62
+ ...reviewNarrativeFields,
63
+ clauses: courtClausesField,
31
64
  }, { additionalProperties: true }));
@@ -12,7 +12,7 @@ import { dirname, join } from "node:path";
12
12
  import { activationBookDirectory, physicalPathIdentity } from "./activation-ledger-topology.js";
13
13
  import { AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE } from "./compliance-transport.js";
14
14
  import { readRoleRunIdentity } from "./public-cli/run-lifecycle.js";
15
- import { findRoleRunDirectory, roleRunPlacement } from "./role-run-placement.js";
15
+ import { roleRunPlacement } from "./role-run-placement.js";
16
16
  import { INVOCATION_PAGE_PATH_FIELDS, PRINCIPAL_SESSION_PATH_FIELDS, RUN_STATE_PAGE_PATH_FIELDS, projectTurnRequestLiveRunDirectory, rewriteAdmittedRoleRunPage, rewritePrincipalSessionPaths, rewriteRunDirectoryPathFields, rewriteRunDirectoryPathFieldsAgainstRewrites, rewriteRunDirectoryPathValue, rewriteRunDirectoryPathValueAgainstRewrites, rewriteSummonsMaterials, } from "./role-run-path-rewrite.js";
17
17
  import { readPageSync, updateSectionSync } from "./run-dossier.js";
18
18
  import { isEnoent, isRecord } from "./unknown-value.js";
@@ -51,8 +51,9 @@ export async function retainedRunPathsMatch(recordedPath, currentPath, currentRu
51
51
  if (rewriteRunDirectoryPathValue(recordedPathIdentity, unbound.runDirectory, runDirectory) !== currentPathIdentity) {
52
52
  return false;
53
53
  }
54
- const placed = await findRoleRunDirectory([directory], identity.runId, identity.role);
55
- return placed !== undefined && physicalPathIdentity(placed) === runDirectory;
54
+ // The current volume's identity and the unbound→ticket rewrite already
55
+ // establish this relationship; do not select that volume again by bare ID.
56
+ return true;
56
57
  }
57
58
  return false;
58
59
  }