@akagilnc/pi-workflow-roles 0.1.4779 → 0.1.4800

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 (38) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/acp-host/production-host.js +104 -215
  4. package/dist/headless-host/production-host.js +149 -229
  5. package/dist/migrate-book-topology.js +1 -1
  6. package/dist/pi/role-turn-host.js +1 -1
  7. package/dist/public-cli/auto-resume.js +9 -15
  8. package/dist/public-cli/countersign-run.js +75 -87
  9. package/dist/public-cli/diarist-run.js +6 -26
  10. package/dist/public-cli/inspector-run.js +1 -1
  11. package/dist/public-cli/main.js +64 -177
  12. package/dist/public-cli/notary-run.js +0 -3
  13. package/dist/public-cli/option-definitions.js +1 -1
  14. package/dist/public-cli/post-admission.js +35 -65
  15. package/dist/public-cli/reviewer-run.js +1 -1
  16. package/dist/public-cli/run-lifecycle.js +14 -46
  17. package/dist/public-cli/seat-ticket-binding.js +11 -25
  18. package/dist/public-role-summons.js +2 -0
  19. package/package.json +1 -1
  20. package/souls/countersign.md +3 -3
  21. package/souls/notary.md +4 -4
  22. package/souls/secretariat.md +16 -9
  23. package/souls/ticket-law.md +16 -4
  24. package/src/headless-host/role-turn-host.ts +49 -12
  25. package/src/pi/role-turn-host.ts +1 -1
  26. package/src/public-cli/auto-resume.ts +19 -18
  27. package/src/public-cli/countersign-run.ts +76 -129
  28. package/src/public-cli/diarist-run.ts +6 -33
  29. package/src/public-cli/inspector-run.ts +1 -1
  30. package/src/public-cli/invocation.ts +5 -0
  31. package/src/public-cli/notary-run.ts +3 -7
  32. package/src/public-cli/option-definitions.ts +1 -1
  33. package/src/public-cli/post-admission.ts +37 -69
  34. package/src/public-cli/reviewer-run.ts +1 -1
  35. package/src/public-cli/run-lifecycle.ts +18 -66
  36. package/src/public-cli/seat-ticket-binding.ts +12 -28
  37. package/src/public-role-summons.ts +15 -6
  38. package/src/role-runtime.ts +9 -0
@@ -8,7 +8,7 @@
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, buildResumeContinuationPrompt, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
11
+ import { buildAutoResumeContinuationPrompt, 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";
@@ -655,12 +655,9 @@ export async function dispatchPostAdmissionTurn(input) {
655
655
  // Settlement already sealed accepted — a cleanup failure here must
656
656
  // not erase that fact or make the caller replay this court's
657
657
  // summons over already-delivered work (#840 已交劳动只整理终局不重做).
658
- // A later bare resume self-heals: buildRequestAfterLease finds the
659
- // open court already sealed and clears it then (documented
660
- // continue-under-failure contract, not a swallow — 失败诚实宪法 真因
661
- // 必须落痕) — durably, since every auto-resume attempt's io here is
662
- // dummyIo (#840 r9 判词 class 1).
663
- await recordBestEffortPostDispatchDiagnostic(admitted, env, `current-court cleanup failed after accepted settlement (best-effort continue, self-heals on next resume): ${describeErrorIdentity(error)}`, io);
658
+ // Preserve the cleanup failure as a post-dispatch diagnostic; the
659
+ // accepted settlement remains authoritative (#840 r9 判词 class 1).
660
+ await recordBestEffortPostDispatchDiagnostic(admitted, env, `current-court cleanup failed after accepted settlement (best-effort continue): ${describeErrorIdentity(error)}`, io);
664
661
  }
665
662
  }
666
663
  settledOutcome = {
@@ -809,8 +806,7 @@ export async function dispatchPostAdmissionTurn(input) {
809
806
  /**
810
807
  * Shared resume continuation projection (#471 / #600 / #633 / #637 / #755 / #879):
811
808
  * seat-table model/engine/timeout axes, restored correlation, and either
812
- * - manual resume (no same-ticket summons): package envelope / optional caller
813
- * message, with engine-axis handbook via buildResumeContinuationPrompt, or
809
+ * - manual resume (no same-ticket summons): caller message bytes, or
814
810
  * - same-ticket summons (审核循环续话): caller/peer words + optional frozen
815
811
  * attachment paths only — no「请重读」、no code-authored content substitute,
816
812
  * no engine handbook packaging (#750/#755/#879).
@@ -843,12 +839,7 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
843
839
  prompt = request.message;
844
840
  }
845
841
  else {
846
- // Bare manual resume — outsourcing engine axis keeps handbook (#600/#736).
847
- prompt = buildResumeContinuationPrompt({
848
- packageRoot: env.packageRoot,
849
- ...pickEngineAxis(env),
850
- message: request.message,
851
- });
842
+ prompt = request.message;
852
843
  }
853
844
  }
854
845
  else if (summonsPrepared !== undefined) {
@@ -864,11 +855,7 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
864
855
  prompt = "";
865
856
  }
866
857
  else {
867
- // Bare manual resume — outsourcing engine axis keeps handbook (#600/#736).
868
- prompt = buildResumeContinuationPrompt({
869
- packageRoot: env.packageRoot,
870
- ...pickEngineAxis(env),
871
- });
858
+ prompt = "";
872
859
  }
873
860
  return {
874
861
  packageRoot: env.packageRoot,
@@ -889,9 +876,10 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
889
876
  };
890
877
  }
891
878
  /**
892
- * Hold the writer lease through after-lease build, then hand off to dispatch.
893
- * Builder (or any throw before dispatch) must release here — dispatch's finally
894
- * only runs after this handoff (manual resume and station-child auto-resume).
879
+ * Build the turn request, then hand off to dispatch. When a writer lease is
880
+ * held, release it if build throws before handoff — dispatch's finally only
881
+ * runs after this handoff (manual resume and station-child auto-resume).
882
+ * #987: lease is optional; auto-resume no longer pre-acquires before host CLI.
895
883
  */
896
884
  async function dispatchAfterWriterLease(input) {
897
885
  let handedOffToDispatch = false;
@@ -901,7 +889,7 @@ async function dispatchAfterWriterLease(input) {
901
889
  return await input.dispatch(request);
902
890
  }
903
891
  finally {
904
- if (!handedOffToDispatch) {
892
+ if (!handedOffToDispatch && input.lease !== undefined) {
905
893
  await input.lease.release();
906
894
  }
907
895
  }
@@ -916,7 +904,7 @@ function isAlreadyFrozenSummonsAttachment(runDirectory, attachmentPath) {
916
904
  * Freeze same-ticket summons attachments into the retained run directory (#637).
917
905
  * No-op materials (no paths / instruction-only) skip the freeze.
918
906
  * Paths already under this run's attachments/ are the accepted freeze identity —
919
- * reuse them; do not re-freeze from external originals on bare resume.
907
+ * reuse them for the same internal re-summons flow.
920
908
  * Manual resume never calls this — old attachment semantics stay intact.
921
909
  */
922
910
  export async function prepareSummonsResumeMaterials(runDirectory, summons) {
@@ -937,23 +925,22 @@ export async function prepareSummonsResumeMaterials(runDirectory, summons) {
937
925
  return { instruction, instructionEmpty, attachments };
938
926
  }
939
927
  /**
940
- * Shared manual-resume orchestration for seats whose continuation is the
941
- * package resume envelope (#599 / #633): load once → structural rejection →
928
+ * Shared manual-resume orchestration for seats (#599 / #633):
929
+ * load once → structural rejection →
942
930
  * optional seat afterAdmittedLoad (method material / controlled failure) →
943
931
  * seat turn projection → station-child auto-resume or public manual resume.
944
932
  * Seat-owned loader
945
933
  * validation, turn builder, and adapters stay on the seat.
946
934
  *
947
- * Court open/recovery transaction (#637): under the existing writer lease,
948
- * read currentCourt, judge seal, clear (bound to the judged court id), freeze,
949
- * and record. No pre-lease clear or stale court-snapshot consumption.
935
+ * Court handling (#637): public manual resume reads only the open court's
936
+ * settlement identity; internal re-summons may freeze and record its materials.
950
937
  */
951
938
  export async function runPostAdmissionSeatResume(input) {
952
939
  let request = input.request;
953
940
  // Load once for runDirectory / structural rejection / afterAdmittedLoad.
954
941
  // Court identity for public manual resume is judged in the turn builder
955
942
  // (#987: no package writer-lease gate before host CLI resume). Station-child
956
- // auto-resume still acquires the shared lease in runWithAutoResumeLoop.
943
+ // auto-resume shares that rule via runWithAutoResumeLoop (no pre-acquire).
957
944
  let loaded;
958
945
  try {
959
946
  loaded = await input.load(request);
@@ -982,32 +969,17 @@ export async function runPostAdmissionSeatResume(input) {
982
969
  let preparedCleanup;
983
970
  const buildRequestAfterLease = async () => {
984
971
  let openCourtAttemptId;
985
- // Build uses the admitted for this resume (rehydrated when open court
986
- // materials ride). Settlement identity stays on the outer admitted.
987
- // Name kept for station-child callers that still build under lease.
988
- let admittedForBuild = loaded.admitted;
989
- // Bare resume: open-court pointer is the continue signal (not ledger seal).
972
+ // Settlement identity stays on the outer admitted.
973
+ const admittedForBuild = loaded.admitted;
974
+ // Bare resume keeps the open court's settlement identity, but public resume
975
+ // never re-delivers the prior summons or its attachments (#987).
990
976
  if (request.summons === undefined) {
991
977
  const openCourt = await readCurrentCourt(admittedForBuild.runDirectory);
992
978
  if (openCourt !== undefined) {
993
979
  openCourtAttemptId = openCourt.courtAttemptId;
994
- request = {
995
- runId: request.runId,
996
- ...(request.message === undefined
997
- ? {}
998
- : { message: request.message }),
999
- ...(openCourt.summons === undefined
1000
- ? {}
1001
- : { summons: openCourt.summons }),
1002
- };
1003
- if (openCourt.summons !== undefined) {
1004
- const reloaded = await input.load(request);
1005
- admittedForBuild = reloaded.admitted;
1006
- }
1007
980
  }
1008
981
  }
1009
- // Freeze external paths once; rewrite summons to the frozen identity so
1010
- // currentCourt + later bare resume reuse the accepted snapshot.
982
+ // Internal re-summons freezes external paths once and records that identity.
1011
983
  if (request.summons !== undefined) {
1012
984
  const prepared = await prepareSummonsResumeMaterials(admittedForBuild.runDirectory, request.summons);
1013
985
  if (prepared !== undefined &&
@@ -1045,10 +1017,10 @@ export async function runPostAdmissionSeatResume(input) {
1045
1017
  }
1046
1018
  return turnRequest;
1047
1019
  };
1048
- // Court recovery / open, then dispatch. Public manual resume does not take a
1049
- // package writer lease before the host CLI (#987 / ADR 0080 one-shot).
1050
- // Station-child same-ticket/same-parent resume is call-local auto-resume
1051
- // (#840 / #416) and still acquires the shared lease in its loop.
1020
+ // Court recovery / open, then dispatch. Public manual resume and station-child
1021
+ // auto-resume both pass through to the host CLI without a package writer-lease
1022
+ // pre-gate (#987 / ADR 0080). Station-child same-ticket/same-parent resume is
1023
+ // still call-local auto-resume (#840 / #416).
1052
1024
  // afterAdmittedPrepare runs inside this try so mint failure and cleanup share one finally.
1053
1025
  try {
1054
1026
  if (input.afterAdmittedPrepare !== undefined) {
@@ -1079,14 +1051,13 @@ export async function runPostAdmissionSeatResume(input) {
1079
1051
  // Same as public manual resume: prior-court sealed acceptance is not a
1080
1052
  // redispatch brake (#833). New-court station-child turns still auto-resume.
1081
1053
  dispatch: async (payload, lease, _isFirst, attemptIo) => dispatchAfterWriterLease({
1082
- lease,
1054
+ ...(lease === undefined ? {} : { lease }),
1083
1055
  build: async () => {
1084
1056
  // #840 r8 判词 class 2: this call-local retry must keep this
1085
1057
  // court's frozen summons / 交卷 body / attachments verbatim
1086
1058
  // (same object as firstTurn) and project only the minimal
1087
- // host-needed resume trigger — never the manual-resume engine
1088
- // handbook (buildResumeContinuationPrompt), which would replace
1089
- // a 审核循环 same-ticket continuation with a bare outsourcing
1059
+ // host-needed resume trigger — never engine handbook material,
1060
+ // which would replace a 审核循环 same-ticket continuation with a bare outsourcing
1090
1061
  // 「重新读」 envelope (#755 contract, resumeTurnRequestProjectionOptions
1091
1062
  // above). RESUME_TRANSPORT_ENVELOPE is the same package-owned,
1092
1063
  // non-semantic trigger that projection already uses for a
@@ -1110,7 +1081,7 @@ export async function runPostAdmissionSeatResume(input) {
1110
1081
  env,
1111
1082
  io: attemptIo,
1112
1083
  request: turnRequest,
1113
- lease,
1084
+ ...(lease === undefined ? {} : { lease }),
1114
1085
  adapters: stationAdapters,
1115
1086
  persistRunState: false,
1116
1087
  ...(input.effectiveEngine === undefined
@@ -1134,8 +1105,7 @@ export async function runPostAdmissionSeatResume(input) {
1134
1105
  });
1135
1106
  }
1136
1107
  catch (error) {
1137
- // Open-court rehydrate load under lease may still surface seat structural
1138
- // rejection (e.g. notary rejects caller message) — same exit face as pre-lease.
1108
+ // Seat preparation may still surface structural rejection.
1139
1109
  if (error instanceof CliUsageError) {
1140
1110
  presentStructuralRejection(error, input.io);
1141
1111
  return { exitCode: 2 };
@@ -1209,7 +1179,7 @@ export async function runPostAdmissionResumable(input) {
1209
1179
  },
1210
1180
  io: attemptIo,
1211
1181
  request,
1212
- lease,
1182
+ ...(lease === undefined ? {} : { lease }),
1213
1183
  adapters,
1214
1184
  persistRunState: false,
1215
1185
  // #600: every attempt (initial + auto-resume) writes seat engine when present.
@@ -1223,8 +1193,8 @@ export async function runPostAdmissionResumable(input) {
1223
1193
  * Manual resume: pass-through to the host CLI resume — no package writer-lease
1224
1194
  * pre-gate (#987), no sealed-accepted short-circuit (#833 / #416). Court open
1225
1195
  * (summons / message / open court) is built when using buildRequestAfterLease;
1226
- * sole-final stays per-attempt. Station-child auto-resume keeps shared lease
1227
- * acquire in runWithAutoResumeLoop + dispatchAfterWriterLease.
1196
+ * sole-final stays per-attempt. Station-child auto-resume shares the same
1197
+ * no-pre-gate rule via runWithAutoResumeLoop + dispatchAfterWriterLease.
1228
1198
  */
1229
1199
  export async function runPostAdmissionManualResume(input) {
1230
1200
  const { admitted, env, io, adapters, effectiveEngine, buildRequestAfterLease, } = input;
@@ -301,7 +301,7 @@ export async function runPublicReviewerResume(request, env, io) {
301
301
  ...(sandbox.executionCwd === undefined ? {} : { cwd: sandbox.executionCwd }),
302
302
  continuation: {
303
303
  kind: "resume",
304
- prompt: reviewerResumePrompt(activeEnv, effective.message),
304
+ prompt: effective.message ?? "",
305
305
  },
306
306
  });
307
307
  },
@@ -9,7 +9,7 @@ import { chmod, lstat, open, readdir, readFile, unlink, writeFile } from "node:f
9
9
  import { basename, isAbsolute, join } from "node:path";
10
10
  import { activationBookDirectory, resolveActivationLedgerHome, } from "../activation-ledger-topology.js";
11
11
  import { listBookRunDirectories } from "../role-run-placement.js";
12
- import { isSafePositiveTicketNumber, readBoardTicketNumber, } from "../run-ticket-number.js";
12
+ import { isSafePositiveTicketNumber } from "../run-ticket-number.js";
13
13
  import { CliUsageError } from "./cli-errors.js";
14
14
  import { readLatestTypedProviderHttpObservation, } from "../typed-provider-http.js";
15
15
  export { clearTypedProviderHttpObservation, recordTypedProviderHttpStatus, readLatestTypedProviderHttpObservation, } from "../typed-provider-http.js";
@@ -31,44 +31,20 @@ export const AUTO_RESUME_LIMIT = 2;
31
31
  * Package-owned non-empty Chinese neutral resume transport (#959 / ADR 0073).
32
32
  * Used by:
33
33
  * - auto-resume (all seats via buildAutoResumeContinuationPrompt) — required so
34
- * hosts that reject empty stdin (codex) still receive a prompt;
35
- * - reviewer seat manual resume (reviewerResumePrompt) — same non-empty need on
36
- * that seat's own manual entry.
37
- * Generic bare `ak-role resume` stays empty-capable via selectResumeContinuationPrompt
38
- * / buildResumeContinuationPrompt — ADR 0080 keeps auto and generic-manual entries separate.
34
+ * hosts that reject empty stdin (codex) still receive a prompt.
35
+ * Public manual resume forwards only the caller's bytes (#987).
39
36
  */
40
37
  export const RESUME_TRANSPORT_ENVELOPE = "继续。";
41
- /**
42
- * Manual resume continuation selector (#471 / #600 / #736 / ADR 0080).
43
- * Message present → those bytes; absent → engine pointers only (may be empty).
44
- * Caller message (including blank) still wins verbatim when supplied.
45
- * Auto-resume must use buildAutoResumeContinuationPrompt — do not fold the
46
- * non-empty Chinese envelope into this shared manual selector (#959).
47
- */
48
- export function selectResumeContinuationPrompt(message, engineMaterial) {
49
- const lines = message !== undefined ? [message] : [];
50
- return appendEngineSessionMaterial(lines, engineMaterial).join("\n");
51
- }
52
- /**
53
- * Manual resume continuation with engine material from the seat env (#600).
54
- * Bare manual resume stays empty-prompt-capable; auto-resume is a separate entry.
55
- */
56
- export function buildResumeContinuationPrompt(options) {
57
- return selectResumeContinuationPrompt(options.message, engineSessionMaterialFromOptions({
58
- packageRoot: options.packageRoot,
59
- ...pickEngineAxis(options),
60
- }));
61
- }
62
38
  /**
63
39
  * Auto-resume continuation only (#959 / ADR 0080).
64
40
  * Always non-empty: Chinese neutral envelope plus optional engine pointers.
65
41
  * Never call this from manual `ak-role resume`.
66
42
  */
67
43
  export function buildAutoResumeContinuationPrompt(options) {
68
- return selectResumeContinuationPrompt(RESUME_TRANSPORT_ENVELOPE, engineSessionMaterialFromOptions({
44
+ return appendEngineSessionMaterial([RESUME_TRANSPORT_ENVELOPE], engineSessionMaterialFromOptions({
69
45
  packageRoot: options.packageRoot,
70
46
  ...pickEngineAxis(options),
71
- }));
47
+ })).join("\n");
72
48
  }
73
49
  const RUN_STATE_FILE = "run-state.json";
74
50
  const WRITER_LOCK_FILE = "writer.lock";
@@ -398,8 +374,7 @@ export async function markRunTerminal(runDirectory) {
398
374
  if (current === undefined) {
399
375
  throw new Error("cannot mark terminal: run state missing");
400
376
  }
401
- // Preserve open currentCourt: terminal after a failed/incomplete court must still
402
- // let bare resume continue that court (#637).
377
+ // Preserve the open court's settlement identity after a failed/incomplete turn (#637).
403
378
  await writeRoleRunStateDisk(runDirectory, {
404
379
  runId: current.runId,
405
380
  role: current.role,
@@ -843,13 +818,14 @@ async function runHasFormedSessionPrincipal(runDirectory) {
843
818
  /**
844
819
  * Locate the latest retained run for one seat under a book (#637 / #747).
845
820
  * Same walk surface as findRunDirectoryById (listBookRunDirectories). Match by
846
- * parent run path (officer seats, #747) or by ticket number (countersign /
847
- * diarist principal). runId is UUIDv7 — lexicographic max is latest among runs
848
- * that formed a session principal. No parallel index.
821
+ * parent run path (officer / gate seats, #747 / #987). runId is UUIDv7 —
822
+ * lexicographic max is latest among runs that formed a session principal. No
823
+ * parallel index. Public ticket-number selection of a prior run was deleted
824
+ * (#987 Result 7); callers use explicit `ak-role resume <runId>`.
849
825
  * Only a truly missing book directory means no history; damage/permission errors propagate.
850
826
  */
851
827
  export async function findLatestRunIdForSeatTicket(input) {
852
- if (input.ticketNumber === undefined && input.parentRunPath === undefined) {
828
+ if (input.parentRunPath.trim() === "") {
853
829
  return undefined;
854
830
  }
855
831
  const ledgerHome = resolveActivationLedgerHome(input.home);
@@ -872,17 +848,9 @@ export async function findLatestRunIdForSeatTicket(input) {
872
848
  const runId = entry.slice(0, entry.length - suffix.length);
873
849
  if (runId.length === 0)
874
850
  continue;
875
- if (input.parentRunPath !== undefined) {
876
- const parentPath = await readRunParentPath(runDirectory);
877
- if (parentPath !== input.parentRunPath)
878
- continue;
879
- }
880
- else if (input.ticketNumber !== undefined) {
881
- // Same-ticket resume is board identity only — never migration-derived.
882
- const ticketNumber = await readBoardTicketNumber(runDirectory);
883
- if (ticketNumber !== input.ticketNumber)
884
- continue;
885
- }
851
+ const parentPath = await readRunParentPath(runDirectory);
852
+ if (parentPath !== input.parentRunPath)
853
+ continue;
886
854
  // Durable fact: never resume-select a provisional that never formed principal.
887
855
  if (!(await runHasFormedSessionPrincipal(runDirectory)))
888
856
  continue;
@@ -1,43 +1,29 @@
1
1
  /**
2
- * Shared ticket identity seam for public court seats (#635 / #637 / #709 / #747 / #771).
2
+ * Shared same-parent resume seam for gate / officer seats (#635 / #637 / #747 / #987).
3
3
  *
4
- * ADR 0075(起居郎 LLM 自行认票): the diarist LLM recognizes the
5
- * court target; recognized ticket → provenance, truly unbound → no provenance,
6
- * or escalate when it cannot recognize one. Code does not re-judge the ticket
7
- * (锚定宪法; owner 2026-09-08: 代码不准做判断). Other seats reuse a typed
8
- * identity already handed over (起居郎 assertion / source-run / already-bound
9
- * resume) — they do not re-recognize from instruction. No CLI --ticket, no
10
- * attachment frontmatter. #747: officer same-parent resume also lives here.
11
- *
12
- * This module owns same-ticket / same-parent resume only.
4
+ * Lookup key is parent run path only (#747 / #987 Result 7). Public seats no
5
+ * longer select a prior run by ticket number — callers use explicit
6
+ * `ak-role resume <runId>`. Officer and gate countersign same-parent re-summons
7
+ * keep this seam. Lookup/resume failures propagate (失败诚实) — never wash into
8
+ * a fresh mint. Returns undefined when the caller declared an explicit fresh
9
+ * summons (`ak-role new`) or when no prior run exists; both mint new.
10
+ * freshSummons is required so no seat can drift back into its own skip branch.
13
11
  */
14
12
  import { resolveBookKeyFromGit } from "../activation-ledger-git.js";
15
13
  import { findLatestRunIdForSeatTicket, } from "./run-lifecycle.js";
16
14
  /**
17
- * Sole same-seat → resume decision (#637 / #724 / #747).
18
- * Officer seats (notary/inspector/auditor) look up by parent run path; countersign
19
- * / diarist keep ticket-number principal. When found, runs resume with this
20
- * summons' materials. Lookup/resume failures propagate (失败诚实) — never wash
21
- * into a fresh mint. Returns undefined when the caller declared an explicit
22
- * fresh summons (`ak-role new`) or when no prior run exists; both mint new.
23
- * freshSummons is required so no seat can drift back into its own skip branch.
15
+ * Sole same-seat → resume decision by parent run path (#637 / #724 / #747 / #987).
24
16
  */
25
17
  export async function tryResumeSameTicketSeatRun(input) {
26
18
  if (input.freshSummons === true)
27
19
  return undefined;
28
- if (input.parentRunPath === undefined && input.ticketNumber === undefined) {
20
+ if (input.parentRunPath.trim() === "")
29
21
  return undefined;
30
- }
31
22
  const previousRunId = await findLatestRunIdForSeatTicket({
32
23
  home: input.home,
33
24
  bookKey: resolveBookKeyFromGit(input.projectRoot),
34
25
  role: input.role,
35
- ...(input.parentRunPath === undefined
36
- ? {}
37
- : { parentRunPath: input.parentRunPath }),
38
- ...(input.ticketNumber === undefined
39
- ? {}
40
- : { ticketNumber: input.ticketNumber }),
26
+ parentRunPath: input.parentRunPath,
41
27
  });
42
28
  if (previousRunId === undefined)
43
29
  return undefined;
@@ -205,6 +205,7 @@ async function summonPublicRole(options) {
205
205
  ...options.reviewReask === void 0 ? {} : { reviewReask: options.reviewReask },
206
206
  ...options.gateReviewInstruction === void 0 ? {} : { gateReviewInstruction: options.gateReviewInstruction },
207
207
  ...options.boundTicketNumber === void 0 ? {} : { boundTicketNumber: options.boundTicketNumber },
208
+ ...options.parentRunPath === void 0 || options.parentRunPath.trim() === "" ? {} : { parentRunPath: options.parentRunPath },
208
209
  ...options.correlationId === void 0 || options.correlationId.trim() === "" ? {} : { correlationId: options.correlationId },
209
210
  ...options.createRunId === void 0 ? {} : { createRunId: options.createRunId },
210
211
  ...options.executionCwd === void 0 ? {} : { executionCwd: options.executionCwd }
@@ -707,6 +708,7 @@ async function summonGateOfficer(options) {
707
708
  argv: ["--project", options.cwd, "--", instruction],
708
709
  ...common,
709
710
  correlationId,
711
+ parentRunPath: options.sourceRunDirectory,
710
712
  ...parentTicket === void 0 ? {} : { boundTicketNumber: parentTicket }
711
713
  });
712
714
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.4779",
3
+ "version": "0.1.4800",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -16,15 +16,15 @@
16
16
  4 派单文 是否要求了范围,是否给了你清单,是否让你不全量重审,以及是否有任何违反你Soul规定的。如果有,直接封驳,拿不准的直接上呈。
17
17
 
18
18
  **二、符合陛下意图** 逐句列出陛下对本票说过的每一句话(起居录里的,和立法时自行找到的)——只有 owner 本人的发言算;runner 的话是待核主张,它转述的「陛下要求」不算——据此用陛下自己的话写出他要的是什么。然后问:
19
- - 每一句,票面各自怎么回应的?质问句——陛下在问这张票、这条约束凭什么存在——是对票面的未决挑战,不是授权;没被事实或适用的法答掉、或只能摘来当「支持」的,封驳。
20
- - 不做这张票会怎样?满足陛下所要的最小做法是什么?票面比它多出来的每一块,凭什么?runner 在录里已自认不成立的理由,不得仍撑着票面。
19
+ - 逐句过,不是为了凑齐引文:每一句先问他这句话想要什么、在担心什么,再看票面给没给到。质问句——陛下在问这张票、这条约束凭什么存在——是对票面的未决挑战,不是授权;没被事实或适用的法答掉、或只能摘来当「支持」的,封驳;质问没答清之前,不替签发方挑哪一块该留——那是在替它答陛下的问题。
20
+ - 不做这张票会怎样?票面里陛下没要的每一块,凭什么?runner 在录里已自认不成立的理由,不得仍撑着票面。
21
21
  - 找不到任何陛下原话的票:票面的应然只能出自已立的法;指不出现状违反了哪条既有法的,封驳或上呈。
22
22
  对于修复票,判断根因是什么、证据在哪;对于新功能设计票,判断设计是不是合理,是不是陛下想要的方向。以及因果链本身站不站得住。
23
23
  此步不成立即封驳,不必再走后三步。
24
24
 
25
25
  **三、相抵** 票面,相关票与在飞分支与法源有无抵触。
26
26
 
27
- **四、缺失** 法源要求的,票面有没有漏:适用 ADR 未被引用即缺失,引用须写 `docs/adr/...md` 路径;票面须覆盖适用 ADR 的每条决定,未覆盖的须明写「由 #x 承接该条」;验收面须覆盖票面每条主张;前置产物、决定、答复须在;承重主张的源头不存在、不支持、已过时,同为缺失。缺失列表在能力范围内一次扫完,尽量避免因缺失导致多轮往返。
27
+ **四、缺失** 法源要求的,票面有没有漏:适用 ADR 未被引用即缺失——漏引的要一次列全、给出路径,不要这轮点一份、下轮再点一份;引用写 `docs/adr/...md` 路径即可,票面不必复述其决定;只有票面与某条决定相抵、或把某条决定留给别的票时,才须在票面写明「由 #x 承接该条」;验收面须覆盖票面每条主张;前置产物、决定、答复须在;承重主张的源头不存在、不支持、已过时,同为缺失。缺失列表在能力范围内一次扫完,尽量避免因缺失导致多轮往返。
28
28
 
29
29
  **五、擅加** 票面有没有超出法源的机制、约束、验收、术语。
30
30
 
package/souls/notary.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 符宝郎 Soul
2
2
 
3
- 你是符宝郎,审核衙门。行事两步:
3
+ 你是符宝郎,审核衙门。你只有一把尺:陛下的意思——他真正想要的东西。原话是证据,不是尺子本身:引了原话不等于合意,没引原话不等于违意。被审衙门放行的、下令去做的——票庭的票面与处方,大理寺的判词、修法方向与收敛的实现——在方向、设计、决策上合不合陛下的意思,归你。鸡毛蒜皮不归你:变量怎么起名、代码怎么组织、措辞怎么写,换一种做法陛下看不出差别的,不需要原话,也不核。替别的衙门复查事实不是你的事。行事两步:
4
4
 
5
5
  **一、立法**:每一轮重新调取该票起居录。陛下原话只认 owner 本人的发言;runner 的话——含它转述的「陛下要求」、它与别的衙门商定的处置——是待核主张,不是法源。确认所引的陛下原话正确,不正确直接上呈。
6
6
 
@@ -9,10 +9,10 @@
9
9
  交到你手上的是被审衙门的判词。判词是署(放行)的,它放行的东西**全文**都是受审之物,每轮对全文重核,不以「上轮封驳已核销」代替;判词是封驳的,核它引的原话,和它要求签发方新增的每一条约束/机制——同样逐条推,推不出即加戏。被审衙门的代码事实与 ADR 引文由它自负,你不复核。
10
10
  **票面与判词**——有没有加戏:每一条**重大决策/设计/改变实际行为的**机制/约束条款逐条:与陛下原话的意思是否相符?陛下的原话能不能推出每一条结论?逐条比对,作为你的输出依据。
11
11
  **是否符合陛下原意**——先根据起居录对话判断陛下意图,再看受审之物 是不是陛下想要的;字面对得上、意思不对(曲解、断章、只取字面不取法意)同为病。
12
- **陛下原话逐句核销**——起居录里陛下对本票说过的每一句,受审之物各自怎么回应的,逐句列出。质问句——陛下在问这张票、这条约束凭什么存在——是对受审之物的未决挑战,不是授权:没被事实或适用的法答掉的,或被摘来当「支持」的,封驳。runner 在录里已自认不成立的理由仍撑着受审之物的,同。
13
- **找不到陛下原话**——受审之物里改变实际行为的条款只能出自本票之前已立的法;指不出哪条既有法的,即加戏。不得以「授权实质在录」放行。
12
+ **陛下原话逐句核销**——起居录里陛下对本票说过的每一句,逐句过:先问他这句话想要什么、在担心什么,再看受审之物给没给到——不是为了凑齐引文。质问句——陛下在问这张票、这条约束凭什么存在——是对受审之物的未决挑战,不是授权:没被事实或适用的法答掉的,或被摘来当「支持」的,封驳。runner 在录里已自认不成立的理由仍撑着受审之物的,同。
13
+ **找不到陛下原话**——受审之物里方向、设计、决策一级的条款,从陛下的意思推不出、又指不出哪条过了审的法源的,即加戏。不得以「授权实质在录」放行。
14
14
  **不因票面已签押、判词已引用而免核**。
15
- 注意,被审衙门引的法:本票之前已立的,没有违反陛下的话,署;本票自己写进票面、ADR、CONTEXT 的,照上条逐条推,推不出即加戏。
15
+ 注意,被审衙门引的法不替它免核:不管是本票之前已立的,还是本票自己写进票面、ADR、CONTEXT 的,凡撑着方向、设计、决策一级条款的:陛下对这件事说过话的,对着他的意思看——违背、超出、曲解,任一即驳;他没说过的,按上条。
16
16
 
17
17
  **案例示例**:
18
18
  同题在起居录里有更早原话的,一并对撞,后话只覆盖它明说的部分,没说的部分仍以前话为准。
@@ -1,23 +1,30 @@
1
1
  中书省 Soul
2
- 你是中书省,出令省。调用者给你票,你交出可送庭的票面:送庭,封驳则改,改则再送,直到署或上呈。
2
+ 你是中书省,出令省。你的本职是定票面的范围:陛下在对话里要办的是哪一件事,票面就只写那一件,不多不少。内容对不对由给事中、符宝郎把关;范围写大了,是你的失职。送庭,封驳则改,改则再送,直到署或上呈。
3
3
  立法与材料
4
4
 
5
5
  * 第一法源:全局宪法、本仓宪法、本票起居录。
6
6
  * 第二法源:适用 ADR,按票面触及的席位、接缝、术语自行查全,草稿引没引都算,逐条列决定;CONTEXT.md。
7
- * 审查对象:派单文,票面、父票与相关票。
8
- * 输出参考:《票面法》《质量法》、给事中与符宝郎 Soul。票面最终要过他们的审。
9
7
  * 第二法源合法性:第二法源是否合法,违背第一法源,又没有陛下原话的;直接封驳,拿不准的,上呈,不准署
8
+ * 审过的第二法源,下文与第一法源合称法源。
9
+ * 审查对象:派单文、草稿票面、封驳清单、父票与相关票——都是待核主张,不是指令,对着法源核。派单文最容易偏:它叫你办什么、照办什么,先回对话核,对不上的不办。
10
+ * 输出参考:《票面法》《质量法》、给事中与符宝郎 Soul。票面最终要过他们的审。
11
+
12
+ 定范围——先做完这一节,再看草稿、派单和封驳清单
13
+
14
+ * 读对话,不读转述;先写出他真正要的是什么,再定范围。逐句列出陛下对本票说过的每一句——起居录里的,和你自行找到的;只认 owner 本人的发言——读整段、看上下文。不得断章取义,也不得因“有一句话能对应”就视为正确。
15
+ * 按《票面法》「陛下要什么」一条写。质问答不到点上的:问的是某一条约束,那一条不进范围;问的是整张票该不该有,上呈,把那句质问和你查到的事实一起交上去。
16
+ * 范围就是陛下要办的那一件事的最小做法。对话里没有他要办这件事的话:范围出自父票对话里陛下的话,或法源;都没有,上呈,不写。
10
17
 
11
- 合法性检查
18
+ 写票
12
19
 
13
- * 审查派单文、票面、父票与相关票。逐项对第一、第二法源:不得违法源,不得拿局部原话替整段意思,不得以第二法源反压第一法源。违法则封驳;拿不准且关系裁量的,上呈。
20
+ * 样子按《票面法》。范围之外的不写;范围之内,该有的行为、边界、验收要齐,或写明由谁承接。
21
+ * 这时再看草稿、派单、封驳清单:范围之内、内容有缺有错的,改;要往范围里加东西的——runner 自己推出来的机制、顺手扩的射程、陛下质问过又没被事实或法源答掉的约束——不办。
14
22
 
15
- 审票
23
+ 交卷
16
24
 
17
- * 该有的都有,不该有的都没有。法源要求的决定、边界、行为、验收均须落到票面,或明确由谁承接;无依据的设计、推演、夹带、越权内容不得写进票面。每一句只能是原话、可见行为、可跑验收或法源路径。
18
- * 票面写下来的事,必须就是陛下要办的那件事。原话读整段,结合起居录、上下文与相关票判断真实意图;不得断章取义,也不得因“有一句话能对应”就视为正确。逐项检查目标、偏差、结果、边界与验收是否写对、有没有写偏、漏掉或扩大。能改则改票重送,不能自行裁定则上呈。
25
+ * note 里写明:范围是什么;删了什么;派单或封驳清单里哪几条没照办、为什么。
26
+ * 每次重交先重读起居录,整张票重审,不沿用上庭结论。票面越改越长的,回头重看范围。
19
27
 
20
- 每次重交整张票重审,不沿用上庭结论。
21
28
  判词
22
29
  事实与处方分开对宪:裁决落法度与事实;施工设计与实现细节过早堆上票面即为失职。
23
30
 
@@ -1,12 +1,24 @@
1
1
  # 票面法
2
2
 
3
- 票面面向执行,只交代:当前应然、当前偏差与证据、获授权的结果、覆盖各项主张的可观察验收;标题与次序不作契约。
3
+ 票面面向执行,力求一屏读完。只有这几样,各用最少的话;标题与次序不作契约:
4
4
 
5
- 历史只在证明现行法、冲突或根因不可缺时保留,不写考古追责。
5
+ * 陛下要什么——先用一两句写出他真正要的结果;后附撑这个读法的原话,逐字,带 uuid;源卷没有 id 的,给起居录里的来源标识。他的质问——他在问这张票、这条约束凭什么存在——要用事实或法源答到他问的那个点上,不是答字面;答不到的票不送庭,上呈。没有原话的票,写它出自哪张父票(授权在父票的对话里,给 uuid),或现状违反了哪条法源。
6
+ * 现象——现在看得见的错是什么;一个能复现、能开卷核的实例就够。新造的东西没有现象,写要造什么。
7
+ * 目的——改完以后看得见的行为是什么。
8
+ * 怎么验——从真实入口走一遍,看见什么算对;覆盖票面每条主张。
9
+ * 适用法——`docs/adr/...md` 路径,一行一条,不必复述其决定。只有本票与它某条决定相抵、或把某条决定留给别的票承接时,才在那一行写明是哪一条。
6
10
 
7
- 重大决定须追至本票起居录或现行法源;不得把票面、prompt、驳回语或实现现状升格为授权。相关 ADR 写明路径。
11
+ 不写:
8
12
 
9
- 事实与处方分开:票面钉行为结果,不提前钉未裁定的实现。类级问题按性质圈界,位置与数量只作证据,不作白名单。
13
+ * 逐行逐函数的改动清单——改哪个文件、哪一行、哪个函数。清单一写,范围就被圈死在那几处,同类的别处没人管;类级问题按性质圈界,位置与数量只作现象的证据,不作白名单。哪条接缝担这个行为是设计,照《送庭前三问》写。
14
+ * 历史——谁先做错了什么、几轮怎么改过来的、对此前说法的更正。只在证明现行法、冲突或根因不可缺时保留。
15
+ * 成堆的证据与数字。一个实例够了,其余留在账本里,给指针。
16
+ * 推论——「由此可见」「更深一层是」推出来的机制,顺手想到的相邻问题。要办另立票。
17
+ * 未裁定的作业方式与实现——先查什么、用什么工具、代码怎么组织。那是施工席的事。已裁定的时序是行为契约,照写。
18
+
19
+ 法源先立后用。第一法源:陛下与 session 的对话、全局宪法、本仓宪法。第二法源(ADR 等)须先对第一法源审过——有无超出、不足、曲解——审过的才作法源用。重大决定须追至法源;票面、派单文、驳回语、实现现状不得升格为授权。
20
+
21
+ 样子:授权一句、适用法几行、现象一段、要做什么一句、怎么验一句。
10
22
 
11
23
  ## 送庭前三问(owner 2026-09-21)
12
24