@akagilnc/pi-workflow-roles 0.1.3671 → 0.1.3682
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 +272 -166
- package/dist/diarist-contracts.js +4 -4
- package/dist/diarist-role.js +10 -4
- package/dist/diarist.js +121 -17
- package/dist/package-contracts/terminating-tools.js +1 -1
- package/dist/public-cli/inspector-run.js +3 -13
- package/dist/public-cli/instruction-seat-run.js +6 -31
- package/dist/public-cli/invocation.js +32 -0
- package/dist/public-cli/judge-run.js +1 -7
- package/dist/public-cli/main.js +721 -754
- package/dist/public-cli/run-lifecycle.js +0 -28
- package/dist/public-cli/seat-ticket-binding.js +16 -113
- package/dist/role-runtime.js +89 -10
- package/package.json +1 -1
- package/resources/diarist-collect.md +10 -0
- package/src/diarist-contracts.ts +21 -9
- package/src/diarist-role.ts +15 -4
- package/src/diarist.ts +192 -22
- package/src/package-contracts/terminating-tools.ts +1 -1
- package/src/public-cli/coder-run.ts +1 -7
- package/src/public-cli/countersign-run.ts +238 -86
- package/src/public-cli/diarist-run.ts +83 -28
- package/src/public-cli/fixer-run.ts +1 -7
- package/src/public-cli/inspector-run.ts +3 -17
- package/src/public-cli/instruction-seat-run.ts +6 -36
- package/src/public-cli/invocation.ts +52 -0
- package/src/public-cli/judge-run.ts +1 -7
- package/src/public-cli/run-lifecycle.ts +0 -31
- package/src/public-cli/seat-ticket-binding.ts +15 -146
- package/src/role-runtime.ts +107 -9
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Public 起居郎 (diarist) terminating receipt contracts — ADR 0075 `diarist-is-role`.
|
|
3
|
-
* Lawful explicit
|
|
3
|
+
* Lawful explicit releases: completed (入录选择) | escalate (认不出本庭对象上抛).
|
|
4
4
|
* Machine facts about the volume come from the mechanical sitian seam, never
|
|
5
5
|
* from model self-report (锚定宪法); this module owns the receipt shape only.
|
|
6
6
|
*/
|
|
7
7
|
export const DIARIST_OUTPUT_TOOL_NAME = "ak_diarist_output";
|
|
8
8
|
export const DIARIST_ACCEPTED_TEXT = "起居郎回执已接受";
|
|
9
|
-
/** Internal transport: frozen candidate-catalog path for this turn
|
|
9
|
+
/** Internal transport: frozen candidate-catalog path for this turn. */
|
|
10
10
|
export const DIARIST_SOURCES_FLAG = {
|
|
11
11
|
name: "ak-diarist-sources",
|
|
12
12
|
definition: {
|
|
13
|
-
description: "Frozen
|
|
13
|
+
description: "Frozen source catalog the diarist selects from (may be unbound until LLM asserts ticketNumber)",
|
|
14
14
|
type: "string",
|
|
15
15
|
},
|
|
16
16
|
};
|
|
@@ -25,7 +25,7 @@ export function validateRecordedDiaristOutput(value) {
|
|
|
25
25
|
catch {
|
|
26
26
|
throw new Error("Diarist output has no execution discriminator");
|
|
27
27
|
}
|
|
28
|
-
if (status === "completed") {
|
|
28
|
+
if (status === "completed" || status === "escalate") {
|
|
29
29
|
return value;
|
|
30
30
|
}
|
|
31
31
|
throw new Error("Diarist output has no execution discriminator");
|
package/dist/diarist-role.js
CHANGED
|
@@ -7,15 +7,21 @@ export { validateRecordedDiaristOutput };
|
|
|
7
7
|
/** 起居郎交卷形状;形状指引,非 schema 闸。 */
|
|
8
8
|
export const diaristOutputSchema = withInfrastructureFailureDeclaration(openToolObject(Type.Object({
|
|
9
9
|
status: Type.Unknown({
|
|
10
|
-
description: "completed — 形状指引,非 schema 闸",
|
|
10
|
+
description: "completed | escalate — 形状指引,非 schema 闸",
|
|
11
11
|
}),
|
|
12
|
+
ticketNumber: Type.Optional(Type.Unknown({
|
|
13
|
+
description: "本庭对象票号(正整数)或 null/省略=真无票;机械验真后下游走 typed 键;认不出用 status=escalate,不洗成无录",
|
|
14
|
+
})),
|
|
15
|
+
reason: Type.Optional(Type.String({
|
|
16
|
+
description: "status 为 escalate 时:认不出本庭对象的原因",
|
|
17
|
+
})),
|
|
12
18
|
selections: Type.Array(Type.Object({
|
|
13
19
|
candidateIndex: Type.Number({ description: "候选目录中的整块序号" }),
|
|
14
20
|
quotes: Type.Array(Type.String(), {
|
|
15
21
|
description: "该块 transcript 内的连续原文;机械反验",
|
|
16
22
|
}),
|
|
17
23
|
note: Type.Optional(Type.String({ description: "该材料与本案的关系(人读)" })),
|
|
18
|
-
}, { additionalProperties: true, description: "一条入录选择" }), { description: "
|
|
24
|
+
}, { additionalProperties: true, description: "一条入录选择" }), { description: "入录选择;空列表合法完局;escalate 时可不交" }),
|
|
19
25
|
})));
|
|
20
26
|
/**
|
|
21
27
|
* 决定工具规格。生命周期装配归注册信封 owner——src/role-runtime.ts(ADR 0018)。
|
|
@@ -23,7 +29,7 @@ export const diaristOutputSchema = withInfrastructureFailureDeclaration(openTool
|
|
|
23
29
|
export const DIARIST_TOOL_SPEC = {
|
|
24
30
|
name: DIARIST_OUTPUT_TOOL_NAME,
|
|
25
31
|
label: "起居郎输出",
|
|
26
|
-
description: "
|
|
27
|
-
promptSnippet: "
|
|
32
|
+
description: "起居郎入录选择与本票身份断言;认不出本庭对象则 escalate。",
|
|
33
|
+
promptSnippet: "起居郎入录选择与本票身份",
|
|
28
34
|
parameters: diaristOutputSchema,
|
|
29
35
|
};
|
package/dist/diarist.js
CHANGED
|
@@ -13,6 +13,81 @@ import { parseGitHubOriginRemote } from "./reviewer-pinned-git.js";
|
|
|
13
13
|
import { appendIssueSourceFailureDiagnostic, appendQuoteVerifyFailureDiagnostic, appendTicketProvenanceEntry, ensureTicketProvenanceVolume, readOfferedIdentities, readTicketProvenance, recordOfferedIdentities, resolveTicketProvenanceVolume, ticketProvenanceEntryIdentity, writeTicketProvenanceHumanView, } from "./ticket-provenance.js";
|
|
14
14
|
import { readSitianRecords } from "./sitian-facade.js";
|
|
15
15
|
import { execFileSync } from "node:child_process";
|
|
16
|
+
/**
|
|
17
|
+
* Honest failure of mechanical ticket verification (ADR 0075).
|
|
18
|
+
* Must settle as failure — never wash into true-unbound / 无录.
|
|
19
|
+
*/
|
|
20
|
+
export class DiaristTicketVerificationError extends Error {
|
|
21
|
+
code = "diarist-ticket-verification";
|
|
22
|
+
reason;
|
|
23
|
+
constructor(reason, message, options) {
|
|
24
|
+
super(message, options);
|
|
25
|
+
this.name = "DiaristTicketVerificationError";
|
|
26
|
+
this.reason = reason;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Decision-key exact identity: complete decimal number N appears in instruction.
|
|
31
|
+
* A longer number's digit substring (e.g. 82 inside 582) is not N.
|
|
32
|
+
* Verifies a typed claim — does not harvest candidates from prose.
|
|
33
|
+
*/
|
|
34
|
+
export function instructionContainsTicketNumber(instruction, ticketNumber) {
|
|
35
|
+
if (!Number.isSafeInteger(ticketNumber) || ticketNumber < 1)
|
|
36
|
+
return false;
|
|
37
|
+
// Complete decimal token: not preceded or followed by another digit.
|
|
38
|
+
// Plain string (not template) so the digit-class backslash survives emit.
|
|
39
|
+
return new RegExp("(?<!\\d)" + String(ticketNumber) + "(?!\\d)").test(instruction);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Production live-ticket check over shared gh issue-body projection.
|
|
43
|
+
* Available issue face → exists; unavailable/invalid → missing.
|
|
44
|
+
*/
|
|
45
|
+
export function createGhTicketExistenceChecker(options) {
|
|
46
|
+
const runner = options?.runner ?? createGhApiRunner();
|
|
47
|
+
return async (input) => {
|
|
48
|
+
const projected = await projectGhIssueBody(runner, {
|
|
49
|
+
owner: input.owner,
|
|
50
|
+
repo: input.repo,
|
|
51
|
+
ticketNumber: input.ticketNumber,
|
|
52
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
53
|
+
});
|
|
54
|
+
return projected.status === "available";
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Mechanical verify of an LLM typed ticket assertion (ADR 0075).
|
|
59
|
+
* Complete decimal of N must appear in the summons; ticket must exist live.
|
|
60
|
+
* Throws DiaristTicketVerificationError — caller must not wash into 无录.
|
|
61
|
+
*/
|
|
62
|
+
export async function verifyAssertedTicketNumber(input) {
|
|
63
|
+
const n = input.ticketNumber;
|
|
64
|
+
if (!Number.isSafeInteger(n) || n < 1) {
|
|
65
|
+
throw new DiaristTicketVerificationError("assertion-uninterpretable", `diarist ticket assertion must be a safe integer >= 1, got ${String(n)}`);
|
|
66
|
+
}
|
|
67
|
+
if (!instructionContainsTicketNumber(input.instruction, n)) {
|
|
68
|
+
throw new DiaristTicketVerificationError("number-not-in-instruction", `diarist ticket assertion #${n} complete decimal number does not appear in accepted instruction`);
|
|
69
|
+
}
|
|
70
|
+
const origin = resolveDiaristGithubOrigin(input.projectRoot);
|
|
71
|
+
if (origin === undefined) {
|
|
72
|
+
throw new DiaristTicketVerificationError("origin-unresolved", `diarist ticket assertion #${n} requires a resolvable github.com origin remote for live verification`);
|
|
73
|
+
}
|
|
74
|
+
const checkExistence = input.checkExistence ?? createGhTicketExistenceChecker();
|
|
75
|
+
let exists;
|
|
76
|
+
try {
|
|
77
|
+
exists = await checkExistence({
|
|
78
|
+
owner: origin.owner,
|
|
79
|
+
repo: origin.repo,
|
|
80
|
+
ticketNumber: n,
|
|
81
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
throw new DiaristTicketVerificationError("ticket-missing", `diarist ticket assertion #${n} live verification failed`, { cause: error });
|
|
86
|
+
}
|
|
87
|
+
if (!exists) {
|
|
88
|
+
throw new DiaristTicketVerificationError("ticket-missing", `diarist ticket assertion #${n} does not exist as a live issue on ${origin.owner}/${origin.repo}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
16
91
|
/** Bound-ticket issue source failure — not a soft degrade to empty face. */
|
|
17
92
|
export class DiaristIssueSourceError extends Error {
|
|
18
93
|
code = "diarist-issue-source";
|
|
@@ -181,26 +256,36 @@ function loadSourceBlocks(input) {
|
|
|
181
256
|
}
|
|
182
257
|
/**
|
|
183
258
|
* Mechanical half A — source enumeration into a frozen catalog.
|
|
184
|
-
*
|
|
185
|
-
* `ticket-provenance-file` 每票一份起居录)
|
|
186
|
-
*
|
|
259
|
+
* When ticketNumber is known: establishes the per-ticket volume + human view
|
|
260
|
+
* (ADR 0075 `ticket-provenance-file` 每票一份起居录) and offers only blocks not
|
|
261
|
+
* yet on the volume / offered watermark (增量幂等).
|
|
262
|
+
* When ticketNumber is absent (first summons): offers session candidates without
|
|
263
|
+
* minting a volume — the LLM turn asserts identity; accept verifies and mints.
|
|
187
264
|
*/
|
|
188
265
|
export async function prepareDiaristSourceCatalog(input) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
266
|
+
const known = input.ticketNumber;
|
|
267
|
+
if (known !== undefined) {
|
|
268
|
+
ensureTicketProvenanceVolume(known, input.cwd, input.home);
|
|
269
|
+
const volume = await readTicketProvenance(known, input.cwd, input.home);
|
|
270
|
+
writeTicketProvenanceHumanView({
|
|
271
|
+
ticketNumber: known,
|
|
272
|
+
cwd: input.cwd,
|
|
273
|
+
...(input.home === undefined ? {} : { home: input.home }),
|
|
274
|
+
entries: volume.entries,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
197
277
|
const rawBlocks = loadSourceBlocks(input);
|
|
198
278
|
// Safeguard only (notify filter + dedupe) — never prose-based exclusion.
|
|
199
279
|
const safeguarded = mechanicalSafeguardPipeline(rawBlocks);
|
|
200
|
-
const seen =
|
|
201
|
-
|
|
280
|
+
const seen = known === undefined
|
|
281
|
+
? new Set()
|
|
282
|
+
: await loadSeenEntryIdentities(known, input.cwd, input.home);
|
|
283
|
+
const fresh = safeguarded.filter((block) => known === undefined || !seen.has(blockEntryIdentity(known, block)));
|
|
202
284
|
return {
|
|
203
|
-
ticketNumber:
|
|
285
|
+
...(known === undefined ? {} : { ticketNumber: known }),
|
|
286
|
+
instruction: input.instruction,
|
|
287
|
+
runDirectory: input.runDirectory,
|
|
288
|
+
projectRoot: input.projectRoot,
|
|
204
289
|
cwd: input.cwd,
|
|
205
290
|
...(input.home === undefined ? {} : { home: input.home }),
|
|
206
291
|
candidates: fresh.map((block, candidateIndex) => ({ ...block, candidateIndex })),
|
|
@@ -216,8 +301,23 @@ export function loadDiaristSourceCatalog(path) {
|
|
|
216
301
|
throw new Error(`diarist source catalog is not an object (${path})`);
|
|
217
302
|
}
|
|
218
303
|
const record = parsed;
|
|
219
|
-
if (typeof record.
|
|
220
|
-
throw new Error(`diarist source catalog is missing
|
|
304
|
+
if (typeof record.cwd !== "string") {
|
|
305
|
+
throw new Error(`diarist source catalog is missing cwd (${path})`);
|
|
306
|
+
}
|
|
307
|
+
if (typeof record.instruction !== "string") {
|
|
308
|
+
throw new Error(`diarist source catalog is missing instruction (${path})`);
|
|
309
|
+
}
|
|
310
|
+
if (typeof record.runDirectory !== "string") {
|
|
311
|
+
throw new Error(`diarist source catalog is missing runDirectory (${path})`);
|
|
312
|
+
}
|
|
313
|
+
if (typeof record.projectRoot !== "string") {
|
|
314
|
+
throw new Error(`diarist source catalog is missing projectRoot (${path})`);
|
|
315
|
+
}
|
|
316
|
+
if (record.ticketNumber !== undefined &&
|
|
317
|
+
(typeof record.ticketNumber !== "number" ||
|
|
318
|
+
!Number.isSafeInteger(record.ticketNumber) ||
|
|
319
|
+
record.ticketNumber < 1)) {
|
|
320
|
+
throw new Error(`diarist source catalog ticketNumber is not a safe ticket (${path})`);
|
|
221
321
|
}
|
|
222
322
|
if (!Array.isArray(record.candidates)) {
|
|
223
323
|
throw new Error(`diarist source catalog is missing candidates (${path})`);
|
|
@@ -231,7 +331,11 @@ export function loadDiaristSourceCatalog(path) {
|
|
|
231
331
|
* never bounces the receipt (第 0 条) and never enters the volume as an entry.
|
|
232
332
|
*/
|
|
233
333
|
export async function commitDiaristSelections(input) {
|
|
234
|
-
const
|
|
334
|
+
const ticketNumber = input.catalog.ticketNumber;
|
|
335
|
+
if (ticketNumber === undefined) {
|
|
336
|
+
throw new Error("commitDiaristSelections requires a bound ticketNumber on the catalog");
|
|
337
|
+
}
|
|
338
|
+
const { cwd } = input.catalog;
|
|
235
339
|
const homeOpt = input.catalog.home === undefined ? {} : { home: input.catalog.home };
|
|
236
340
|
const volumePaths = ensureTicketProvenanceVolume(ticketNumber, cwd, input.catalog.home);
|
|
237
341
|
const anchors = buildDiaristAnchors({ ticketNumber });
|
|
@@ -123,7 +123,7 @@ export function validateAcceptedDetails(toolName, details) {
|
|
|
123
123
|
[GATEKEEPER_OUTPUT_TOOL_NAME]: ["dispatch", "pass"],
|
|
124
124
|
[NAVIGATOR_OUTPUT_TOOL_NAME]: ["advice"],
|
|
125
125
|
[AUDITOR_OUTPUT_TOOL_NAME]: ["pass", "bounce", "escalate"],
|
|
126
|
-
[DIARIST_OUTPUT_TOOL_NAME]: ["completed"],
|
|
126
|
+
[DIARIST_OUTPUT_TOOL_NAME]: ["completed", "escalate"],
|
|
127
127
|
};
|
|
128
128
|
const collectorDiscriminator = toolName === COLLECTOR_OUTPUT_TOOL && Array.isArray(candidate?.groups);
|
|
129
129
|
const baseDiscriminator = discriminator;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.js";
|
|
2
2
|
import { CliUsageError } from "./cli-errors.js";
|
|
3
|
-
import {
|
|
3
|
+
import { 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
6
|
import { loadResumableInspectorRun, markRunAdmitted, parentRunPathFromGatePointerInstruction, } from "./run-lifecycle.js";
|
|
@@ -27,14 +27,9 @@ export async function runPublicInspector(argv, env, io, parseInspectorArgv) {
|
|
|
27
27
|
throw error;
|
|
28
28
|
}
|
|
29
29
|
// #747: same parent (卷宗指针) → resume prior inspector run with this summons' materials.
|
|
30
|
-
// #
|
|
30
|
+
// #771: no mechanical ticket match against instruction text; parent-run path only.
|
|
31
31
|
// No bare catch→fresh: lookup/resume failures surface; only true absence mints new.
|
|
32
32
|
const projectRoot = parsed.project ?? env.cwd;
|
|
33
|
-
const reusedTicketNumber = await resolveKnownTicketNumber({
|
|
34
|
-
instruction: parsed.instruction,
|
|
35
|
-
projectRoot,
|
|
36
|
-
home: env.home,
|
|
37
|
-
});
|
|
38
33
|
const parentRunPath = parentRunPathFromGatePointerInstruction(parsed.instruction);
|
|
39
34
|
if (parentRunPath !== undefined) {
|
|
40
35
|
const summons = {
|
|
@@ -100,12 +95,7 @@ export async function runPublicInspector(argv, env, io, parseInspectorArgv) {
|
|
|
100
95
|
env,
|
|
101
96
|
io,
|
|
102
97
|
request: turnRequest,
|
|
103
|
-
adapters: inspectorAdapters(
|
|
104
|
-
beforeDispatch: async (admittedSeat) => {
|
|
105
|
-
// #635/#709: bind the reused identity inside the controlled-failure boundary.
|
|
106
|
-
await bindReusedTicketNumber(admittedSeat, reusedTicketNumber);
|
|
107
|
-
},
|
|
108
|
-
}),
|
|
98
|
+
adapters: inspectorAdapters(),
|
|
109
99
|
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
110
100
|
});
|
|
111
101
|
}
|
|
@@ -4,7 +4,7 @@ import { CliUsageError } from "./cli-errors.js";
|
|
|
4
4
|
import { admitAuditorInvocation, 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
|
-
import {
|
|
7
|
+
import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
|
|
8
8
|
import { presentStructuralRejection, readEngineDetourInfrastructureFailure, trySettleAuditorTerminalResult, trySettleGatekeeperTerminalResult, trySettleNavigatorTerminalResult, } from "./settlement.js";
|
|
9
9
|
import { projectRoleTurnRequest, } from "./turn-request.js";
|
|
10
10
|
/** Project an admitted instruction-seat invocation onto the host-neutral turn request. */
|
|
@@ -119,10 +119,10 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
|
|
|
119
119
|
let auditorSubject;
|
|
120
120
|
let auditorSourceRun;
|
|
121
121
|
let auditorSourceTicket;
|
|
122
|
-
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
122
|
+
// #637 / #747 / #771: resume prior seat run with this summons' materials.
|
|
123
|
+
// Auditor (#747): parent --source-run path is the lookup key (typed inherit).
|
|
124
|
+
// Other instruction seats: no mechanical ticket match against instruction text.
|
|
125
|
+
// No bare catch→fresh.
|
|
126
126
|
const projectRoot = parsed.project ?? env.cwd;
|
|
127
127
|
if (role === "auditor") {
|
|
128
128
|
if (parsed.subject !== "judge" && parsed.subject !== "doctor") {
|
|
@@ -150,13 +150,6 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
|
|
|
150
150
|
return { exitCode: 2 };
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
|
-
else {
|
|
154
|
-
reusedTicketNumber = await resolveKnownTicketNumber({
|
|
155
|
-
instruction: parsed.instruction,
|
|
156
|
-
projectRoot,
|
|
157
|
-
home: env.home,
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
153
|
const summons = {
|
|
161
154
|
instruction: parsed.instruction,
|
|
162
155
|
instructionEmpty: parsed.instruction.trim() === "",
|
|
@@ -179,19 +172,6 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
|
|
|
179
172
|
if (resumed !== undefined)
|
|
180
173
|
return resumed;
|
|
181
174
|
}
|
|
182
|
-
else if (reusedTicketNumber !== undefined) {
|
|
183
|
-
const resumed = await tryResumeSameTicketSeatRun({
|
|
184
|
-
home: env.home,
|
|
185
|
-
projectRoot,
|
|
186
|
-
role,
|
|
187
|
-
ticketNumber: reusedTicketNumber,
|
|
188
|
-
freshSummons: env.freshSummons,
|
|
189
|
-
summons,
|
|
190
|
-
resume: (runId, materials) => runPublicInstructionSeatResume({ runId, ...(materials === undefined ? {} : { summons: materials }) }, env, io),
|
|
191
|
-
});
|
|
192
|
-
if (resumed !== undefined)
|
|
193
|
-
return resumed;
|
|
194
|
-
}
|
|
195
175
|
let admitted;
|
|
196
176
|
try {
|
|
197
177
|
admitted = await admitInstructionSeat(role, {
|
|
@@ -248,15 +228,10 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
|
|
|
248
228
|
request: turnRequest,
|
|
249
229
|
adapters: instructionSeatAdapters({
|
|
250
230
|
beforeDispatch: async (admittedSeat) => {
|
|
251
|
-
//
|
|
252
|
-
// Auditor inherits source-run ticket (notary face).
|
|
253
|
-
// Other seats: reuse known ticket identity.
|
|
231
|
+
// Auditor inherits source-run ticket (notary face) — typed, not prose match.
|
|
254
232
|
if (auditorSourceTicket !== undefined) {
|
|
255
233
|
await bindAdmittedTicketNumber(admittedSeat, auditorSourceTicket);
|
|
256
234
|
}
|
|
257
|
-
else {
|
|
258
|
-
await bindReusedTicketNumber(admittedSeat, reusedTicketNumber);
|
|
259
|
-
}
|
|
260
235
|
},
|
|
261
236
|
}),
|
|
262
237
|
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Attachments, project default/override (ADR 0052 / #106).
|
|
4
4
|
*/
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
6
7
|
import { lstat, mkdir, readFile, realpath, writeFile, } from "node:fs/promises";
|
|
7
8
|
import { basename, isAbsolute, join, resolve, sep } from "node:path";
|
|
8
9
|
import { activationBookDirectory, ensureRealDirectoryTree, homeFromRunDirectory, pathContainedIn, resolveActivationLedgerHome, } from "../activation-ledger-topology.js";
|
|
@@ -164,6 +165,37 @@ export async function bindAdmittedTicketNumber(admitted, ticketNumber) {
|
|
|
164
165
|
const current = JSON.parse(await readFile(admittedPath, "utf8"));
|
|
165
166
|
await writeFile(admittedPath, `${JSON.stringify({ ...current, ticketNumber }, null, 2)}\n`, "utf8");
|
|
166
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Bind ticket identity onto a run directory's durable pages when the admitted
|
|
170
|
+
* object is not in hand (起居郎 accept hook after LLM assertion, #771).
|
|
171
|
+
* Idempotent when the same number is already on the pages.
|
|
172
|
+
*/
|
|
173
|
+
export async function bindTicketNumberOnRunDirectory(runDirectory, ticketNumber) {
|
|
174
|
+
if (!Number.isSafeInteger(ticketNumber) || ticketNumber < 1) {
|
|
175
|
+
throw new Error(`bindTicketNumberOnRunDirectory requires a safe positive integer, got ${String(ticketNumber)}`);
|
|
176
|
+
}
|
|
177
|
+
const admittedPath = join(runDirectory, "admitted-request.json");
|
|
178
|
+
const invocationPath = join(runDirectory, "invocation.json");
|
|
179
|
+
const admitted = JSON.parse(await readFile(admittedPath, "utf8"));
|
|
180
|
+
const existing = admitted.ticketNumber;
|
|
181
|
+
if (typeof existing === "number") {
|
|
182
|
+
if (existing === ticketNumber)
|
|
183
|
+
return;
|
|
184
|
+
throw new Error(`bindTicketNumberOnRunDirectory refuses to replace existing ticket #${existing} with #${ticketNumber}`);
|
|
185
|
+
}
|
|
186
|
+
// Conflict guard before any write: crash window of bindAdmittedTicketNumber
|
|
187
|
+
// can leave invocation bound while admitted-request is still unbound — refuse
|
|
188
|
+
// silent rebind. Read-before-merge; never check the page just overwritten.
|
|
189
|
+
if (existsSync(invocationPath)) {
|
|
190
|
+
const invocation = JSON.parse(await readFile(invocationPath, "utf8"));
|
|
191
|
+
if (typeof invocation.ticketNumber === "number" &&
|
|
192
|
+
invocation.ticketNumber !== ticketNumber) {
|
|
193
|
+
throw new Error(`bindTicketNumberOnRunDirectory refuses to replace invocation ticket #${invocation.ticketNumber} with #${ticketNumber}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
await writeFile(admittedPath, `${JSON.stringify({ ...admitted, ticketNumber }, null, 2)}\n`, "utf8");
|
|
197
|
+
await mergeInvocationIdentityPage(runDirectory, { ticketNumber });
|
|
198
|
+
}
|
|
167
199
|
/** Add the identity returned by the production Pi launch seam to its existing ledger page. */
|
|
168
200
|
export async function recordLaunchedPiIdentity(runDirectory, identity) {
|
|
169
201
|
await mergeInvocationIdentityPage(runDirectory, {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.js";
|
|
2
2
|
import { CliUsageError } from "./cli-errors.js";
|
|
3
3
|
import { admitJudgeInvocation, buildJudgeTransportPrompt, } from "./invocation.js";
|
|
4
|
-
import { resolveSeatTicketBinding } from "./seat-ticket-binding.js";
|
|
5
4
|
import { loadResumableJudgeRun, markRunAdmitted, buildResumeContinuationPrompt, } from "./run-lifecycle.js";
|
|
6
5
|
import { presentStructuralRejection, readEngineDetourInfrastructureFailure, trySettleJudgeTerminalResult, } from "./settlement.js";
|
|
7
6
|
import { projectRoleTurnRequest, } from "./turn-request.js";
|
|
@@ -91,12 +90,7 @@ export async function runPublicJudge(argv, env, io, parseJudgeArgv) {
|
|
|
91
90
|
}),
|
|
92
91
|
},
|
|
93
92
|
}),
|
|
94
|
-
adapters:
|
|
95
|
-
...judgeAdapters(),
|
|
96
|
-
beforeDispatch: async (admittedSeat) => {
|
|
97
|
-
await resolveSeatTicketBinding(admittedSeat, env);
|
|
98
|
-
},
|
|
99
|
-
},
|
|
93
|
+
adapters: judgeAdapters(),
|
|
100
94
|
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
101
95
|
});
|
|
102
96
|
}
|