@akagilnc/pi-workflow-roles 0.1.3629 → 0.1.3635
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 +102 -25
- package/dist/gatekeeper-role.js +3 -1
- package/dist/notary-contracts.js +1 -0
- package/dist/public-cli/inspector-run.js +6 -6
- package/dist/public-cli/instruction-seat-run.js +31 -17
- package/dist/public-cli/invocation.js +17 -0
- package/dist/public-cli/main.js +98 -20
- package/dist/public-cli/notary-run.js +4 -5
- package/dist/public-cli/run-lifecycle.js +52 -4
- package/dist/public-cli/seat-ticket-binding.js +14 -5
- package/package.json +1 -1
- package/src/acp-host/role-envelope.ts +2 -4
- package/src/gatekeeper-role.ts +3 -1
- package/src/notary-contracts.ts +2 -0
- package/src/public-cli/inspector-run.ts +6 -6
- package/src/public-cli/instruction-seat-run.ts +39 -21
- package/src/public-cli/invocation.ts +29 -0
- package/src/public-cli/notary-run.ts +4 -5
- package/src/public-cli/run-lifecycle.ts +57 -5
- package/src/public-cli/seat-ticket-binding.ts +16 -6
|
@@ -3207,6 +3207,7 @@ function notaryDecisiveFacts(output) {
|
|
|
3207
3207
|
if (status === "pass" || status === "bounce") {
|
|
3208
3208
|
const findings = output.findings;
|
|
3209
3209
|
facts.findingsCount = Array.isArray(findings) ? findings.length : 0;
|
|
3210
|
+
facts.findings = Array.isArray(findings) ? findings : [];
|
|
3210
3211
|
}
|
|
3211
3212
|
if (status === "bounce") {
|
|
3212
3213
|
facts.disposition = "rewrite";
|
|
@@ -4706,6 +4707,33 @@ async function findRunDirectoryById(home, runId) {
|
|
|
4706
4707
|
}
|
|
4707
4708
|
return void 0;
|
|
4708
4709
|
}
|
|
4710
|
+
function parentRunPathFromGatePointerInstruction(instruction) {
|
|
4711
|
+
if (!instruction.startsWith(GATE_DOSSIER_POINTER_PREFIX)) return void 0;
|
|
4712
|
+
const path = instruction.slice(GATE_DOSSIER_POINTER_PREFIX.length).trim();
|
|
4713
|
+
return path === "" ? void 0 : path;
|
|
4714
|
+
}
|
|
4715
|
+
async function readRunParentPath(runDirectory) {
|
|
4716
|
+
let raw;
|
|
4717
|
+
try {
|
|
4718
|
+
raw = JSON.parse(
|
|
4719
|
+
await readFile8(join11(runDirectory, "admitted-request.json"), "utf8")
|
|
4720
|
+
);
|
|
4721
|
+
} catch (error) {
|
|
4722
|
+
if (errorCodeOf2(error) === "ENOENT") return void 0;
|
|
4723
|
+
throw error;
|
|
4724
|
+
}
|
|
4725
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
4726
|
+
return void 0;
|
|
4727
|
+
}
|
|
4728
|
+
const record4 = raw;
|
|
4729
|
+
if (typeof record4.sourceRunPath === "string" && record4.sourceRunPath.trim() !== "") {
|
|
4730
|
+
return record4.sourceRunPath;
|
|
4731
|
+
}
|
|
4732
|
+
if (typeof record4.instruction === "string") {
|
|
4733
|
+
return parentRunPathFromGatePointerInstruction(record4.instruction);
|
|
4734
|
+
}
|
|
4735
|
+
return void 0;
|
|
4736
|
+
}
|
|
4709
4737
|
async function findLatestRunIdForSeatTicket(input) {
|
|
4710
4738
|
const ledgerHome = resolveActivationLedgerHome(input.home);
|
|
4711
4739
|
const runsDir = join11(
|
|
@@ -4725,8 +4753,16 @@ async function findLatestRunIdForSeatTicket(input) {
|
|
|
4725
4753
|
if (!entry.endsWith(suffix)) continue;
|
|
4726
4754
|
const runId = entry.slice(0, entry.length - suffix.length);
|
|
4727
4755
|
if (runId.length === 0) continue;
|
|
4728
|
-
const
|
|
4729
|
-
if (
|
|
4756
|
+
const runDirectory = join11(runsDir, entry);
|
|
4757
|
+
if (input.parentRunPath !== void 0) {
|
|
4758
|
+
const parentPath = await readRunParentPath(runDirectory);
|
|
4759
|
+
if (parentPath !== input.parentRunPath) continue;
|
|
4760
|
+
} else if (input.ticketNumber !== void 0) {
|
|
4761
|
+
const ticketNumber = await readRunTicketNumber(runDirectory);
|
|
4762
|
+
if (ticketNumber !== input.ticketNumber) continue;
|
|
4763
|
+
} else {
|
|
4764
|
+
continue;
|
|
4765
|
+
}
|
|
4730
4766
|
if (best === void 0 || runId > best) best = runId;
|
|
4731
4767
|
}
|
|
4732
4768
|
return best;
|
|
@@ -5073,7 +5109,7 @@ async function loadResumableInspectorRun(home, runId, authority) {
|
|
|
5073
5109
|
};
|
|
5074
5110
|
return seatLoadedResult(loaded, admitted);
|
|
5075
5111
|
}
|
|
5076
|
-
var V1_RESUMABLE_PROVIDERS, AUTO_RESUME_LIMIT, RESUME_TRANSPORT_ENVELOPE, RUN_STATE_FILE, WRITER_LOCK_FILE, RunWriterLeaseHeldError, WRITER_LEASE_RECLAIM_ROUNDS;
|
|
5112
|
+
var V1_RESUMABLE_PROVIDERS, AUTO_RESUME_LIMIT, RESUME_TRANSPORT_ENVELOPE, RUN_STATE_FILE, WRITER_LOCK_FILE, RunWriterLeaseHeldError, WRITER_LEASE_RECLAIM_ROUNDS, GATE_DOSSIER_POINTER_PREFIX;
|
|
5077
5113
|
var init_run_lifecycle = __esm({
|
|
5078
5114
|
"src/public-cli/run-lifecycle.ts"() {
|
|
5079
5115
|
"use strict";
|
|
@@ -5098,6 +5134,7 @@ var init_run_lifecycle = __esm({
|
|
|
5098
5134
|
}
|
|
5099
5135
|
};
|
|
5100
5136
|
WRITER_LEASE_RECLAIM_ROUNDS = 3;
|
|
5137
|
+
GATE_DOSSIER_POINTER_PREFIX = "\u5377\u5B97\u6307\u9488\uFF1A";
|
|
5101
5138
|
}
|
|
5102
5139
|
});
|
|
5103
5140
|
|
|
@@ -6287,6 +6324,7 @@ __export(invocation_exports, {
|
|
|
6287
6324
|
parseNotaryArgv: () => parseNotaryArgv,
|
|
6288
6325
|
parsePositiveTicketNumber: () => parsePositiveTicketNumber,
|
|
6289
6326
|
parseReviewerArgv: () => parseReviewerArgv,
|
|
6327
|
+
persistAdmittedSourceRunPath: () => persistAdmittedSourceRunPath,
|
|
6290
6328
|
recordEffectiveInvocationModel: () => recordEffectiveInvocationModel,
|
|
6291
6329
|
recordLaunchedPiIdentity: () => recordLaunchedPiIdentity,
|
|
6292
6330
|
recordLaunchedRolePackageIdentity: () => recordLaunchedRolePackageIdentity,
|
|
@@ -6402,6 +6440,25 @@ async function mergeInvocationIdentityPage(runDirectory, fields) {
|
|
|
6402
6440
|
"utf8"
|
|
6403
6441
|
);
|
|
6404
6442
|
}
|
|
6443
|
+
async function persistAdmittedSourceRunPath(admitted, sourceRunPath) {
|
|
6444
|
+
if (sourceRunPath.trim() === "") {
|
|
6445
|
+
throw new Error("persistAdmittedSourceRunPath requires a non-empty sourceRunPath");
|
|
6446
|
+
}
|
|
6447
|
+
const admittedPath = admitted.admittedRequestPath;
|
|
6448
|
+
const current = JSON.parse(await readFile9(admittedPath, "utf8"));
|
|
6449
|
+
if (typeof current.sourceRunPath === "string") {
|
|
6450
|
+
if (current.sourceRunPath === sourceRunPath) return;
|
|
6451
|
+
throw new Error(
|
|
6452
|
+
`persistAdmittedSourceRunPath refuses to replace ${current.sourceRunPath} with ${sourceRunPath}`
|
|
6453
|
+
);
|
|
6454
|
+
}
|
|
6455
|
+
await writeFile4(
|
|
6456
|
+
admittedPath,
|
|
6457
|
+
`${JSON.stringify({ ...current, sourceRunPath }, null, 2)}
|
|
6458
|
+
`,
|
|
6459
|
+
"utf8"
|
|
6460
|
+
);
|
|
6461
|
+
}
|
|
6405
6462
|
async function bindAdmittedTicketNumber(admitted, ticketNumber) {
|
|
6406
6463
|
if (!Number.isSafeInteger(ticketNumber) || ticketNumber < 1) {
|
|
6407
6464
|
throw new Error(`bindAdmittedTicketNumber requires a safe positive integer, got ${String(ticketNumber)}`);
|
|
@@ -10536,11 +10593,15 @@ async function resolveSeatTicketBinding(admitted, env = {}) {
|
|
|
10536
10593
|
}
|
|
10537
10594
|
async function tryResumeSameTicketSeatRun(input) {
|
|
10538
10595
|
if (input.freshSummons === true) return void 0;
|
|
10596
|
+
if (input.parentRunPath === void 0 && input.ticketNumber === void 0) {
|
|
10597
|
+
return void 0;
|
|
10598
|
+
}
|
|
10539
10599
|
const previousRunId = await findLatestRunIdForSeatTicket({
|
|
10540
10600
|
home: input.home,
|
|
10541
10601
|
bookKey: resolveBookKeyFromGit(input.projectRoot),
|
|
10542
10602
|
role: input.role,
|
|
10543
|
-
|
|
10603
|
+
...input.parentRunPath === void 0 ? {} : { parentRunPath: input.parentRunPath },
|
|
10604
|
+
...input.ticketNumber === void 0 ? {} : { ticketNumber: input.ticketNumber }
|
|
10544
10605
|
});
|
|
10545
10606
|
if (previousRunId === void 0) return void 0;
|
|
10546
10607
|
return await input.resume(previousRunId, input.summons);
|
|
@@ -10659,7 +10720,7 @@ function failureReason(error) {
|
|
|
10659
10720
|
}
|
|
10660
10721
|
function asStringArray3(value) {
|
|
10661
10722
|
if (!Array.isArray(value)) return [];
|
|
10662
|
-
return value.
|
|
10723
|
+
return value.map((item) => typeof item === "string" ? item : JSON.stringify(item));
|
|
10663
10724
|
}
|
|
10664
10725
|
function isRecord14(value) {
|
|
10665
10726
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -16961,8 +17022,7 @@ async function runPublicNotary(argv, env, io, parseNotaryArgv2) {
|
|
|
16961
17022
|
}
|
|
16962
17023
|
throw error;
|
|
16963
17024
|
}
|
|
16964
|
-
|
|
16965
|
-
if (ticketNumber !== void 0) {
|
|
17025
|
+
{
|
|
16966
17026
|
const summons = {
|
|
16967
17027
|
sourceRunPath: source.runDirectory,
|
|
16968
17028
|
sourceRun: source
|
|
@@ -16971,7 +17031,7 @@ async function runPublicNotary(argv, env, io, parseNotaryArgv2) {
|
|
|
16971
17031
|
home: env.home,
|
|
16972
17032
|
projectRoot,
|
|
16973
17033
|
role: "notary",
|
|
16974
|
-
|
|
17034
|
+
parentRunPath: source.runDirectory,
|
|
16975
17035
|
freshSummons: env.freshSummons,
|
|
16976
17036
|
summons,
|
|
16977
17037
|
resume: (runId, materials) => runPublicNotaryResume(
|
|
@@ -17086,7 +17146,6 @@ var init_notary_run = __esm({
|
|
|
17086
17146
|
init_auditor_dossier_tool();
|
|
17087
17147
|
init_notary_source_run();
|
|
17088
17148
|
init_engine_material();
|
|
17089
|
-
init_run_ticket_number();
|
|
17090
17149
|
init_cli_errors();
|
|
17091
17150
|
init_invocation();
|
|
17092
17151
|
init_seat_ticket_binding();
|
|
@@ -17132,8 +17191,8 @@ async function runPublicInspector(argv, env, io, parseInspectorArgv2) {
|
|
|
17132
17191
|
projectRoot,
|
|
17133
17192
|
env
|
|
17134
17193
|
);
|
|
17135
|
-
const
|
|
17136
|
-
if (
|
|
17194
|
+
const parentRunPath = parentRunPathFromGatePointerInstruction(parsed.instruction);
|
|
17195
|
+
if (parentRunPath !== void 0) {
|
|
17137
17196
|
const summons = {
|
|
17138
17197
|
instruction: parsed.instruction,
|
|
17139
17198
|
instructionEmpty: parsed.instruction.trim() === "",
|
|
@@ -17143,7 +17202,7 @@ async function runPublicInspector(argv, env, io, parseInspectorArgv2) {
|
|
|
17143
17202
|
home: env.home,
|
|
17144
17203
|
projectRoot,
|
|
17145
17204
|
role: "inspector",
|
|
17146
|
-
|
|
17205
|
+
parentRunPath,
|
|
17147
17206
|
freshSummons: env.freshSummons,
|
|
17148
17207
|
summons,
|
|
17149
17208
|
resume: (runId, materials) => runPublicInspectorResume(
|
|
@@ -17428,21 +17487,20 @@ async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
|
|
|
17428
17487
|
env
|
|
17429
17488
|
);
|
|
17430
17489
|
}
|
|
17431
|
-
const
|
|
17432
|
-
|
|
17433
|
-
|
|
17434
|
-
|
|
17435
|
-
|
|
17436
|
-
|
|
17437
|
-
};
|
|
17490
|
+
const summons = {
|
|
17491
|
+
instruction: parsed.instruction,
|
|
17492
|
+
instructionEmpty: parsed.instruction.trim() === "",
|
|
17493
|
+
attachmentPaths: parsed.attachmentPaths
|
|
17494
|
+
};
|
|
17495
|
+
if (role === "auditor" && auditorSourceRun !== void 0) {
|
|
17438
17496
|
const resumed = await withAuditorSoulEnv({
|
|
17439
17497
|
...auditorSubject === void 0 ? {} : { subject: auditorSubject },
|
|
17440
|
-
|
|
17498
|
+
sourceRunDirectory: auditorSourceRun,
|
|
17441
17499
|
run: () => tryResumeSameTicketSeatRun({
|
|
17442
17500
|
home: env.home,
|
|
17443
17501
|
projectRoot,
|
|
17444
17502
|
role,
|
|
17445
|
-
|
|
17503
|
+
parentRunPath: auditorSourceRun,
|
|
17446
17504
|
freshSummons: env.freshSummons,
|
|
17447
17505
|
summons,
|
|
17448
17506
|
resume: (runId, materials) => runPublicInstructionSeatResume(
|
|
@@ -17453,6 +17511,24 @@ async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
|
|
|
17453
17511
|
})
|
|
17454
17512
|
});
|
|
17455
17513
|
if (resumed !== void 0) return resumed;
|
|
17514
|
+
} else {
|
|
17515
|
+
const probedTicketNumber = ticketProbe === void 0 ? void 0 : ticketNumberFromProbe(ticketProbe);
|
|
17516
|
+
if (probedTicketNumber !== void 0) {
|
|
17517
|
+
const resumed = await tryResumeSameTicketSeatRun({
|
|
17518
|
+
home: env.home,
|
|
17519
|
+
projectRoot,
|
|
17520
|
+
role,
|
|
17521
|
+
ticketNumber: probedTicketNumber,
|
|
17522
|
+
freshSummons: env.freshSummons,
|
|
17523
|
+
summons,
|
|
17524
|
+
resume: (runId, materials) => runPublicInstructionSeatResume(
|
|
17525
|
+
{ runId, ...materials === void 0 ? {} : { summons: materials } },
|
|
17526
|
+
env,
|
|
17527
|
+
io
|
|
17528
|
+
)
|
|
17529
|
+
});
|
|
17530
|
+
if (resumed !== void 0) return resumed;
|
|
17531
|
+
}
|
|
17456
17532
|
}
|
|
17457
17533
|
let admitted;
|
|
17458
17534
|
try {
|
|
@@ -17474,6 +17550,9 @@ async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
|
|
|
17474
17550
|
}
|
|
17475
17551
|
throw error;
|
|
17476
17552
|
}
|
|
17553
|
+
if (role === "auditor" && auditorSourceRun !== void 0) {
|
|
17554
|
+
await persistAdmittedSourceRunPath(admitted, auditorSourceRun);
|
|
17555
|
+
}
|
|
17477
17556
|
return await withAuditorSoulEnv({
|
|
17478
17557
|
...auditorSubject === void 0 ? {} : { subject: auditorSubject },
|
|
17479
17558
|
...auditorSourceRun === void 0 ? {} : { sourceRunDirectory: auditorSourceRun },
|
|
@@ -24667,13 +24746,11 @@ async function prepareAcpRoleEnvelope(options) {
|
|
|
24667
24746
|
});
|
|
24668
24747
|
reply(socket, rpc.id, {
|
|
24669
24748
|
content: projected.content,
|
|
24670
|
-
structuredContent: projected.details,
|
|
24671
24749
|
isError: true
|
|
24672
24750
|
});
|
|
24673
24751
|
} catch {
|
|
24674
24752
|
reply(socket, rpc.id, {
|
|
24675
24753
|
content: declared.content,
|
|
24676
|
-
structuredContent: declared.details,
|
|
24677
24754
|
isError: true
|
|
24678
24755
|
});
|
|
24679
24756
|
}
|
|
@@ -24686,7 +24763,7 @@ async function prepareAcpRoleEnvelope(options) {
|
|
|
24686
24763
|
details: result.details,
|
|
24687
24764
|
isError: false
|
|
24688
24765
|
});
|
|
24689
|
-
reply(socket, rpc.id, { content: projected.content,
|
|
24766
|
+
reply(socket, rpc.id, { content: projected.content, ...projected.isError ? { isError: true } : {} });
|
|
24690
24767
|
} catch (error) {
|
|
24691
24768
|
let content;
|
|
24692
24769
|
let details;
|
|
@@ -24710,7 +24787,7 @@ async function prepareAcpRoleEnvelope(options) {
|
|
|
24710
24787
|
details,
|
|
24711
24788
|
isError: true
|
|
24712
24789
|
});
|
|
24713
|
-
reply(socket, rpc.id, { content: projected.content,
|
|
24790
|
+
reply(socket, rpc.id, { content: projected.content, ...projected.isError ? { isError: true } : {} });
|
|
24714
24791
|
}
|
|
24715
24792
|
} catch (error) {
|
|
24716
24793
|
reply(socket, rpc.id, void 0, error);
|
package/dist/gatekeeper-role.js
CHANGED
|
@@ -51,7 +51,9 @@ function failureReason(error) {
|
|
|
51
51
|
function asStringArray(value) {
|
|
52
52
|
if (!Array.isArray(value))
|
|
53
53
|
return [];
|
|
54
|
-
|
|
54
|
+
// Officer findings may be structured objects (category/law/evidence); the
|
|
55
|
+
// parent role must see them verbatim, not an empty list (#750 evidence).
|
|
56
|
+
return value.map((item) => (typeof item === "string" ? item : JSON.stringify(item)));
|
|
55
57
|
}
|
|
56
58
|
/** Serializable stand-in when the child tool call had no arguments object. */
|
|
57
59
|
export const MISSING_ARGUMENTS_SUBMISSION = Object.freeze({ missing: "arguments" });
|
package/dist/notary-contracts.js
CHANGED
|
@@ -80,6 +80,7 @@ function notaryDecisiveFacts(output) {
|
|
|
80
80
|
if (status === "pass" || status === "bounce") {
|
|
81
81
|
const findings = output.findings;
|
|
82
82
|
facts.findingsCount = Array.isArray(findings) ? findings.length : 0;
|
|
83
|
+
facts.findings = Array.isArray(findings) ? findings : [];
|
|
83
84
|
}
|
|
84
85
|
if (status === "bounce") {
|
|
85
86
|
facts.disposition = "rewrite";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.js";
|
|
2
2
|
import { CliUsageError } from "./cli-errors.js";
|
|
3
|
-
import { applyInstructionTicketProbe, probeInstructionTicket,
|
|
3
|
+
import { applyInstructionTicketProbe, probeInstructionTicket, tryResumeSameTicketSeatRun, } from "./seat-ticket-binding.js";
|
|
4
4
|
import { admitInspectorInvocation, buildInspectorTransportPrompt, } from "./invocation.js";
|
|
5
5
|
import { prepareSummonsResumeMaterials, runPostAdmissionOneShot, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
|
|
6
|
-
import { loadResumableInspectorRun, markRunAdmitted, } from "./run-lifecycle.js";
|
|
6
|
+
import { loadResumableInspectorRun, markRunAdmitted, parentRunPathFromGatePointerInstruction, } from "./run-lifecycle.js";
|
|
7
7
|
import { presentStructuralRejection, trySettleInspectorTerminalResult, } from "./settlement.js";
|
|
8
8
|
import { projectRoleTurnRequest, } from "./turn-request.js";
|
|
9
9
|
/** Project admitted invocation onto the host-neutral turn request. */
|
|
@@ -26,14 +26,14 @@ export async function runPublicInspector(argv, env, io, parseInspectorArgv) {
|
|
|
26
26
|
}
|
|
27
27
|
throw error;
|
|
28
28
|
}
|
|
29
|
-
// #
|
|
29
|
+
// #747: same parent (卷宗指针) → resume prior inspector run with this summons' materials.
|
|
30
30
|
// Probe captures DiaristTicketResolutionError so admit+beforeDispatch can settle
|
|
31
31
|
// controlled failure (bare pre-admit throw skips terminal settlement).
|
|
32
32
|
// No bare catch→fresh: lookup/resume failures surface; only true absence mints new.
|
|
33
33
|
const projectRoot = parsed.project ?? env.cwd;
|
|
34
34
|
const ticketProbe = await probeInstructionTicket(parsed.instruction, projectRoot, env);
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
35
|
+
const parentRunPath = parentRunPathFromGatePointerInstruction(parsed.instruction);
|
|
36
|
+
if (parentRunPath !== undefined) {
|
|
37
37
|
const summons = {
|
|
38
38
|
instruction: parsed.instruction,
|
|
39
39
|
instructionEmpty: parsed.instruction.trim() === "",
|
|
@@ -43,7 +43,7 @@ export async function runPublicInspector(argv, env, io, parseInspectorArgv) {
|
|
|
43
43
|
home: env.home,
|
|
44
44
|
projectRoot,
|
|
45
45
|
role: "inspector",
|
|
46
|
-
|
|
46
|
+
parentRunPath,
|
|
47
47
|
freshSummons: env.freshSummons,
|
|
48
48
|
summons,
|
|
49
49
|
resume: (runId, materials) => runPublicInspectorResume({ runId, ...(materials === undefined ? {} : { summons: materials }) }, env, io),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.js";
|
|
2
2
|
import { readRunTicketNumber } from "../run-ticket-number.js";
|
|
3
3
|
import { CliUsageError } from "./cli-errors.js";
|
|
4
|
-
import { admitAuditorInvocation, admitEvidenceChildInvocation, admitGatekeeperInvocation, admitNavigatorInvocation, bindAdmittedTicketNumber, buildInstructionTransportPrompt, } from "./invocation.js";
|
|
4
|
+
import { admitAuditorInvocation, admitEvidenceChildInvocation, admitGatekeeperInvocation, admitNavigatorInvocation, bindAdmittedTicketNumber, buildInstructionTransportPrompt, persistAdmittedSourceRunPath, } from "./invocation.js";
|
|
5
5
|
import { prepareSummonsResumeMaterials, runPostAdmissionOneShot, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
|
|
6
6
|
import { loadResumableInstructionSeatRun, markRunAdmitted, } from "./run-lifecycle.js";
|
|
7
7
|
import { applyInstructionTicketProbe, probeInstructionTicket, ticketNumberFromProbe, tryResumeSameTicketSeatRun, } from "./seat-ticket-binding.js";
|
|
@@ -124,10 +124,9 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
|
|
|
124
124
|
let auditorSourceRun;
|
|
125
125
|
let auditorSourceTicket;
|
|
126
126
|
let ticketProbe;
|
|
127
|
-
// #637
|
|
128
|
-
// Auditor
|
|
129
|
-
//
|
|
130
|
-
// surface; only true absence of a prior run mints new.
|
|
127
|
+
// #637 / #747: resume prior seat run with this summons' materials.
|
|
128
|
+
// Auditor (#747): parent --source-run path is the lookup key.
|
|
129
|
+
// Other instruction seats: ticket probe (inspector face). No bare catch→fresh.
|
|
131
130
|
const projectRoot = parsed.project ?? env.cwd;
|
|
132
131
|
if (role === "auditor") {
|
|
133
132
|
if (parsed.subject !== "judge" && parsed.subject !== "doctor") {
|
|
@@ -159,24 +158,20 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
|
|
|
159
158
|
// Inspector face: unconditional probe (same seam as inspector-run.ts).
|
|
160
159
|
ticketProbe = await probeInstructionTicket(parsed.instruction, projectRoot, env);
|
|
161
160
|
}
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
attachmentPaths: parsed.attachmentPaths,
|
|
169
|
-
};
|
|
161
|
+
const summons = {
|
|
162
|
+
instruction: parsed.instruction,
|
|
163
|
+
instructionEmpty: parsed.instruction.trim() === "",
|
|
164
|
+
attachmentPaths: parsed.attachmentPaths,
|
|
165
|
+
};
|
|
166
|
+
if (role === "auditor" && auditorSourceRun !== undefined) {
|
|
170
167
|
const resumed = await withAuditorSoulEnv({
|
|
171
168
|
...(auditorSubject === undefined ? {} : { subject: auditorSubject }),
|
|
172
|
-
|
|
173
|
-
? {}
|
|
174
|
-
: { sourceRunDirectory: auditorSourceRun }),
|
|
169
|
+
sourceRunDirectory: auditorSourceRun,
|
|
175
170
|
run: () => tryResumeSameTicketSeatRun({
|
|
176
171
|
home: env.home,
|
|
177
172
|
projectRoot,
|
|
178
173
|
role,
|
|
179
|
-
|
|
174
|
+
parentRunPath: auditorSourceRun,
|
|
180
175
|
freshSummons: env.freshSummons,
|
|
181
176
|
summons,
|
|
182
177
|
resume: (runId, materials) => runPublicInstructionSeatResume({ runId, ...(materials === undefined ? {} : { summons: materials }) }, env, io),
|
|
@@ -185,6 +180,22 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
|
|
|
185
180
|
if (resumed !== undefined)
|
|
186
181
|
return resumed;
|
|
187
182
|
}
|
|
183
|
+
else {
|
|
184
|
+
const probedTicketNumber = ticketProbe === undefined ? undefined : ticketNumberFromProbe(ticketProbe);
|
|
185
|
+
if (probedTicketNumber !== undefined) {
|
|
186
|
+
const resumed = await tryResumeSameTicketSeatRun({
|
|
187
|
+
home: env.home,
|
|
188
|
+
projectRoot,
|
|
189
|
+
role,
|
|
190
|
+
ticketNumber: probedTicketNumber,
|
|
191
|
+
freshSummons: env.freshSummons,
|
|
192
|
+
summons,
|
|
193
|
+
resume: (runId, materials) => runPublicInstructionSeatResume({ runId, ...(materials === undefined ? {} : { summons: materials }) }, env, io),
|
|
194
|
+
});
|
|
195
|
+
if (resumed !== undefined)
|
|
196
|
+
return resumed;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
188
199
|
let admitted;
|
|
189
200
|
try {
|
|
190
201
|
admitted = await admitInstructionSeat(role, {
|
|
@@ -206,6 +217,9 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
|
|
|
206
217
|
}
|
|
207
218
|
throw error;
|
|
208
219
|
}
|
|
220
|
+
if (role === "auditor" && auditorSourceRun !== undefined) {
|
|
221
|
+
await persistAdmittedSourceRunPath(admitted, auditorSourceRun);
|
|
222
|
+
}
|
|
209
223
|
return await withAuditorSoulEnv({
|
|
210
224
|
...(auditorSubject === undefined ? {} : { subject: auditorSubject }),
|
|
211
225
|
...(auditorSourceRun === undefined
|
|
@@ -126,6 +126,23 @@ async function mergeInvocationIdentityPage(runDirectory, fields) {
|
|
|
126
126
|
...fields,
|
|
127
127
|
}, null, 2)}\n`, "utf8");
|
|
128
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Persist parent --source-run path onto admitted-request for officer resume lookup (#747).
|
|
131
|
+
* Reuses the existing notary sourceRunPath key; does not invent a new field name.
|
|
132
|
+
*/
|
|
133
|
+
export async function persistAdmittedSourceRunPath(admitted, sourceRunPath) {
|
|
134
|
+
if (sourceRunPath.trim() === "") {
|
|
135
|
+
throw new Error("persistAdmittedSourceRunPath requires a non-empty sourceRunPath");
|
|
136
|
+
}
|
|
137
|
+
const admittedPath = admitted.admittedRequestPath;
|
|
138
|
+
const current = JSON.parse(await readFile(admittedPath, "utf8"));
|
|
139
|
+
if (typeof current.sourceRunPath === "string") {
|
|
140
|
+
if (current.sourceRunPath === sourceRunPath)
|
|
141
|
+
return;
|
|
142
|
+
throw new Error(`persistAdmittedSourceRunPath refuses to replace ${current.sourceRunPath} with ${sourceRunPath}`);
|
|
143
|
+
}
|
|
144
|
+
await writeFile(admittedPath, `${JSON.stringify({ ...current, sourceRunPath }, null, 2)}\n`, "utf8");
|
|
145
|
+
}
|
|
129
146
|
/**
|
|
130
147
|
* Bind a post-admission resolved ticketNumber onto the in-memory admitted
|
|
131
148
|
* object and both durable pages (invocation.json + admitted-request.json).
|
package/dist/public-cli/main.js
CHANGED
|
@@ -14902,6 +14902,7 @@ function notaryDecisiveFacts(output) {
|
|
|
14902
14902
|
if (status === "pass" || status === "bounce") {
|
|
14903
14903
|
const findings = output.findings;
|
|
14904
14904
|
facts.findingsCount = Array.isArray(findings) ? findings.length : 0;
|
|
14905
|
+
facts.findings = Array.isArray(findings) ? findings : [];
|
|
14905
14906
|
}
|
|
14906
14907
|
if (status === "bounce") {
|
|
14907
14908
|
facts.disposition = "rewrite";
|
|
@@ -18000,6 +18001,33 @@ async function findRunDirectoryById(home, runId) {
|
|
|
18000
18001
|
}
|
|
18001
18002
|
return void 0;
|
|
18002
18003
|
}
|
|
18004
|
+
function parentRunPathFromGatePointerInstruction(instruction) {
|
|
18005
|
+
if (!instruction.startsWith(GATE_DOSSIER_POINTER_PREFIX)) return void 0;
|
|
18006
|
+
const path = instruction.slice(GATE_DOSSIER_POINTER_PREFIX.length).trim();
|
|
18007
|
+
return path === "" ? void 0 : path;
|
|
18008
|
+
}
|
|
18009
|
+
async function readRunParentPath(runDirectory) {
|
|
18010
|
+
let raw;
|
|
18011
|
+
try {
|
|
18012
|
+
raw = JSON.parse(
|
|
18013
|
+
await readFile8(join12(runDirectory, "admitted-request.json"), "utf8")
|
|
18014
|
+
);
|
|
18015
|
+
} catch (error) {
|
|
18016
|
+
if (errorCodeOf2(error) === "ENOENT") return void 0;
|
|
18017
|
+
throw error;
|
|
18018
|
+
}
|
|
18019
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
18020
|
+
return void 0;
|
|
18021
|
+
}
|
|
18022
|
+
const record4 = raw;
|
|
18023
|
+
if (typeof record4.sourceRunPath === "string" && record4.sourceRunPath.trim() !== "") {
|
|
18024
|
+
return record4.sourceRunPath;
|
|
18025
|
+
}
|
|
18026
|
+
if (typeof record4.instruction === "string") {
|
|
18027
|
+
return parentRunPathFromGatePointerInstruction(record4.instruction);
|
|
18028
|
+
}
|
|
18029
|
+
return void 0;
|
|
18030
|
+
}
|
|
18003
18031
|
async function findLatestRunIdForSeatTicket(input) {
|
|
18004
18032
|
const ledgerHome = resolveActivationLedgerHome(input.home);
|
|
18005
18033
|
const runsDir = join12(
|
|
@@ -18019,8 +18047,16 @@ async function findLatestRunIdForSeatTicket(input) {
|
|
|
18019
18047
|
if (!entry.endsWith(suffix)) continue;
|
|
18020
18048
|
const runId = entry.slice(0, entry.length - suffix.length);
|
|
18021
18049
|
if (runId.length === 0) continue;
|
|
18022
|
-
const
|
|
18023
|
-
if (
|
|
18050
|
+
const runDirectory = join12(runsDir, entry);
|
|
18051
|
+
if (input.parentRunPath !== void 0) {
|
|
18052
|
+
const parentPath = await readRunParentPath(runDirectory);
|
|
18053
|
+
if (parentPath !== input.parentRunPath) continue;
|
|
18054
|
+
} else if (input.ticketNumber !== void 0) {
|
|
18055
|
+
const ticketNumber = await readRunTicketNumber(runDirectory);
|
|
18056
|
+
if (ticketNumber !== input.ticketNumber) continue;
|
|
18057
|
+
} else {
|
|
18058
|
+
continue;
|
|
18059
|
+
}
|
|
18024
18060
|
if (best === void 0 || runId > best) best = runId;
|
|
18025
18061
|
}
|
|
18026
18062
|
return best;
|
|
@@ -18577,7 +18613,7 @@ async function peekRoleRunRole(home, runId) {
|
|
|
18577
18613
|
const run = await readRoleRunIdentity(runDirectory);
|
|
18578
18614
|
return run?.role;
|
|
18579
18615
|
}
|
|
18580
|
-
var V1_RESUMABLE_PROVIDERS, AUTO_RESUME_LIMIT, RESUME_TRANSPORT_ENVELOPE, RUN_STATE_FILE, WRITER_LOCK_FILE, RunWriterLeaseHeldError, WRITER_LEASE_RECLAIM_ROUNDS;
|
|
18616
|
+
var V1_RESUMABLE_PROVIDERS, AUTO_RESUME_LIMIT, RESUME_TRANSPORT_ENVELOPE, RUN_STATE_FILE, WRITER_LOCK_FILE, RunWriterLeaseHeldError, WRITER_LEASE_RECLAIM_ROUNDS, GATE_DOSSIER_POINTER_PREFIX;
|
|
18581
18617
|
var init_run_lifecycle = __esm({
|
|
18582
18618
|
"src/public-cli/run-lifecycle.ts"() {
|
|
18583
18619
|
"use strict";
|
|
@@ -18602,6 +18638,7 @@ var init_run_lifecycle = __esm({
|
|
|
18602
18638
|
}
|
|
18603
18639
|
};
|
|
18604
18640
|
WRITER_LEASE_RECLAIM_ROUNDS = 3;
|
|
18641
|
+
GATE_DOSSIER_POINTER_PREFIX = "\u5377\u5B97\u6307\u9488\uFF1A";
|
|
18605
18642
|
}
|
|
18606
18643
|
});
|
|
18607
18644
|
|
|
@@ -19898,6 +19935,25 @@ async function mergeInvocationIdentityPage(runDirectory, fields) {
|
|
|
19898
19935
|
"utf8"
|
|
19899
19936
|
);
|
|
19900
19937
|
}
|
|
19938
|
+
async function persistAdmittedSourceRunPath(admitted, sourceRunPath) {
|
|
19939
|
+
if (sourceRunPath.trim() === "") {
|
|
19940
|
+
throw new Error("persistAdmittedSourceRunPath requires a non-empty sourceRunPath");
|
|
19941
|
+
}
|
|
19942
|
+
const admittedPath = admitted.admittedRequestPath;
|
|
19943
|
+
const current = JSON.parse(await readFile9(admittedPath, "utf8"));
|
|
19944
|
+
if (typeof current.sourceRunPath === "string") {
|
|
19945
|
+
if (current.sourceRunPath === sourceRunPath) return;
|
|
19946
|
+
throw new Error(
|
|
19947
|
+
`persistAdmittedSourceRunPath refuses to replace ${current.sourceRunPath} with ${sourceRunPath}`
|
|
19948
|
+
);
|
|
19949
|
+
}
|
|
19950
|
+
await writeFile5(
|
|
19951
|
+
admittedPath,
|
|
19952
|
+
`${JSON.stringify({ ...current, sourceRunPath }, null, 2)}
|
|
19953
|
+
`,
|
|
19954
|
+
"utf8"
|
|
19955
|
+
);
|
|
19956
|
+
}
|
|
19901
19957
|
async function bindAdmittedTicketNumber(admitted, ticketNumber) {
|
|
19902
19958
|
if (!Number.isSafeInteger(ticketNumber) || ticketNumber < 1) {
|
|
19903
19959
|
throw new Error(`bindAdmittedTicketNumber requires a safe positive integer, got ${String(ticketNumber)}`);
|
|
@@ -23782,11 +23838,15 @@ async function resolveSeatTicketBinding(admitted, env = {}) {
|
|
|
23782
23838
|
}
|
|
23783
23839
|
async function tryResumeSameTicketSeatRun(input) {
|
|
23784
23840
|
if (input.freshSummons === true) return void 0;
|
|
23841
|
+
if (input.parentRunPath === void 0 && input.ticketNumber === void 0) {
|
|
23842
|
+
return void 0;
|
|
23843
|
+
}
|
|
23785
23844
|
const previousRunId = await findLatestRunIdForSeatTicket({
|
|
23786
23845
|
home: input.home,
|
|
23787
23846
|
bookKey: resolveBookKeyFromGit(input.projectRoot),
|
|
23788
23847
|
role: input.role,
|
|
23789
|
-
|
|
23848
|
+
...input.parentRunPath === void 0 ? {} : { parentRunPath: input.parentRunPath },
|
|
23849
|
+
...input.ticketNumber === void 0 ? {} : { ticketNumber: input.ticketNumber }
|
|
23790
23850
|
});
|
|
23791
23851
|
if (previousRunId === void 0) return void 0;
|
|
23792
23852
|
return await input.resume(previousRunId, input.summons);
|
|
@@ -29436,21 +29496,20 @@ async function runPublicInstructionSeat(argv, env, io, role, parseArgv2) {
|
|
|
29436
29496
|
env
|
|
29437
29497
|
);
|
|
29438
29498
|
}
|
|
29439
|
-
const
|
|
29440
|
-
|
|
29441
|
-
|
|
29442
|
-
|
|
29443
|
-
|
|
29444
|
-
|
|
29445
|
-
};
|
|
29499
|
+
const summons = {
|
|
29500
|
+
instruction: parsed.instruction,
|
|
29501
|
+
instructionEmpty: parsed.instruction.trim() === "",
|
|
29502
|
+
attachmentPaths: parsed.attachmentPaths
|
|
29503
|
+
};
|
|
29504
|
+
if (role === "auditor" && auditorSourceRun !== void 0) {
|
|
29446
29505
|
const resumed = await withAuditorSoulEnv({
|
|
29447
29506
|
...auditorSubject === void 0 ? {} : { subject: auditorSubject },
|
|
29448
|
-
|
|
29507
|
+
sourceRunDirectory: auditorSourceRun,
|
|
29449
29508
|
run: () => tryResumeSameTicketSeatRun({
|
|
29450
29509
|
home: env.home,
|
|
29451
29510
|
projectRoot,
|
|
29452
29511
|
role,
|
|
29453
|
-
|
|
29512
|
+
parentRunPath: auditorSourceRun,
|
|
29454
29513
|
freshSummons: env.freshSummons,
|
|
29455
29514
|
summons,
|
|
29456
29515
|
resume: (runId, materials) => runPublicInstructionSeatResume(
|
|
@@ -29461,6 +29520,24 @@ async function runPublicInstructionSeat(argv, env, io, role, parseArgv2) {
|
|
|
29461
29520
|
})
|
|
29462
29521
|
});
|
|
29463
29522
|
if (resumed !== void 0) return resumed;
|
|
29523
|
+
} else {
|
|
29524
|
+
const probedTicketNumber = ticketProbe === void 0 ? void 0 : ticketNumberFromProbe(ticketProbe);
|
|
29525
|
+
if (probedTicketNumber !== void 0) {
|
|
29526
|
+
const resumed = await tryResumeSameTicketSeatRun({
|
|
29527
|
+
home: env.home,
|
|
29528
|
+
projectRoot,
|
|
29529
|
+
role,
|
|
29530
|
+
ticketNumber: probedTicketNumber,
|
|
29531
|
+
freshSummons: env.freshSummons,
|
|
29532
|
+
summons,
|
|
29533
|
+
resume: (runId, materials) => runPublicInstructionSeatResume(
|
|
29534
|
+
{ runId, ...materials === void 0 ? {} : { summons: materials } },
|
|
29535
|
+
env,
|
|
29536
|
+
io
|
|
29537
|
+
)
|
|
29538
|
+
});
|
|
29539
|
+
if (resumed !== void 0) return resumed;
|
|
29540
|
+
}
|
|
29464
29541
|
}
|
|
29465
29542
|
let admitted;
|
|
29466
29543
|
try {
|
|
@@ -29482,6 +29559,9 @@ async function runPublicInstructionSeat(argv, env, io, role, parseArgv2) {
|
|
|
29482
29559
|
}
|
|
29483
29560
|
throw error;
|
|
29484
29561
|
}
|
|
29562
|
+
if (role === "auditor" && auditorSourceRun !== void 0) {
|
|
29563
|
+
await persistAdmittedSourceRunPath(admitted, auditorSourceRun);
|
|
29564
|
+
}
|
|
29485
29565
|
return await withAuditorSoulEnv({
|
|
29486
29566
|
...auditorSubject === void 0 ? {} : { subject: auditorSubject },
|
|
29487
29567
|
...auditorSourceRun === void 0 ? {} : { sourceRunDirectory: auditorSourceRun },
|
|
@@ -30427,8 +30507,7 @@ async function runPublicNotary(argv, env, io, parseNotaryArgv2) {
|
|
|
30427
30507
|
}
|
|
30428
30508
|
throw error;
|
|
30429
30509
|
}
|
|
30430
|
-
|
|
30431
|
-
if (ticketNumber !== void 0) {
|
|
30510
|
+
{
|
|
30432
30511
|
const summons = {
|
|
30433
30512
|
sourceRunPath: source.runDirectory,
|
|
30434
30513
|
sourceRun: source
|
|
@@ -30437,7 +30516,7 @@ async function runPublicNotary(argv, env, io, parseNotaryArgv2) {
|
|
|
30437
30516
|
home: env.home,
|
|
30438
30517
|
projectRoot,
|
|
30439
30518
|
role: "notary",
|
|
30440
|
-
|
|
30519
|
+
parentRunPath: source.runDirectory,
|
|
30441
30520
|
freshSummons: env.freshSummons,
|
|
30442
30521
|
summons,
|
|
30443
30522
|
resume: (runId, materials) => runPublicNotaryResume(
|
|
@@ -30552,7 +30631,6 @@ var init_notary_run = __esm({
|
|
|
30552
30631
|
init_auditor_dossier_tool();
|
|
30553
30632
|
init_notary_source_run();
|
|
30554
30633
|
init_engine_material();
|
|
30555
|
-
init_run_ticket_number();
|
|
30556
30634
|
init_cli_errors();
|
|
30557
30635
|
init_invocation();
|
|
30558
30636
|
init_seat_ticket_binding();
|
|
@@ -30592,8 +30670,8 @@ async function runPublicInspector(argv, env, io, parseInspectorArgv2) {
|
|
|
30592
30670
|
projectRoot,
|
|
30593
30671
|
env
|
|
30594
30672
|
);
|
|
30595
|
-
const
|
|
30596
|
-
if (
|
|
30673
|
+
const parentRunPath = parentRunPathFromGatePointerInstruction(parsed.instruction);
|
|
30674
|
+
if (parentRunPath !== void 0) {
|
|
30597
30675
|
const summons = {
|
|
30598
30676
|
instruction: parsed.instruction,
|
|
30599
30677
|
instructionEmpty: parsed.instruction.trim() === "",
|
|
@@ -30603,7 +30681,7 @@ async function runPublicInspector(argv, env, io, parseInspectorArgv2) {
|
|
|
30603
30681
|
home: env.home,
|
|
30604
30682
|
projectRoot,
|
|
30605
30683
|
role: "inspector",
|
|
30606
|
-
|
|
30684
|
+
parentRunPath,
|
|
30607
30685
|
freshSummons: env.freshSummons,
|
|
30608
30686
|
summons,
|
|
30609
30687
|
resume: (runId, materials) => runPublicInspectorResume(
|
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
* Public Notary Role run: admit source-run locator → shared post-admission coordinator
|
|
3
3
|
* → settle Terminal result (#448 / #517). Zero caller prompt/attachment. Lifecycle is
|
|
4
4
|
* the shared post-admission seam; this module keeps only Notary adapters.
|
|
5
|
-
* #637: same-
|
|
5
|
+
* #637 / #747: same-parent (--source-run) re-summons resume the seat's previous run.
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync } from "node:fs";
|
|
8
8
|
import { gateSubmissionCandidatePath } from "../auditor-dossier-tool.js";
|
|
9
9
|
import { NotarySourceRunError, resolveNotarySourceRunLocator, } from "../notary-source-run.js";
|
|
10
10
|
import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.js";
|
|
11
|
-
import { readRunTicketNumber } from "../run-ticket-number.js";
|
|
12
11
|
import { CliUsageError } from "./cli-errors.js";
|
|
13
12
|
import { admitNotaryInvocation, buildNotaryTransportPrompt, } from "./invocation.js";
|
|
14
13
|
import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
|
|
@@ -60,8 +59,8 @@ export async function runPublicNotary(argv, env, io, parseNotaryArgv) {
|
|
|
60
59
|
}
|
|
61
60
|
throw error;
|
|
62
61
|
}
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
// #747: officer resume key is this parent source-run path (not ticket number).
|
|
63
|
+
{
|
|
65
64
|
const summons = {
|
|
66
65
|
sourceRunPath: source.runDirectory,
|
|
67
66
|
sourceRun: source,
|
|
@@ -70,7 +69,7 @@ export async function runPublicNotary(argv, env, io, parseNotaryArgv) {
|
|
|
70
69
|
home: env.home,
|
|
71
70
|
projectRoot,
|
|
72
71
|
role: "notary",
|
|
73
|
-
|
|
72
|
+
parentRunPath: source.runDirectory,
|
|
74
73
|
freshSummons: env.freshSummons,
|
|
75
74
|
summons,
|
|
76
75
|
resume: (runId, materials) => runPublicNotaryResume({ runId, ...(materials === undefined ? {} : { summons: materials }) }, env, io),
|
|
@@ -746,9 +746,46 @@ export async function findRunDirectoryById(home, runId) {
|
|
|
746
746
|
}
|
|
747
747
|
return undefined;
|
|
748
748
|
}
|
|
749
|
+
/** Code-owned gate inspector summons prefix (public-role-summons / #747). */
|
|
750
|
+
export const GATE_DOSSIER_POINTER_PREFIX = "卷宗指针:";
|
|
751
|
+
/** Parent path from a code-owned inspector 卷宗指针 instruction; else undefined. */
|
|
752
|
+
export function parentRunPathFromGatePointerInstruction(instruction) {
|
|
753
|
+
if (!instruction.startsWith(GATE_DOSSIER_POINTER_PREFIX))
|
|
754
|
+
return undefined;
|
|
755
|
+
const path = instruction.slice(GATE_DOSSIER_POINTER_PREFIX.length).trim();
|
|
756
|
+
return path === "" ? undefined : path;
|
|
757
|
+
}
|
|
749
758
|
/**
|
|
750
|
-
*
|
|
751
|
-
*
|
|
759
|
+
* Parent-run binding on a retained officer run (#747).
|
|
760
|
+
* Notary/auditor: typed sourceRunPath. Inspector: exact code-owned 卷宗指针 instruction.
|
|
761
|
+
* Missing page → undefined; damage / non-ENOENT IO propagates.
|
|
762
|
+
*/
|
|
763
|
+
export async function readRunParentPath(runDirectory) {
|
|
764
|
+
let raw;
|
|
765
|
+
try {
|
|
766
|
+
raw = JSON.parse(await readFile(join(runDirectory, "admitted-request.json"), "utf8"));
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
if (errorCodeOf(error) === "ENOENT")
|
|
770
|
+
return undefined;
|
|
771
|
+
throw error;
|
|
772
|
+
}
|
|
773
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
774
|
+
return undefined;
|
|
775
|
+
}
|
|
776
|
+
const record = raw;
|
|
777
|
+
if (typeof record.sourceRunPath === "string" && record.sourceRunPath.trim() !== "") {
|
|
778
|
+
return record.sourceRunPath;
|
|
779
|
+
}
|
|
780
|
+
if (typeof record.instruction === "string") {
|
|
781
|
+
return parentRunPathFromGatePointerInstruction(record.instruction);
|
|
782
|
+
}
|
|
783
|
+
return undefined;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Locate the latest retained run for one seat under a book (#637 / #747).
|
|
787
|
+
* Same walk surface as findRunDirectoryById. Match by parent run path (officer
|
|
788
|
+
* seats, #747) or by ticket number (countersign / diarist principal).
|
|
752
789
|
* runId is UUIDv7 — lexicographic max is latest. No parallel index.
|
|
753
790
|
* Only a truly missing runs directory means no history; damage/permission errors propagate.
|
|
754
791
|
*/
|
|
@@ -772,9 +809,20 @@ export async function findLatestRunIdForSeatTicket(input) {
|
|
|
772
809
|
const runId = entry.slice(0, entry.length - suffix.length);
|
|
773
810
|
if (runId.length === 0)
|
|
774
811
|
continue;
|
|
775
|
-
const
|
|
776
|
-
if (
|
|
812
|
+
const runDirectory = join(runsDir, entry);
|
|
813
|
+
if (input.parentRunPath !== undefined) {
|
|
814
|
+
const parentPath = await readRunParentPath(runDirectory);
|
|
815
|
+
if (parentPath !== input.parentRunPath)
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
else if (input.ticketNumber !== undefined) {
|
|
819
|
+
const ticketNumber = await readRunTicketNumber(runDirectory);
|
|
820
|
+
if (ticketNumber !== input.ticketNumber)
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
777
824
|
continue;
|
|
825
|
+
}
|
|
778
826
|
if (best === undefined || runId > best)
|
|
779
827
|
best = runId;
|
|
780
828
|
}
|
|
@@ -91,21 +91,30 @@ export async function resolveSeatTicketBinding(admitted, env = {}) {
|
|
|
91
91
|
return resolution;
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
|
-
* Sole same-
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
94
|
+
* Sole same-seat → resume decision (#637 / #724 / #747).
|
|
95
|
+
* Officer seats (notary/inspector/auditor) look up by parent run path; countersign
|
|
96
|
+
* / diarist keep ticket-number principal. When found, runs resume with this
|
|
97
|
+
* summons' materials. Lookup/resume failures propagate (失败诚实) — never wash
|
|
98
|
+
* into a fresh mint. Returns undefined when the caller declared an explicit
|
|
98
99
|
* fresh summons (`ak-role new`) or when no prior run exists; both mint new.
|
|
99
100
|
* freshSummons is required so no seat can drift back into its own skip branch.
|
|
100
101
|
*/
|
|
101
102
|
export async function tryResumeSameTicketSeatRun(input) {
|
|
102
103
|
if (input.freshSummons === true)
|
|
103
104
|
return undefined;
|
|
105
|
+
if (input.parentRunPath === undefined && input.ticketNumber === undefined) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
104
108
|
const previousRunId = await findLatestRunIdForSeatTicket({
|
|
105
109
|
home: input.home,
|
|
106
110
|
bookKey: resolveBookKeyFromGit(input.projectRoot),
|
|
107
111
|
role: input.role,
|
|
108
|
-
|
|
112
|
+
...(input.parentRunPath === undefined
|
|
113
|
+
? {}
|
|
114
|
+
: { parentRunPath: input.parentRunPath }),
|
|
115
|
+
...(input.ticketNumber === undefined
|
|
116
|
+
? {}
|
|
117
|
+
: { ticketNumber: input.ticketNumber }),
|
|
109
118
|
});
|
|
110
119
|
if (previousRunId === undefined)
|
|
111
120
|
return undefined;
|
package/package.json
CHANGED
|
@@ -474,14 +474,12 @@ export async function prepareAcpRoleEnvelope(options: {
|
|
|
474
474
|
});
|
|
475
475
|
reply(socket, rpc.id, {
|
|
476
476
|
content: projected.content,
|
|
477
|
-
structuredContent: projected.details,
|
|
478
477
|
isError: true,
|
|
479
478
|
});
|
|
480
479
|
} catch {
|
|
481
480
|
// Slot already filled; durable projection may have partially failed.
|
|
482
481
|
reply(socket, rpc.id, {
|
|
483
482
|
content: declared.content,
|
|
484
|
-
structuredContent: declared.details,
|
|
485
483
|
isError: true,
|
|
486
484
|
});
|
|
487
485
|
}
|
|
@@ -497,7 +495,7 @@ export async function prepareAcpRoleEnvelope(options: {
|
|
|
497
495
|
// Candidate only: do not emit turn_end here. Seal waits for the typed ACP
|
|
498
496
|
// round boundary (closeRound after session/prompt), so delayed siblings stay
|
|
499
497
|
// in the same round instead of becoming silent post-seal anomalies.
|
|
500
|
-
reply(socket, rpc.id, { content: projected.content,
|
|
498
|
+
reply(socket, rpc.id, { content: projected.content, ...(projected.isError ? { isError: true } : {}) });
|
|
501
499
|
} catch (error) {
|
|
502
500
|
let content: ContentPart[];
|
|
503
501
|
let details: Record<string, unknown>;
|
|
@@ -529,7 +527,7 @@ export async function prepareAcpRoleEnvelope(options: {
|
|
|
529
527
|
details,
|
|
530
528
|
isError: true,
|
|
531
529
|
});
|
|
532
|
-
reply(socket, rpc.id, { content: projected.content,
|
|
530
|
+
reply(socket, rpc.id, { content: projected.content, ...(projected.isError ? { isError: true } : {}) });
|
|
533
531
|
}
|
|
534
532
|
} catch (error) { reply(socket, rpc.id, undefined, error); }
|
|
535
533
|
})();
|
package/src/gatekeeper-role.ts
CHANGED
|
@@ -146,7 +146,9 @@ function failureReason(error: unknown): string {
|
|
|
146
146
|
|
|
147
147
|
function asStringArray(value: unknown): readonly string[] {
|
|
148
148
|
if (!Array.isArray(value)) return [];
|
|
149
|
-
|
|
149
|
+
// Officer findings may be structured objects (category/law/evidence); the
|
|
150
|
+
// parent role must see them verbatim, not an empty list (#750 evidence).
|
|
151
|
+
return value.map((item) => (typeof item === "string" ? item : JSON.stringify(item)));
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
/** Serializable stand-in when the child tool call had no arguments object. */
|
package/src/notary-contracts.ts
CHANGED
|
@@ -128,6 +128,8 @@ export function notaryDecisiveFacts(output: NotaryOutput): Record<string, unknow
|
|
|
128
128
|
if (status === "pass" || status === "bounce") {
|
|
129
129
|
const findings = (output as { findings?: unknown }).findings;
|
|
130
130
|
facts.findingsCount = Array.isArray(findings) ? findings.length : 0;
|
|
131
|
+
// The parent role reads the findings themselves; the count alone dropped them (#750).
|
|
132
|
+
facts.findings = Array.isArray(findings) ? findings : [];
|
|
131
133
|
}
|
|
132
134
|
if (status === "bounce") {
|
|
133
135
|
facts.disposition = "rewrite";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* coordinator → settle Terminal result (#568 / ADR 0074). Lawful releases:
|
|
4
4
|
* pass/bounce/escalate. #633: manual resume continues the exact session. Dual path
|
|
5
5
|
* with gate-province dispatch; this module is the direct command face.
|
|
6
|
-
* #637: same-
|
|
6
|
+
* #637 / #747: same-parent (卷宗指针) re-summons resume the seat's previous run.
|
|
7
7
|
*/
|
|
8
8
|
import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contracts.ts";
|
|
9
9
|
import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.ts";
|
|
@@ -11,7 +11,6 @@ import { CliUsageError } from "./cli-errors.ts";
|
|
|
11
11
|
import {
|
|
12
12
|
applyInstructionTicketProbe,
|
|
13
13
|
probeInstructionTicket,
|
|
14
|
-
ticketNumberFromProbe,
|
|
15
14
|
tryResumeSameTicketSeatRun,
|
|
16
15
|
} from "./seat-ticket-binding.ts";
|
|
17
16
|
import {
|
|
@@ -31,6 +30,7 @@ import {
|
|
|
31
30
|
import {
|
|
32
31
|
loadResumableInspectorRun,
|
|
33
32
|
markRunAdmitted,
|
|
33
|
+
parentRunPathFromGatePointerInstruction,
|
|
34
34
|
type PublicResumeRequest,
|
|
35
35
|
type SameTicketSummonsMaterials,
|
|
36
36
|
} from "./run-lifecycle.ts";
|
|
@@ -87,7 +87,7 @@ export async function runPublicInspector(
|
|
|
87
87
|
throw error;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
// #
|
|
90
|
+
// #747: same parent (卷宗指针) → resume prior inspector run with this summons' materials.
|
|
91
91
|
// Probe captures DiaristTicketResolutionError so admit+beforeDispatch can settle
|
|
92
92
|
// controlled failure (bare pre-admit throw skips terminal settlement).
|
|
93
93
|
// No bare catch→fresh: lookup/resume failures surface; only true absence mints new.
|
|
@@ -97,8 +97,8 @@ export async function runPublicInspector(
|
|
|
97
97
|
projectRoot,
|
|
98
98
|
env,
|
|
99
99
|
);
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
100
|
+
const parentRunPath = parentRunPathFromGatePointerInstruction(parsed.instruction);
|
|
101
|
+
if (parentRunPath !== undefined) {
|
|
102
102
|
const summons: SameTicketSummonsMaterials = {
|
|
103
103
|
instruction: parsed.instruction,
|
|
104
104
|
instructionEmpty: parsed.instruction.trim() === "",
|
|
@@ -108,7 +108,7 @@ export async function runPublicInspector(
|
|
|
108
108
|
home: env.home,
|
|
109
109
|
projectRoot,
|
|
110
110
|
role: "inspector",
|
|
111
|
-
|
|
111
|
+
parentRunPath,
|
|
112
112
|
freshSummons: env.freshSummons,
|
|
113
113
|
summons,
|
|
114
114
|
resume: (runId, materials) =>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared instruction-seat run (#639 / #675 / #637): gatekeeper, navigator, auditor,
|
|
3
3
|
* and evidence-child share admit → turn-request → post-admission → settle.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Auditor same-parent (--source-run) re-summons resume via tryResumeSameTicketSeatRun
|
|
5
|
+
* (#747); other instruction seats keep ticket-number principal — no independent
|
|
6
|
+
* run/rebind/nest path.
|
|
7
7
|
*/
|
|
8
8
|
import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contracts.ts";
|
|
9
9
|
import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.ts";
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
admitNavigatorInvocation,
|
|
17
17
|
bindAdmittedTicketNumber,
|
|
18
18
|
buildInstructionTransportPrompt,
|
|
19
|
+
persistAdmittedSourceRunPath,
|
|
19
20
|
type AdmittedAuditorInvocation,
|
|
20
21
|
type AdmittedEvidenceChildInvocation,
|
|
21
22
|
type AdmittedGatekeeperInvocation,
|
|
@@ -258,10 +259,9 @@ export async function runPublicInstructionSeat(
|
|
|
258
259
|
let auditorSourceTicket: number | undefined;
|
|
259
260
|
let ticketProbe: InstructionTicketProbe | undefined;
|
|
260
261
|
|
|
261
|
-
// #637
|
|
262
|
-
// Auditor
|
|
263
|
-
//
|
|
264
|
-
// surface; only true absence of a prior run mints new.
|
|
262
|
+
// #637 / #747: resume prior seat run with this summons' materials.
|
|
263
|
+
// Auditor (#747): parent --source-run path is the lookup key.
|
|
264
|
+
// Other instruction seats: ticket probe (inspector face). No bare catch→fresh.
|
|
265
265
|
const projectRoot = parsed.project ?? env.cwd;
|
|
266
266
|
if (role === "auditor") {
|
|
267
267
|
if (parsed.subject !== "judge" && parsed.subject !== "doctor") {
|
|
@@ -305,27 +305,21 @@ export async function runPublicInstructionSeat(
|
|
|
305
305
|
);
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
instruction: parsed.instruction,
|
|
315
|
-
instructionEmpty: parsed.instruction.trim() === "",
|
|
316
|
-
attachmentPaths: parsed.attachmentPaths,
|
|
317
|
-
};
|
|
308
|
+
const summons: SameTicketSummonsMaterials = {
|
|
309
|
+
instruction: parsed.instruction,
|
|
310
|
+
instructionEmpty: parsed.instruction.trim() === "",
|
|
311
|
+
attachmentPaths: parsed.attachmentPaths,
|
|
312
|
+
};
|
|
313
|
+
if (role === "auditor" && auditorSourceRun !== undefined) {
|
|
318
314
|
const resumed = await withAuditorSoulEnv({
|
|
319
315
|
...(auditorSubject === undefined ? {} : { subject: auditorSubject }),
|
|
320
|
-
|
|
321
|
-
? {}
|
|
322
|
-
: { sourceRunDirectory: auditorSourceRun }),
|
|
316
|
+
sourceRunDirectory: auditorSourceRun,
|
|
323
317
|
run: () =>
|
|
324
318
|
tryResumeSameTicketSeatRun({
|
|
325
319
|
home: env.home,
|
|
326
320
|
projectRoot,
|
|
327
321
|
role,
|
|
328
|
-
|
|
322
|
+
parentRunPath: auditorSourceRun,
|
|
329
323
|
freshSummons: env.freshSummons,
|
|
330
324
|
summons,
|
|
331
325
|
resume: (runId, materials) =>
|
|
@@ -337,6 +331,26 @@ export async function runPublicInstructionSeat(
|
|
|
337
331
|
}),
|
|
338
332
|
});
|
|
339
333
|
if (resumed !== undefined) return resumed;
|
|
334
|
+
} else {
|
|
335
|
+
const probedTicketNumber =
|
|
336
|
+
ticketProbe === undefined ? undefined : ticketNumberFromProbe(ticketProbe);
|
|
337
|
+
if (probedTicketNumber !== undefined) {
|
|
338
|
+
const resumed = await tryResumeSameTicketSeatRun({
|
|
339
|
+
home: env.home,
|
|
340
|
+
projectRoot,
|
|
341
|
+
role,
|
|
342
|
+
ticketNumber: probedTicketNumber,
|
|
343
|
+
freshSummons: env.freshSummons,
|
|
344
|
+
summons,
|
|
345
|
+
resume: (runId, materials) =>
|
|
346
|
+
runPublicInstructionSeatResume(
|
|
347
|
+
{ runId, ...(materials === undefined ? {} : { summons: materials }) },
|
|
348
|
+
env,
|
|
349
|
+
io,
|
|
350
|
+
),
|
|
351
|
+
});
|
|
352
|
+
if (resumed !== undefined) return resumed;
|
|
353
|
+
}
|
|
340
354
|
}
|
|
341
355
|
|
|
342
356
|
let admitted: AdmittedInstructionSeatInvocation;
|
|
@@ -360,6 +374,10 @@ export async function runPublicInstructionSeat(
|
|
|
360
374
|
throw error;
|
|
361
375
|
}
|
|
362
376
|
|
|
377
|
+
if (role === "auditor" && auditorSourceRun !== undefined) {
|
|
378
|
+
await persistAdmittedSourceRunPath(admitted, auditorSourceRun);
|
|
379
|
+
}
|
|
380
|
+
|
|
363
381
|
return await withAuditorSoulEnv({
|
|
364
382
|
...(auditorSubject === undefined ? {} : { subject: auditorSubject }),
|
|
365
383
|
...(auditorSourceRun === undefined
|
|
@@ -437,6 +437,35 @@ async function mergeInvocationIdentityPage(
|
|
|
437
437
|
);
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
+
/**
|
|
441
|
+
* Persist parent --source-run path onto admitted-request for officer resume lookup (#747).
|
|
442
|
+
* Reuses the existing notary sourceRunPath key; does not invent a new field name.
|
|
443
|
+
*/
|
|
444
|
+
export async function persistAdmittedSourceRunPath(
|
|
445
|
+
admitted: AdmittedRoleInvocation,
|
|
446
|
+
sourceRunPath: string,
|
|
447
|
+
): Promise<void> {
|
|
448
|
+
if (sourceRunPath.trim() === "") {
|
|
449
|
+
throw new Error("persistAdmittedSourceRunPath requires a non-empty sourceRunPath");
|
|
450
|
+
}
|
|
451
|
+
const admittedPath = admitted.admittedRequestPath;
|
|
452
|
+
const current = JSON.parse(await readFile(admittedPath, "utf8")) as Record<
|
|
453
|
+
string,
|
|
454
|
+
unknown
|
|
455
|
+
>;
|
|
456
|
+
if (typeof current.sourceRunPath === "string") {
|
|
457
|
+
if (current.sourceRunPath === sourceRunPath) return;
|
|
458
|
+
throw new Error(
|
|
459
|
+
`persistAdmittedSourceRunPath refuses to replace ${current.sourceRunPath} with ${sourceRunPath}`,
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
await writeFile(
|
|
463
|
+
admittedPath,
|
|
464
|
+
`${JSON.stringify({ ...current, sourceRunPath }, null, 2)}\n`,
|
|
465
|
+
"utf8",
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
440
469
|
/**
|
|
441
470
|
* Bind a post-admission resolved ticketNumber onto the in-memory admitted
|
|
442
471
|
* object and both durable pages (invocation.json + admitted-request.json).
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Public Notary Role run: admit source-run locator → shared post-admission coordinator
|
|
3
3
|
* → settle Terminal result (#448 / #517). Zero caller prompt/attachment. Lifecycle is
|
|
4
4
|
* the shared post-admission seam; this module keeps only Notary adapters.
|
|
5
|
-
* #637: same-
|
|
5
|
+
* #637 / #747: same-parent (--source-run) re-summons resume the seat's previous run.
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync } from "node:fs";
|
|
8
8
|
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
resolveNotarySourceRunLocator,
|
|
14
14
|
} from "../notary-source-run.ts";
|
|
15
15
|
import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.ts";
|
|
16
|
-
import { readRunTicketNumber } from "../run-ticket-number.ts";
|
|
17
16
|
import { CliUsageError } from "./cli-errors.ts";
|
|
18
17
|
import {
|
|
19
18
|
admitNotaryInvocation,
|
|
@@ -112,8 +111,8 @@ export async function runPublicNotary(
|
|
|
112
111
|
}
|
|
113
112
|
throw error;
|
|
114
113
|
}
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
// #747: officer resume key is this parent source-run path (not ticket number).
|
|
115
|
+
{
|
|
117
116
|
const summons: SameTicketSummonsMaterials = {
|
|
118
117
|
sourceRunPath: source.runDirectory,
|
|
119
118
|
sourceRun: source,
|
|
@@ -122,7 +121,7 @@ export async function runPublicNotary(
|
|
|
122
121
|
home: env.home,
|
|
123
122
|
projectRoot,
|
|
124
123
|
role: "notary",
|
|
125
|
-
|
|
124
|
+
parentRunPath: source.runDirectory,
|
|
126
125
|
freshSummons: env.freshSummons,
|
|
127
126
|
summons,
|
|
128
127
|
resume: (runId, materials) =>
|
|
@@ -1052,9 +1052,52 @@ export async function findRunDirectoryById(
|
|
|
1052
1052
|
return undefined;
|
|
1053
1053
|
}
|
|
1054
1054
|
|
|
1055
|
+
/** Code-owned gate inspector summons prefix (public-role-summons / #747). */
|
|
1056
|
+
export const GATE_DOSSIER_POINTER_PREFIX = "卷宗指针:" as const;
|
|
1057
|
+
|
|
1058
|
+
/** Parent path from a code-owned inspector 卷宗指针 instruction; else undefined. */
|
|
1059
|
+
export function parentRunPathFromGatePointerInstruction(
|
|
1060
|
+
instruction: string,
|
|
1061
|
+
): string | undefined {
|
|
1062
|
+
if (!instruction.startsWith(GATE_DOSSIER_POINTER_PREFIX)) return undefined;
|
|
1063
|
+
const path = instruction.slice(GATE_DOSSIER_POINTER_PREFIX.length).trim();
|
|
1064
|
+
return path === "" ? undefined : path;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1055
1067
|
/**
|
|
1056
|
-
*
|
|
1057
|
-
*
|
|
1068
|
+
* Parent-run binding on a retained officer run (#747).
|
|
1069
|
+
* Notary/auditor: typed sourceRunPath. Inspector: exact code-owned 卷宗指针 instruction.
|
|
1070
|
+
* Missing page → undefined; damage / non-ENOENT IO propagates.
|
|
1071
|
+
*/
|
|
1072
|
+
export async function readRunParentPath(
|
|
1073
|
+
runDirectory: string,
|
|
1074
|
+
): Promise<string | undefined> {
|
|
1075
|
+
let raw: unknown;
|
|
1076
|
+
try {
|
|
1077
|
+
raw = JSON.parse(
|
|
1078
|
+
await readFile(join(runDirectory, "admitted-request.json"), "utf8"),
|
|
1079
|
+
);
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
if (errorCodeOf(error) === "ENOENT") return undefined;
|
|
1082
|
+
throw error;
|
|
1083
|
+
}
|
|
1084
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1085
|
+
return undefined;
|
|
1086
|
+
}
|
|
1087
|
+
const record = raw as Record<string, unknown>;
|
|
1088
|
+
if (typeof record.sourceRunPath === "string" && record.sourceRunPath.trim() !== "") {
|
|
1089
|
+
return record.sourceRunPath;
|
|
1090
|
+
}
|
|
1091
|
+
if (typeof record.instruction === "string") {
|
|
1092
|
+
return parentRunPathFromGatePointerInstruction(record.instruction);
|
|
1093
|
+
}
|
|
1094
|
+
return undefined;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* Locate the latest retained run for one seat under a book (#637 / #747).
|
|
1099
|
+
* Same walk surface as findRunDirectoryById. Match by parent run path (officer
|
|
1100
|
+
* seats, #747) or by ticket number (countersign / diarist principal).
|
|
1058
1101
|
* runId is UUIDv7 — lexicographic max is latest. No parallel index.
|
|
1059
1102
|
* Only a truly missing runs directory means no history; damage/permission errors propagate.
|
|
1060
1103
|
*/
|
|
@@ -1062,7 +1105,8 @@ export async function findLatestRunIdForSeatTicket(input: {
|
|
|
1062
1105
|
readonly home: string;
|
|
1063
1106
|
readonly bookKey: string;
|
|
1064
1107
|
readonly role: RoleRunRecord["role"];
|
|
1065
|
-
readonly ticketNumber
|
|
1108
|
+
readonly ticketNumber?: number;
|
|
1109
|
+
readonly parentRunPath?: string;
|
|
1066
1110
|
}): Promise<string | undefined> {
|
|
1067
1111
|
const ledgerHome = resolveActivationLedgerHome(input.home);
|
|
1068
1112
|
const runsDir = join(
|
|
@@ -1082,8 +1126,16 @@ export async function findLatestRunIdForSeatTicket(input: {
|
|
|
1082
1126
|
if (!entry.endsWith(suffix)) continue;
|
|
1083
1127
|
const runId = entry.slice(0, entry.length - suffix.length);
|
|
1084
1128
|
if (runId.length === 0) continue;
|
|
1085
|
-
const
|
|
1086
|
-
if (
|
|
1129
|
+
const runDirectory = join(runsDir, entry);
|
|
1130
|
+
if (input.parentRunPath !== undefined) {
|
|
1131
|
+
const parentPath = await readRunParentPath(runDirectory);
|
|
1132
|
+
if (parentPath !== input.parentRunPath) continue;
|
|
1133
|
+
} else if (input.ticketNumber !== undefined) {
|
|
1134
|
+
const ticketNumber = await readRunTicketNumber(runDirectory);
|
|
1135
|
+
if (ticketNumber !== input.ticketNumber) continue;
|
|
1136
|
+
} else {
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1087
1139
|
if (best === undefined || runId > best) best = runId;
|
|
1088
1140
|
}
|
|
1089
1141
|
return best;
|
|
@@ -149,10 +149,11 @@ export async function resolveSeatTicketBinding(
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
/**
|
|
152
|
-
* Sole same-
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
152
|
+
* Sole same-seat → resume decision (#637 / #724 / #747).
|
|
153
|
+
* Officer seats (notary/inspector/auditor) look up by parent run path; countersign
|
|
154
|
+
* / diarist keep ticket-number principal. When found, runs resume with this
|
|
155
|
+
* summons' materials. Lookup/resume failures propagate (失败诚实) — never wash
|
|
156
|
+
* into a fresh mint. Returns undefined when the caller declared an explicit
|
|
156
157
|
* fresh summons (`ak-role new`) or when no prior run exists; both mint new.
|
|
157
158
|
* freshSummons is required so no seat can drift back into its own skip branch.
|
|
158
159
|
*/
|
|
@@ -160,7 +161,8 @@ export async function tryResumeSameTicketSeatRun<T>(input: {
|
|
|
160
161
|
readonly home: string;
|
|
161
162
|
readonly projectRoot: string;
|
|
162
163
|
readonly role: RoleRunRecord["role"];
|
|
163
|
-
readonly ticketNumber
|
|
164
|
+
readonly ticketNumber?: number;
|
|
165
|
+
readonly parentRunPath?: string;
|
|
164
166
|
readonly freshSummons: true | undefined;
|
|
165
167
|
readonly summons?: SameTicketSummonsMaterials;
|
|
166
168
|
readonly resume: (
|
|
@@ -169,11 +171,19 @@ export async function tryResumeSameTicketSeatRun<T>(input: {
|
|
|
169
171
|
) => Promise<T>;
|
|
170
172
|
}): Promise<T | undefined> {
|
|
171
173
|
if (input.freshSummons === true) return undefined;
|
|
174
|
+
if (input.parentRunPath === undefined && input.ticketNumber === undefined) {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
172
177
|
const previousRunId = await findLatestRunIdForSeatTicket({
|
|
173
178
|
home: input.home,
|
|
174
179
|
bookKey: resolveBookKeyFromGit(input.projectRoot),
|
|
175
180
|
role: input.role,
|
|
176
|
-
|
|
181
|
+
...(input.parentRunPath === undefined
|
|
182
|
+
? {}
|
|
183
|
+
: { parentRunPath: input.parentRunPath }),
|
|
184
|
+
...(input.ticketNumber === undefined
|
|
185
|
+
? {}
|
|
186
|
+
: { ticketNumber: input.ticketNumber }),
|
|
177
187
|
});
|
|
178
188
|
if (previousRunId === undefined) return undefined;
|
|
179
189
|
return await input.resume(previousRunId, input.summons);
|