@akagilnc/pi-workflow-roles 0.1.5762 → 0.1.5797
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.
- package/dist/acp-host/production-host.js +685 -285
- package/dist/external-host-turn-loop.js +3 -0
- package/dist/headless-host/production-host.js +658 -258
- package/dist/migrate-book-topology.js +70 -29
- package/dist/navigator-attendance.js +1 -1
- package/dist/packaged-role-registry.js +3 -2
- package/dist/pi/role-turn-host.js +6 -1
- package/dist/public-cli/instruction-seat-run.js +152 -60
- package/dist/public-cli/invocation.js +98 -46
- package/dist/public-cli/main.js +482 -222
- package/dist/public-cli/option-definitions.js +4 -4
- package/dist/public-cli/post-admission.js +19 -28
- package/dist/public-cli/run-lifecycle.js +10 -21
- package/dist/public-cli/turn-request.js +3 -0
- package/dist/public-role-summons.js +11 -8
- package/dist/report-ticket-tool.js +3 -3
- package/dist/role-run-placement.js +14 -2
- package/dist/role-run-relocation.js +2 -2
- package/dist/run-dossier.js +5 -4
- package/dist/run-terminal-artifacts.js +0 -7
- package/dist/session-identity.js +3 -2
- package/dist/sitian-appender.js +12 -3
- package/dist/submission-ledger.js +24 -0
- package/dist/ticket-progress.js +306 -0
- package/dist/user-dialogue-stdin.js +23 -17
- package/package.json +1 -1
- package/src/external-host-turn-loop.ts +7 -1
- package/src/host-contracts.ts +1 -1
- package/src/navigator-attendance.ts +2 -1
- package/src/packaged-role-registry.ts +3 -2
- package/src/pi/adapter.ts +57 -45
- package/src/pi/role-turn-host.ts +6 -1
- package/src/prepared-role-turn.ts +6 -0
- package/src/public-cli/instruction-seat-run.ts +166 -68
- package/src/public-cli/invocation.ts +120 -46
- package/src/public-cli/option-definitions.ts +5 -4
- package/src/public-cli/post-admission.ts +19 -30
- package/src/public-cli/run-lifecycle.ts +10 -27
- package/src/public-cli/turn-request.ts +3 -0
- package/src/public-role-summons.ts +27 -16
- package/src/report-ticket-tool.ts +7 -3
- package/src/role-envelope.ts +5 -0
- package/src/role-run-placement.ts +19 -2
- package/src/role-run-relocation.ts +2 -2
- package/src/role-runtime.ts +19 -2
- package/src/run-dossier.ts +5 -3
- package/src/run-terminal-artifacts.ts +0 -8
- package/src/session-identity.ts +3 -2
- package/src/sitian-appender.ts +14 -3
- package/src/submission-ledger.ts +27 -0
- package/src/ticket-progress.ts +383 -0
- package/src/user-dialogue-stdin.ts +21 -14
|
@@ -75,7 +75,6 @@ import {
|
|
|
75
75
|
} from "./post-admission.ts";
|
|
76
76
|
import { presentTerminal } from "./auto-resume.ts";
|
|
77
77
|
import {
|
|
78
|
-
buildAutoResumeContinuationPrompt,
|
|
79
78
|
loadResumablePublicRole,
|
|
80
79
|
markRunAdmitted,
|
|
81
80
|
readCurrentCourt,
|
|
@@ -138,6 +137,20 @@ const POST_SUBMISSION_ROUTING: Partial<Record<PackagedRole, {
|
|
|
138
137
|
judge: {
|
|
139
138
|
statuses: REVIEW_QUEUE_STATUSES,
|
|
140
139
|
},
|
|
140
|
+
// #1208 C1 / ADR 0055: every review-queue seat uses the shared
|
|
141
|
+
// unreadable-status seam (officerConclusionReask). Independent public
|
|
142
|
+
// entry for notary/auditor/inspector was the same miss — route here, do
|
|
143
|
+
// not invent a parallel loop. Gate summons still own the outer budget
|
|
144
|
+
// via nestedIncomplete when this seat exhausts unsettled.
|
|
145
|
+
notary: {
|
|
146
|
+
statuses: REVIEW_QUEUE_STATUSES,
|
|
147
|
+
},
|
|
148
|
+
auditor: {
|
|
149
|
+
statuses: REVIEW_QUEUE_STATUSES,
|
|
150
|
+
},
|
|
151
|
+
inspector: {
|
|
152
|
+
statuses: REVIEW_QUEUE_STATUSES,
|
|
153
|
+
},
|
|
141
154
|
coder: {
|
|
142
155
|
statuses: WORKER_ROUTING_STATUSES,
|
|
143
156
|
reask: WORKER_STATUS_REASK,
|
|
@@ -271,11 +284,14 @@ async function reaskUnreadablePostSubmissionStatus(
|
|
|
271
284
|
if (message === undefined) return undefined;
|
|
272
285
|
const next = withUnreadableReask(env);
|
|
273
286
|
if (next === undefined) return UNREADABLE_REASK_EXHAUSTED;
|
|
287
|
+
// Named status-reask: summons opens a new court; message carries the diagnostic
|
|
288
|
+
// (not arbitrary summons.instruction passthrough through the review resolver).
|
|
274
289
|
return runPublicInstructionSeatResume(
|
|
275
290
|
{
|
|
276
291
|
runId: admitted.runId,
|
|
277
292
|
runDirectory: admitted.runDirectory,
|
|
278
|
-
|
|
293
|
+
message,
|
|
294
|
+
summons: { instructionEmpty: true },
|
|
279
295
|
},
|
|
280
296
|
next,
|
|
281
297
|
io,
|
|
@@ -329,11 +345,14 @@ async function reaskMissingTicketOnce(
|
|
|
329
345
|
softTicketReaskTurn: true,
|
|
330
346
|
softTicketReaskOwnership,
|
|
331
347
|
};
|
|
348
|
+
// Named #1171 missing-ticket: summons opens a new court; message carries the
|
|
349
|
+
// package reask copy (not arbitrary summons.instruction passthrough).
|
|
332
350
|
const result = await runPublicInstructionSeatResume(
|
|
333
351
|
{
|
|
334
352
|
runId: admitted.runId,
|
|
335
353
|
runDirectory: admitted.runDirectory,
|
|
336
|
-
|
|
354
|
+
message: instruction,
|
|
355
|
+
summons: { instructionEmpty: true },
|
|
337
356
|
},
|
|
338
357
|
softReaskEnv,
|
|
339
358
|
io,
|
|
@@ -505,8 +524,8 @@ export function buildInstructionSeatTurnRequest(
|
|
|
505
524
|
|
|
506
525
|
/**
|
|
507
526
|
* Direct review-seat call: reconnect ledger peer-body delivery (ADR 0085 / #1166).
|
|
508
|
-
* Gate summons already ride gateReviewInstruction; direct --source-run
|
|
509
|
-
*
|
|
527
|
+
* Gate summons already ride gateReviewInstruction; direct --source-run falls
|
|
528
|
+
* through the shared dialogue resolver (notary: fixed dispatch).
|
|
510
529
|
*/
|
|
511
530
|
async function loadLedgerPeerBody(
|
|
512
531
|
sourceRunDirectory: string,
|
|
@@ -529,48 +548,59 @@ async function loadLedgerPeerBody(
|
|
|
529
548
|
return readableGateItem(latest.accepted);
|
|
530
549
|
}
|
|
531
550
|
|
|
532
|
-
/** Owner-approved fixed Notary dispatch (2026-10-09); never the parent verdict. */
|
|
551
|
+
/** Owner-approved fixed Notary review dispatch (2026-10-09 #1208); never the parent verdict. */
|
|
533
552
|
export const NOTARY_FIXED_DISPATCH = "请审本轮受审对象,按符宝郎职掌交卷。";
|
|
534
553
|
|
|
535
554
|
/**
|
|
536
555
|
* One delivery rule for review-seat dialogue on new turns and same-parent resume
|
|
537
|
-
* (#1166 / ADR 0085 / ADR 0087):
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
556
|
+
* (#1166 / ADR 0085 / ADR 0087 / #1208): notary review uses fixed dispatch;
|
|
557
|
+
* named operational reasks ride reviewReask (first-mint / gate) or the resume
|
|
558
|
+
* request message seam — never arbitrary non-empty caller/summons passthrough.
|
|
559
|
+
* Other seats prefer format reask, then gate body, then caller dispatch, then
|
|
560
|
+
* ledger peer. Identity stays in startup materials. Notary never takes
|
|
561
|
+
* gate/parent verdict as dialogue (gateReviewInstruction stays unread for notary).
|
|
541
562
|
*/
|
|
542
|
-
async function resolveReviewSeatDialogueBody(input: {
|
|
563
|
+
export async function resolveReviewSeatDialogueBody(input: {
|
|
543
564
|
readonly role?: string;
|
|
544
|
-
readonly
|
|
565
|
+
readonly reviewReask?: string;
|
|
566
|
+
readonly gateReviewInstruction?: string;
|
|
545
567
|
readonly callerInstruction?: string;
|
|
546
568
|
readonly sourceRunPath?: string;
|
|
547
569
|
readonly projectRoot: string;
|
|
548
570
|
readonly home: string;
|
|
549
571
|
}): Promise<string | undefined> {
|
|
550
|
-
|
|
551
|
-
|
|
572
|
+
// #1208: fixed sentence is the review function only (owner 37bfe80d /
|
|
573
|
+
// 393daf98). reviewReask is the named operational override; caller/summons
|
|
574
|
+
// body is not a free passthrough (countersign rejected that prescription).
|
|
575
|
+
if (input.role === "notary") {
|
|
576
|
+
if (input.reviewReask !== undefined) return input.reviewReask;
|
|
577
|
+
return NOTARY_FIXED_DISPATCH;
|
|
578
|
+
}
|
|
579
|
+
const preferred = input.reviewReask ?? input.gateReviewInstruction;
|
|
580
|
+
if (preferred !== undefined && preferred.length > 0) return preferred;
|
|
552
581
|
const caller = input.callerInstruction ?? "";
|
|
553
582
|
if (caller.length > 0) return caller;
|
|
554
583
|
const sourceRunPath = admittedSourceRunPath(input)?.trim();
|
|
555
584
|
if (sourceRunPath === undefined) return undefined;
|
|
556
|
-
if (input.role === "notary") {
|
|
557
|
-
const { isTicketCourtCountersignSource } = await import("../run-terminal-artifacts.ts");
|
|
558
|
-
if (isTicketCourtCountersignSource(sourceRunPath)) return NOTARY_FIXED_DISPATCH;
|
|
559
|
-
}
|
|
560
585
|
return await loadLedgerPeerBody(sourceRunPath, input.projectRoot, input.home);
|
|
561
586
|
}
|
|
562
587
|
|
|
563
|
-
|
|
588
|
+
/**
|
|
589
|
+
* Initial host prompt for this turn (#1199: progress instruction = these bytes).
|
|
590
|
+
*/
|
|
591
|
+
async function resolveInitialTurnPrompt(
|
|
564
592
|
admitted: AdmittedRoleInvocation,
|
|
565
593
|
env: InstructionSeatRunEnv,
|
|
566
594
|
): Promise<string> {
|
|
567
595
|
const record = roleRecord(admitted.role);
|
|
568
596
|
if ("reaskPrompt" in record && record.reaskPrompt === true) {
|
|
569
597
|
const sourceRunPath = admittedSourceRunPath(admitted);
|
|
570
|
-
const reask = env.reviewReask ?? env.gateReviewInstruction;
|
|
571
598
|
const body = await resolveReviewSeatDialogueBody({
|
|
572
599
|
role: admitted.role,
|
|
573
|
-
...(
|
|
600
|
+
...(env.reviewReask === undefined ? {} : { reviewReask: env.reviewReask }),
|
|
601
|
+
...(env.gateReviewInstruction === undefined
|
|
602
|
+
? {}
|
|
603
|
+
: { gateReviewInstruction: env.gateReviewInstruction }),
|
|
574
604
|
callerInstruction: admitted.instruction,
|
|
575
605
|
...(sourceRunPath === undefined ? {} : { sourceRunPath }),
|
|
576
606
|
projectRoot: admitted.projectRoot,
|
|
@@ -699,9 +729,10 @@ async function dispatchAdmitted(
|
|
|
699
729
|
if (activeEnv.correlationId !== undefined && activeEnv.correlationId.trim() !== "") {
|
|
700
730
|
await recordAdmittedCorrelation(admitted, activeEnv.correlationId);
|
|
701
731
|
}
|
|
702
|
-
const
|
|
732
|
+
const initialPrompt = await resolveInitialTurnPrompt(admitted, activeEnv);
|
|
703
733
|
const auto = "inCallAutoResume" in record && record.inCallAutoResume === true;
|
|
704
734
|
if (auto) {
|
|
735
|
+
// #1208: auto-resume resends this turn's actual content, never a filler phrase.
|
|
705
736
|
return await runPostAdmissionResumable({
|
|
706
737
|
admitted,
|
|
707
738
|
env: activeEnv,
|
|
@@ -710,14 +741,14 @@ async function dispatchAdmitted(
|
|
|
710
741
|
admitted,
|
|
711
742
|
roleTurnOptions(activeEnv, admitted, {
|
|
712
743
|
kind: "initial",
|
|
713
|
-
prompt:
|
|
744
|
+
prompt: initialPrompt,
|
|
714
745
|
}),
|
|
715
746
|
),
|
|
716
747
|
buildResumeRequest: () => buildInstructionSeatTurnRequest(
|
|
717
748
|
admitted,
|
|
718
749
|
roleTurnOptions(activeEnv, admitted, {
|
|
719
750
|
kind: "resume",
|
|
720
|
-
prompt:
|
|
751
|
+
prompt: initialPrompt,
|
|
721
752
|
}),
|
|
722
753
|
),
|
|
723
754
|
adapters,
|
|
@@ -732,7 +763,7 @@ async function dispatchAdmitted(
|
|
|
732
763
|
admitted,
|
|
733
764
|
roleTurnOptions(activeEnv, admitted, {
|
|
734
765
|
kind: "initial",
|
|
735
|
-
prompt:
|
|
766
|
+
prompt: initialPrompt,
|
|
736
767
|
}),
|
|
737
768
|
),
|
|
738
769
|
adapters,
|
|
@@ -851,21 +882,28 @@ function resumeSameParentInstructionSeat(input: {
|
|
|
851
882
|
...(input.ticketNumber === undefined ? {} : { ticketNumber: input.ticketNumber }),
|
|
852
883
|
freshSummons: input.env.freshSummons,
|
|
853
884
|
summons: input.summons,
|
|
854
|
-
resume: (runId, materials, runDirectory) =>
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
885
|
+
resume: (runId, materials, runDirectory) => {
|
|
886
|
+
// Named operational reask on this summon: explicit message seam.
|
|
887
|
+
// Review dialogue stays on summons → shared resolver (notary: fixed).
|
|
888
|
+
// Do not stuff reask into summons.instruction for later passthrough.
|
|
889
|
+
const reask = input.env.reviewReask;
|
|
890
|
+
return runPublicInstructionSeatResume(
|
|
891
|
+
{
|
|
892
|
+
runId,
|
|
893
|
+
runDirectory,
|
|
894
|
+
...(reask === undefined ? {} : { message: reask }),
|
|
895
|
+
...(materials === undefined ? {} : { summons: materials }),
|
|
896
|
+
},
|
|
897
|
+
input.env,
|
|
898
|
+
input.io,
|
|
899
|
+
);
|
|
900
|
+
},
|
|
863
901
|
});
|
|
864
902
|
}
|
|
865
903
|
|
|
866
904
|
/**
|
|
867
|
-
* Same delivery rule as
|
|
868
|
-
*
|
|
905
|
+
* Same delivery rule as resolveInitialTurnPrompt for same-parent resume summons.
|
|
906
|
+
* Shared resolver owns the input choice — callers only pass env fields.
|
|
869
907
|
*/
|
|
870
908
|
async function sameParentDialogue(
|
|
871
909
|
env: InstructionSeatRunEnv,
|
|
@@ -874,10 +912,12 @@ async function sameParentDialogue(
|
|
|
874
912
|
projectRoot: string,
|
|
875
913
|
role: string,
|
|
876
914
|
): Promise<{ readonly instruction: string; readonly instructionEmpty: boolean }> {
|
|
877
|
-
const reask = env.reviewReask ?? env.gateReviewInstruction;
|
|
878
915
|
const body = await resolveReviewSeatDialogueBody({
|
|
879
916
|
role,
|
|
880
|
-
...(
|
|
917
|
+
...(env.reviewReask === undefined ? {} : { reviewReask: env.reviewReask }),
|
|
918
|
+
...(env.gateReviewInstruction === undefined
|
|
919
|
+
? {}
|
|
920
|
+
: { gateReviewInstruction: env.gateReviewInstruction }),
|
|
881
921
|
callerInstruction: fallback.instruction,
|
|
882
922
|
sourceRunPath,
|
|
883
923
|
projectRoot,
|
|
@@ -898,16 +938,27 @@ async function runCountersignBody(
|
|
|
898
938
|
env: InstructionSeatRunEnv,
|
|
899
939
|
io: CliIo,
|
|
900
940
|
): Promise<SeatRunResult> {
|
|
941
|
+
const projectRoot = resolve(parsed.project ?? env.cwd);
|
|
901
942
|
const gateParentRunPath =
|
|
902
943
|
typeof env.parentRunPath === "string" && env.parentRunPath.trim() !== ""
|
|
903
944
|
? env.parentRunPath
|
|
904
945
|
: undefined;
|
|
905
946
|
if (gateParentRunPath !== undefined) {
|
|
906
|
-
|
|
947
|
+
// #1208 C1: same shared dialogue resolver as every other same-parent seat.
|
|
948
|
+
const dialogue = await sameParentDialogue(
|
|
949
|
+
env,
|
|
950
|
+
{
|
|
951
|
+
instruction: parsed.instruction ?? "",
|
|
952
|
+
instructionEmpty: (parsed.instruction ?? "").length === 0,
|
|
953
|
+
},
|
|
954
|
+
gateParentRunPath,
|
|
955
|
+
projectRoot,
|
|
956
|
+
"countersign",
|
|
957
|
+
);
|
|
907
958
|
const resumed = await resumeSameParentInstructionSeat({
|
|
908
959
|
env,
|
|
909
960
|
io,
|
|
910
|
-
projectRoot
|
|
961
|
+
projectRoot,
|
|
911
962
|
role: "countersign",
|
|
912
963
|
parentRunPath: gateParentRunPath,
|
|
913
964
|
...(isSafePositiveTicketNumber(env.boundTicketNumber)
|
|
@@ -915,8 +966,8 @@ async function runCountersignBody(
|
|
|
915
966
|
: {}),
|
|
916
967
|
summons: {
|
|
917
968
|
sourceRunPath: gateParentRunPath,
|
|
918
|
-
instruction:
|
|
919
|
-
instructionEmpty:
|
|
969
|
+
instruction: dialogue.instruction,
|
|
970
|
+
instructionEmpty: dialogue.instructionEmpty,
|
|
920
971
|
// #1166 J11: same-parent resume delivers this call's file-flag paths
|
|
921
972
|
// through the shared summons → appendCallerFileFlagPaths rule.
|
|
922
973
|
attachmentPaths: parsed.attachmentPaths ?? [],
|
|
@@ -954,14 +1005,10 @@ async function runCountersignBody(
|
|
|
954
1005
|
await persistAdmittedSourceRunPath(admitted, gateParentRunPath);
|
|
955
1006
|
admitted = { ...admitted, sourceRunPath: gateParentRunPath };
|
|
956
1007
|
}
|
|
957
|
-
// #1166 J11:
|
|
958
|
-
// resolveInitialPrompt / resumeTurnRequestProjectionOptions — never body alone.
|
|
959
|
-
const reviewBody = env.reviewReask ?? env.gateReviewInstruction;
|
|
1008
|
+
// #1208 C1 / #1166 J11: one shared initial-prompt seam (incl. file flags).
|
|
960
1009
|
const turnProjection = roleTurnOptions(env, admitted, {
|
|
961
1010
|
kind: "initial",
|
|
962
|
-
prompt:
|
|
963
|
-
? appendCallerFileFlagPaths(reviewBody, admitted.attachments)
|
|
964
|
-
: buildInstructionTransportPrompt(admitted),
|
|
1011
|
+
prompt: await resolveInitialTurnPrompt(admitted, env),
|
|
965
1012
|
});
|
|
966
1013
|
const turnRequest = buildInstructionSeatTurnRequest(admitted, turnProjection);
|
|
967
1014
|
const result = await runPostAdmissionOneShot({
|
|
@@ -1187,6 +1234,10 @@ export async function runPublicInstructionSeat(
|
|
|
1187
1234
|
await persistAdmittedSourceRunPath(admitted, auditorSource, auditorSubject);
|
|
1188
1235
|
admitted = { ...admitted, sourceRunPath: auditorSource } as AdmittedRoleInvocation;
|
|
1189
1236
|
}
|
|
1237
|
+
// #1208 C2: same helper as resume — known board ticket on the audited
|
|
1238
|
+
// source binds and relocates before the missing-ticket soft reask can
|
|
1239
|
+
// fire. True no-ticket sources stay unbound (#1171 asks once).
|
|
1240
|
+
admitted = await inheritKnownTicketFromSource(admitted, env.principalAuthority);
|
|
1190
1241
|
|
|
1191
1242
|
const runAdmitted = async (): Promise<SeatRunResult> => {
|
|
1192
1243
|
await markRunAdmitted(admitted, env.principalAuthority);
|
|
@@ -1235,6 +1286,25 @@ export async function runPublicInstructionSeat(
|
|
|
1235
1286
|
return runAdmitted();
|
|
1236
1287
|
}
|
|
1237
1288
|
|
|
1289
|
+
/**
|
|
1290
|
+
* #1208 C2: same as new source-locator admission — when this leg is still
|
|
1291
|
+
* unbound, inherit the board ticket already on the audited source and relocate.
|
|
1292
|
+
* True no-ticket sources stay unbound so #1171 still asks once.
|
|
1293
|
+
*/
|
|
1294
|
+
async function inheritKnownTicketFromSource(
|
|
1295
|
+
admitted: AdmittedRoleInvocation,
|
|
1296
|
+
authority: DurablePrincipalAuthority,
|
|
1297
|
+
): Promise<AdmittedRoleInvocation> {
|
|
1298
|
+
if (admitted.ticketNumber !== undefined) return admitted;
|
|
1299
|
+
const sourcePath = admittedSourceRunPath(admitted);
|
|
1300
|
+
if (sourcePath === undefined) return admitted;
|
|
1301
|
+
const sourceTicket = await readBoardTicketNumber(sourcePath);
|
|
1302
|
+
if (sourceTicket === undefined) return admitted;
|
|
1303
|
+
await bindAdmittedTicketNumber(admitted, sourceTicket);
|
|
1304
|
+
await relocateAdmittedRunToTicket(admitted, authority);
|
|
1305
|
+
return admitted;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1238
1308
|
export async function runPublicInstructionSeatResume(
|
|
1239
1309
|
request: PublicResumeRequest,
|
|
1240
1310
|
env: InstructionSeatRunEnv,
|
|
@@ -1259,30 +1329,53 @@ export async function runPublicInstructionSeatResume(
|
|
|
1259
1329
|
env.principalAuthority,
|
|
1260
1330
|
effective.runDirectory === undefined ? undefined : { runDirectory: effective.runDirectory },
|
|
1261
1331
|
);
|
|
1332
|
+
let admitted = loaded.admitted;
|
|
1262
1333
|
if (
|
|
1263
|
-
packagedRebindSourceOnResume(
|
|
1334
|
+
packagedRebindSourceOnResume(admitted.role)
|
|
1264
1335
|
&& effective.summons?.sourceRunPath !== undefined
|
|
1265
1336
|
&& effective.summons.sourceRun !== undefined
|
|
1266
1337
|
) {
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
admitted
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
},
|
|
1274
|
-
};
|
|
1338
|
+
// source-locator seats only (notary); sourceRun is on that admission face.
|
|
1339
|
+
admitted = {
|
|
1340
|
+
...admitted,
|
|
1341
|
+
sourceRunPath: effective.summons.sourceRunPath,
|
|
1342
|
+
sourceRun: effective.summons.sourceRun,
|
|
1343
|
+
} as AdmittedRoleInvocation;
|
|
1275
1344
|
}
|
|
1276
|
-
|
|
1345
|
+
admitted = await inheritKnownTicketFromSource(admitted, env.principalAuthority);
|
|
1346
|
+
return { ...loaded, admitted };
|
|
1277
1347
|
},
|
|
1278
1348
|
buildTurnRequest: async (admitted, effective) => {
|
|
1279
1349
|
const summonsPrepared = await prepareSummonsResumeMaterials(admitted.runDirectory, effective.summons);
|
|
1280
|
-
|
|
1281
|
-
admitted,
|
|
1282
|
-
{
|
|
1283
|
-
...resumeTurnRequestProjectionOptions(admitted, effective, env, summonsPrepared),
|
|
1284
|
-
},
|
|
1350
|
+
const projection = resumeTurnRequestProjectionOptions(
|
|
1351
|
+
admitted, effective, env, summonsPrepared,
|
|
1285
1352
|
);
|
|
1353
|
+
// #1208: internal summons only — shared resolver owns review dialogue.
|
|
1354
|
+
// Public bare resume / explicit message keep projection (operational
|
|
1355
|
+
// reasks use the message seam). Do not reselect stale env dialogue over
|
|
1356
|
+
// this-round summons (#1208 R1).
|
|
1357
|
+
if (effective.summons === undefined || effective.message !== undefined) {
|
|
1358
|
+
return buildInstructionSeatTurnRequest(admitted, { ...projection });
|
|
1359
|
+
}
|
|
1360
|
+
const sourceRunPath = admittedSourceRunPath(admitted);
|
|
1361
|
+
const body = await resolveReviewSeatDialogueBody({
|
|
1362
|
+
role: admitted.role,
|
|
1363
|
+
callerInstruction: summonsPrepared?.instruction ?? "",
|
|
1364
|
+
...(sourceRunPath === undefined ? {} : { sourceRunPath }),
|
|
1365
|
+
projectRoot: admitted.projectRoot,
|
|
1366
|
+
home: env.home,
|
|
1367
|
+
});
|
|
1368
|
+
if (body === undefined) {
|
|
1369
|
+
return buildInstructionSeatTurnRequest(admitted, { ...projection });
|
|
1370
|
+
}
|
|
1371
|
+
const fileFlags = summonsPrepared?.attachments ?? [];
|
|
1372
|
+
const prompt = fileFlags.length === 0
|
|
1373
|
+
? body
|
|
1374
|
+
: appendCallerFileFlagPaths(body, fileFlags);
|
|
1375
|
+
return buildInstructionSeatTurnRequest(admitted, {
|
|
1376
|
+
...projection,
|
|
1377
|
+
continuation: { kind: "resume", prompt },
|
|
1378
|
+
});
|
|
1286
1379
|
},
|
|
1287
1380
|
adapters: {
|
|
1288
1381
|
trySettle: async () => undefined,
|
|
@@ -1361,9 +1454,12 @@ async function queueConclusionFromChild(
|
|
|
1361
1454
|
return { stop: { exitCode: 0, ...(terminal === undefined ? {} : { terminal }) } };
|
|
1362
1455
|
}
|
|
1363
1456
|
let current: AdmittedRoleInvocation = child;
|
|
1364
|
-
//
|
|
1365
|
-
//
|
|
1366
|
-
//
|
|
1457
|
+
// #1208: review-queue gate children also have seat-local
|
|
1458
|
+
// POST_SUBMISSION_ROUTING. This parent-side loop remains the
|
|
1459
|
+
// gate/correlation budget; nestedIncomplete from an exhausted seat-local
|
|
1460
|
+
// chain surfaces without a second spend. The child resume keeps the
|
|
1461
|
+
// caller's env so delivery, failure recovery, and other reask loops
|
|
1462
|
+
// stay separate.
|
|
1367
1463
|
let budgetEnv: InstructionSeatRunEnv = envForCourtContinue(env);
|
|
1368
1464
|
for (;;) {
|
|
1369
1465
|
const terminal = await trySettlePublicSeat(
|
|
@@ -1548,7 +1644,8 @@ async function auditSubmittedRole(
|
|
|
1548
1644
|
return runPublicInstructionSeatResume({
|
|
1549
1645
|
runId: admitted.runId,
|
|
1550
1646
|
runDirectory: admitted.runDirectory,
|
|
1551
|
-
|
|
1647
|
+
message: SECRETARIAT_STATUS_REASK,
|
|
1648
|
+
summons: { instructionEmpty: true },
|
|
1552
1649
|
}, next, io);
|
|
1553
1650
|
}
|
|
1554
1651
|
if (admitted.role === "countersign" && (typeof status !== "string" || !REVIEW_QUEUE_STATUSES.has(status))) {
|
|
@@ -1562,7 +1659,8 @@ async function auditSubmittedRole(
|
|
|
1562
1659
|
return runPublicInstructionSeatResume({
|
|
1563
1660
|
runId: admitted.runId,
|
|
1564
1661
|
runDirectory: admitted.runDirectory,
|
|
1565
|
-
|
|
1662
|
+
message: officerConclusionReask(status),
|
|
1663
|
+
summons: { instructionEmpty: true },
|
|
1566
1664
|
}, next, io);
|
|
1567
1665
|
}
|
|
1568
1666
|
// Self-escalation is a valid open routing state for these seats only.
|
|
@@ -23,12 +23,14 @@ import { resolveBookKeyFromGit } from "../activation-ledger-git.ts";
|
|
|
23
23
|
import {
|
|
24
24
|
ensureRoleRunDirectory,
|
|
25
25
|
ensureRoleRunPlacement,
|
|
26
|
-
formatRunLeaf,
|
|
27
26
|
isUnboundRunDirectory,
|
|
28
27
|
listBookRunDirectories,
|
|
29
28
|
roleRunPlacement,
|
|
29
|
+
sessionDirectoryOf,
|
|
30
|
+
sessionFileIn,
|
|
30
31
|
type RoleRunSubject,
|
|
31
32
|
} from "../role-run-placement.ts";
|
|
33
|
+
import { relocateTicketProgressForLeg } from "../ticket-progress.ts";
|
|
32
34
|
import type {
|
|
33
35
|
DurablePrincipal,
|
|
34
36
|
DurablePrincipalAuthority,
|
|
@@ -580,15 +582,101 @@ export async function recordAdmittedCorrelation(
|
|
|
580
582
|
mutable.correlationIds = correlationIds;
|
|
581
583
|
}
|
|
582
584
|
|
|
585
|
+
/**
|
|
586
|
+
* #1199 R2: registered 起居郎 legs that already belong on this ticket.
|
|
587
|
+
* Unbound source → whole-leg rename + publish; already under ticket → publish
|
|
588
|
+
* leftover staging only. No scan of unregistered siblings.
|
|
589
|
+
*/
|
|
590
|
+
async function relocateRegisteredChildDiarists(input: {
|
|
591
|
+
readonly parentRunDirectory: string;
|
|
592
|
+
readonly bookKey: string;
|
|
593
|
+
readonly ticketNumber: number;
|
|
594
|
+
readonly projectRoot: string;
|
|
595
|
+
readonly ledgerHome: string;
|
|
596
|
+
readonly authority: DurablePrincipalAuthority;
|
|
597
|
+
}): Promise<void> {
|
|
598
|
+
const childRunIds = readPageSync(input.parentRunDirectory, "admitted")?.childDiaristRunIds;
|
|
599
|
+
for (const childRunId of Array.isArray(childRunIds) ? childRunIds : []) {
|
|
600
|
+
if (typeof childRunId !== "string") continue;
|
|
601
|
+
const childTarget = roleRunPlacement(input.ledgerHome, {
|
|
602
|
+
bookKey: input.bookKey,
|
|
603
|
+
subject: { ticketNumber: input.ticketNumber },
|
|
604
|
+
runId: childRunId,
|
|
605
|
+
role: "diarist",
|
|
606
|
+
});
|
|
607
|
+
const unboundChildDirectory = roleRunPlacement(input.ledgerHome, {
|
|
608
|
+
bookKey: input.bookKey,
|
|
609
|
+
subject: { unbound: true },
|
|
610
|
+
runId: childRunId,
|
|
611
|
+
role: "diarist",
|
|
612
|
+
}).runDirectory;
|
|
613
|
+
|
|
614
|
+
if (existsSync(unboundChildDirectory)) {
|
|
615
|
+
input.authority.seal(childTarget);
|
|
616
|
+
await bindTicketNumberOnRunDirectory(unboundChildDirectory, input.ticketNumber);
|
|
617
|
+
await rehomeUnboundTicketProvenance(
|
|
618
|
+
unboundChildDirectory,
|
|
619
|
+
input.ticketNumber,
|
|
620
|
+
input.projectRoot,
|
|
621
|
+
homeFromRunDirectory(input.parentRunDirectory),
|
|
622
|
+
);
|
|
623
|
+
ensureRoleRunDirectory(input.ledgerHome, dirname(childTarget.runDirectory));
|
|
624
|
+
// #1199: whole-leg rename first; originals travel with the directory.
|
|
625
|
+
await rename(unboundChildDirectory, childTarget.runDirectory);
|
|
626
|
+
relocateTicketProgressForLeg({
|
|
627
|
+
runDirectory: childTarget.runDirectory,
|
|
628
|
+
});
|
|
629
|
+
// Finished child legs will not settle again; refresh derived host.original
|
|
630
|
+
// after the rename commit. A derived render fault must not undo or block
|
|
631
|
+
// the committed placement (#1161 L1) — note and keep the true cause.
|
|
632
|
+
try {
|
|
633
|
+
renderCurrentSync(childTarget.runDirectory);
|
|
634
|
+
} catch (error) {
|
|
635
|
+
process.stderr.write(
|
|
636
|
+
`[invocation] current.json render refused after child relocate; placement kept: ${childTarget.runDirectory}: ${errorText(error)}\n`,
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Prior partial relocate left the child under the ticket with staging still held.
|
|
643
|
+
if (existsSync(childTarget.runDirectory)) {
|
|
644
|
+
relocateTicketProgressForLeg({
|
|
645
|
+
runDirectory: childTarget.runDirectory,
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
583
651
|
/** File a role run under its latest typed ticket identity. */
|
|
584
652
|
export async function relocateAdmittedRunToTicket(
|
|
585
653
|
admitted: AdmittedRoleInvocation,
|
|
586
654
|
authority: DurablePrincipalAuthority,
|
|
587
655
|
heldLease?: { relocate(runDirectory: string): void },
|
|
588
656
|
): Promise<RunDirectoryRelocation | undefined> {
|
|
589
|
-
if (admitted.ticketNumber === undefined
|
|
657
|
+
if (admitted.ticketNumber === undefined) return undefined;
|
|
658
|
+
|
|
659
|
+
const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(admitted.runDirectory));
|
|
660
|
+
|
|
661
|
+
// Already under the ticket: publish leftover progress.jsonl (self + registered children).
|
|
662
|
+
if (!isUnboundRunDirectory(admitted.runDirectory)) {
|
|
663
|
+
relocateTicketProgressForLeg({
|
|
664
|
+
runDirectory: admitted.runDirectory,
|
|
665
|
+
});
|
|
666
|
+
if (admitted.role !== "diarist") {
|
|
667
|
+
await relocateRegisteredChildDiarists({
|
|
668
|
+
parentRunDirectory: admitted.runDirectory,
|
|
669
|
+
bookKey: admitted.bookKey,
|
|
670
|
+
ticketNumber: admitted.ticketNumber,
|
|
671
|
+
projectRoot: admitted.projectRoot,
|
|
672
|
+
ledgerHome,
|
|
673
|
+
authority,
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
return undefined;
|
|
677
|
+
}
|
|
678
|
+
|
|
590
679
|
const oldRunDirectory = admitted.runDirectory;
|
|
591
|
-
const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(oldRunDirectory));
|
|
592
680
|
const target = roleRunPlacement(ledgerHome, {
|
|
593
681
|
bookKey: admitted.bookKey,
|
|
594
682
|
subject: { ticketNumber: admitted.ticketNumber },
|
|
@@ -596,39 +684,18 @@ export async function relocateAdmittedRunToTicket(
|
|
|
596
684
|
role: admitted.role,
|
|
597
685
|
});
|
|
598
686
|
ensureRoleRunDirectory(ledgerHome, dirname(target.runDirectory));
|
|
599
|
-
//
|
|
600
|
-
|
|
601
|
-
const principal = authority.seal(target);
|
|
687
|
+
// Pre-flight seal; final seal after the filesystem commit.
|
|
688
|
+
authority.seal(target);
|
|
602
689
|
|
|
603
690
|
if (admitted.role !== "diarist") {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
subject: { ticketNumber: admitted.ticketNumber },
|
|
613
|
-
runId: childRunId,
|
|
614
|
-
role: "diarist",
|
|
615
|
-
});
|
|
616
|
-
authority.seal(childTarget);
|
|
617
|
-
await bindTicketNumberOnRunDirectory(childDirectory, admitted.ticketNumber);
|
|
618
|
-
await rehomeUnboundTicketProvenance(childDirectory, admitted.ticketNumber, admitted.projectRoot, homeFromRunDirectory(oldRunDirectory));
|
|
619
|
-
ensureRoleRunDirectory(ledgerHome, dirname(childTarget.runDirectory));
|
|
620
|
-
await rename(childDirectory, childTarget.runDirectory);
|
|
621
|
-
// Finished child legs will not settle again; refresh derived host.original
|
|
622
|
-
// after the rename commit. A derived render fault must not undo or block
|
|
623
|
-
// the committed placement (#1161 L1) — note and keep the true cause.
|
|
624
|
-
try {
|
|
625
|
-
renderCurrentSync(childTarget.runDirectory);
|
|
626
|
-
} catch (error) {
|
|
627
|
-
process.stderr.write(
|
|
628
|
-
`[invocation] current.json render refused after child relocate; placement kept: ${childTarget.runDirectory}: ${errorText(error)}\n`,
|
|
629
|
-
);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
691
|
+
await relocateRegisteredChildDiarists({
|
|
692
|
+
parentRunDirectory: oldRunDirectory,
|
|
693
|
+
bookKey: admitted.bookKey,
|
|
694
|
+
ticketNumber: admitted.ticketNumber,
|
|
695
|
+
projectRoot: admitted.projectRoot,
|
|
696
|
+
ledgerHome,
|
|
697
|
+
authority,
|
|
698
|
+
});
|
|
632
699
|
}
|
|
633
700
|
|
|
634
701
|
// The first ticket assignment moves only an unbound diarist's own diary.
|
|
@@ -637,21 +704,32 @@ export async function relocateAdmittedRunToTicket(
|
|
|
637
704
|
}
|
|
638
705
|
|
|
639
706
|
// Rename commits the run placement. Diary assignment above is a Sitian append
|
|
640
|
-
// outside the atomic directory move.
|
|
641
|
-
// Persisted paths are resolved from typed run identity on read.
|
|
707
|
+
// outside the atomic directory move. Session/receipts/progress travel with the leg.
|
|
642
708
|
await rename(oldRunDirectory, target.runDirectory);
|
|
643
709
|
|
|
644
710
|
// rename moved the open lock inode with the directory. Transfer cleanup and
|
|
645
|
-
//
|
|
646
|
-
//
|
|
711
|
+
// live path ownership immediately after the commit — before final seal/render
|
|
712
|
+
// (#1161 L1 / BASE order). Live handles first so mid-turn setSessionFile sees
|
|
713
|
+
// the ticket leaf; final seal uses the post-move existence landing.
|
|
647
714
|
heldLease?.relocate(target.runDirectory);
|
|
648
715
|
|
|
716
|
+
const actualSessionDirectory = sessionDirectoryOf(target.runDirectory);
|
|
717
|
+
const principal = authority.seal({
|
|
718
|
+
sessionDirectory: actualSessionDirectory,
|
|
719
|
+
sessionFile: sessionFileIn(actualSessionDirectory),
|
|
720
|
+
});
|
|
721
|
+
|
|
649
722
|
rewriteAdmittedRoleRunPage(
|
|
650
723
|
admitted as unknown as Record<string, unknown>,
|
|
651
724
|
[{ oldRunDirectory, newRunDirectory: target.runDirectory }],
|
|
652
725
|
);
|
|
653
726
|
(admitted as { principal: DurablePrincipal }).principal = principal;
|
|
654
727
|
|
|
728
|
+
// After whole-leg rename + live-handle handoff: fill rounds and append ticket current.
|
|
729
|
+
relocateTicketProgressForLeg({
|
|
730
|
+
runDirectory: target.runDirectory,
|
|
731
|
+
});
|
|
732
|
+
|
|
655
733
|
// current.json projects host.original from this run directory. Ownership is
|
|
656
734
|
// already transferred; a refuse still carries the true cause to the caller
|
|
657
735
|
// (#1161 L1) without leaving the leg under the unbound path.
|
|
@@ -1839,7 +1917,7 @@ type InstructionTransportSource = {
|
|
|
1839
1917
|
|
|
1840
1918
|
function admittedTransportPromptKind(
|
|
1841
1919
|
admitted: InstructionTransportSource,
|
|
1842
|
-
): "instruction" | "
|
|
1920
|
+
): "instruction" | "baseline" | "skill-args" {
|
|
1843
1921
|
if (admitted.role === undefined) return "instruction";
|
|
1844
1922
|
const record = packagedRoleMetadata(admitted.role);
|
|
1845
1923
|
if (record !== undefined && "transportPrompt" in record) return record.transportPrompt;
|
|
@@ -1870,19 +1948,15 @@ export function appendCallerFileFlagPaths(
|
|
|
1870
1948
|
|
|
1871
1949
|
/**
|
|
1872
1950
|
* One initial prompt transport. The registry `transportPrompt` leaf selects
|
|
1873
|
-
* a
|
|
1874
|
-
*
|
|
1951
|
+
* a bound baseline or skill args. Absent means the caller instruction plus
|
|
1952
|
+
* caller file-flag paths (ADR 0087). Notary dialogue is owned by
|
|
1953
|
+
* resolveReviewSeatDialogueBody (review fixed dispatch), not an empty transport leaf.
|
|
1875
1954
|
* Engine / outsourcing material rides startup readingMaterial (#1167), not here.
|
|
1876
1955
|
*/
|
|
1877
1956
|
export function buildInstructionTransportPrompt(
|
|
1878
1957
|
admitted: InstructionTransportSource,
|
|
1879
1958
|
): string {
|
|
1880
1959
|
const kind = admittedTransportPromptKind(admitted);
|
|
1881
|
-
if (kind === "fixed-kickoff") {
|
|
1882
|
-
// #1166 / ADR 0087: audited-run identity rides startup materials; dialogue
|
|
1883
|
-
// prompt is peer body / reask only — never a package-computed directory path.
|
|
1884
|
-
return "";
|
|
1885
|
-
}
|
|
1886
1960
|
if (kind === "baseline") {
|
|
1887
1961
|
if (admitted.baseRevision === undefined) {
|
|
1888
1962
|
throw new Error("baseline transport prompt is missing the bound revision");
|