@akagilnc/pi-workflow-roles 0.1.3708 → 0.1.3712
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 +151 -514
- package/dist/adr-path-refs.js +1 -1
- package/dist/diarist-contracts.js +31 -21
- package/dist/diarist-role.js +17 -8
- package/dist/diarist.js +39 -388
- package/dist/package-contracts/terminating-tools.js +1 -1
- package/dist/packaged-role-registry.js +4 -8
- package/dist/pi/role-turn-host.js +1 -7
- package/dist/public-cli/main.js +93 -1470
- package/dist/role-runtime.js +60 -88
- package/dist/ticket-provenance-contracts.js +0 -5
- package/dist/ticket-provenance.js +2 -149
- package/package.json +1 -1
- package/resources/diarist-collect.md +21 -11
- package/src/acp-host/role-envelope.ts +1 -1
- package/src/adr-path-refs.ts +1 -1
- package/src/diarist-contracts.ts +48 -32
- package/src/diarist-role.ts +31 -16
- package/src/diarist.ts +54 -635
- package/src/host-contracts.ts +1 -5
- package/src/package-contracts/terminating-tools.ts +1 -1
- package/src/packaged-role-registry.ts +4 -8
- package/src/pi/role-turn-host.ts +1 -7
- package/src/public-cli/countersign-run.ts +2 -3
- package/src/public-cli/diarist-run.ts +15 -117
- package/src/role-runtime.ts +73 -105
- package/src/ticket-provenance-contracts.ts +6 -12
- package/src/ticket-provenance.ts +1 -207
- package/dist/diarist-mechanical.js +0 -411
- package/src/diarist-mechanical.ts +0 -571
package/src/host-contracts.ts
CHANGED
|
@@ -127,11 +127,7 @@ export type RoleTurnActivation =
|
|
|
127
127
|
| { readonly role: "gatekeeper" }
|
|
128
128
|
| { readonly role: "navigator" }
|
|
129
129
|
| { readonly role: "auditor" }
|
|
130
|
-
| {
|
|
131
|
-
readonly role: "diarist";
|
|
132
|
-
/** Frozen source-catalog path; absent for a true-unbound summons (#708). */
|
|
133
|
-
readonly sourcesPath?: string;
|
|
134
|
-
};
|
|
130
|
+
| { readonly role: "diarist" };
|
|
135
131
|
|
|
136
132
|
export type RoleTurnContinuation =
|
|
137
133
|
| { readonly kind: "initial"; readonly prompt: string }
|
|
@@ -303,7 +303,7 @@ export function validateAcceptedLifecycle(
|
|
|
303
303
|
if (toolName === DIARIST_OUTPUT_TOOL_NAME) {
|
|
304
304
|
// Envelope-owned mechanical sitian facts are runtime-bound on details only
|
|
305
305
|
// (same shape as the Doctor runtime cost); the submitted arguments carry the
|
|
306
|
-
// role's own
|
|
306
|
+
// role's own entries. Machine facts never come from model self-report.
|
|
307
307
|
const { sitian: _mechanical, ...submitted } = details as DiaristOutput & { sitian?: unknown };
|
|
308
308
|
const testimony = validateAcceptedDetails(toolName, argumentsValue);
|
|
309
309
|
if (!deepEqual(testimony, submitted)) throw new Error("accepted tool lifecycle details mismatch");
|
|
@@ -12,10 +12,7 @@ import { COUNTERSIGN_OUTPUT_TOOL_NAME } from "./countersign-contracts.ts";
|
|
|
12
12
|
import { GLEANER_LEFT_OUTPUT_TOOL_NAME } from "./gleaner-left-contracts.ts";
|
|
13
13
|
import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.ts";
|
|
14
14
|
import { AUDITOR_OUTPUT_TOOL_NAME } from "./package-contracts/auditor-output.ts";
|
|
15
|
-
import {
|
|
16
|
-
DIARIST_OUTPUT_TOOL_NAME,
|
|
17
|
-
DIARIST_SOURCES_FLAG,
|
|
18
|
-
} from "./diarist-contracts.ts";
|
|
15
|
+
import { DIARIST_OUTPUT_TOOL_NAME } from "./diarist-contracts.ts";
|
|
19
16
|
|
|
20
17
|
/** Shared by public notary and gatekeeper-province notary. */
|
|
21
18
|
export const NOTARY_SESSION_MATERIALS = [
|
|
@@ -214,14 +211,13 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
214
211
|
activationStage: "load-and-install",
|
|
215
212
|
sessionMaterials: AUDITOR_PUBLIC_SESSION_MATERIALS,
|
|
216
213
|
},
|
|
217
|
-
// #708 / ADR 0075 `diarist-is-role
|
|
218
|
-
//
|
|
219
|
-
// true-unbound summons (no ticket → no diary).
|
|
214
|
+
// #708 / ADR 0075 `diarist-is-role` / #779: 起居郎 is a seat like any other.
|
|
215
|
+
// No input flag — LLM finds materials itself (caller-transparent).
|
|
220
216
|
{
|
|
221
217
|
role: "diarist",
|
|
222
218
|
phases: [null],
|
|
223
219
|
outputTool: DIARIST_OUTPUT_TOOL_NAME,
|
|
224
|
-
inputFlag:
|
|
220
|
+
inputFlag: undefined,
|
|
225
221
|
phaseFlag: undefined,
|
|
226
222
|
activationStage: "load-and-install",
|
|
227
223
|
sessionMaterials: [
|
package/src/pi/role-turn-host.ts
CHANGED
|
@@ -149,13 +149,7 @@ function buildActivationFlagArgs(activation: RoleTurnActivation): string[] {
|
|
|
149
149
|
case "auditor":
|
|
150
150
|
return ["--ak-role", "auditor"];
|
|
151
151
|
case "diarist":
|
|
152
|
-
return [
|
|
153
|
-
"--ak-role",
|
|
154
|
-
"diarist",
|
|
155
|
-
...(activation.sourcesPath === undefined
|
|
156
|
-
? []
|
|
157
|
-
: ["--ak-diarist-sources", activation.sourcesPath]),
|
|
158
|
-
];
|
|
152
|
+
return ["--ak-role", "diarist"];
|
|
159
153
|
default: {
|
|
160
154
|
const _exhaustive: never = activation;
|
|
161
155
|
return _exhaustive;
|
|
@@ -104,8 +104,7 @@ type CourtDiaristIdentity =
|
|
|
104
104
|
* Non-zero exit without escalate status is not rethrown here — caller decides
|
|
105
105
|
* whether refresh must fail or first-entry settles controlled failure.
|
|
106
106
|
* When `boundTicketNumber` is set (typed handoff from countersign), diarist
|
|
107
|
-
*
|
|
108
|
-
* recognition from prose.
|
|
107
|
+
* binds under that key before the turn — never mechanical recognition from prose.
|
|
109
108
|
*/
|
|
110
109
|
async function invokeCourtDiarist(input: {
|
|
111
110
|
readonly instruction: string;
|
|
@@ -218,7 +217,7 @@ export async function runCountersignCourtDiaristStation(
|
|
|
218
217
|
instruction: `整理 #${admitted.ticketNumber} 的本案依据。`,
|
|
219
218
|
projectRoot: admitted.projectRoot,
|
|
220
219
|
failureLabel: `ticket #${admitted.ticketNumber}`,
|
|
221
|
-
// Refresh holds a typed key — hand it off so
|
|
220
|
+
// Refresh holds a typed key — hand it off so identity is bound before turn.
|
|
222
221
|
boundTicketNumber: admitted.ticketNumber,
|
|
223
222
|
},
|
|
224
223
|
env,
|
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Public Diarist (起居郎) Role run — #708 / ADR 0075 `diarist-is-role
|
|
2
|
+
* Public Diarist (起居郎) Role run — #708 / ADR 0075 `diarist-is-role` / #779.
|
|
3
3
|
* Same admit → post-admission → settle shape as the other instruction seats.
|
|
4
|
-
* Semantic collection is the role's own turn
|
|
5
|
-
*
|
|
4
|
+
* Semantic collection is the role's own turn: LLM finds materials itself
|
|
5
|
+
* (no frozen candidate catalog, no path/attachment burden on the caller).
|
|
6
6
|
* Who calls it and in what order is the caller's business (ADR 0010 `no-call-rule`).
|
|
7
7
|
*/
|
|
8
|
-
import { existsSync } from "node:fs";
|
|
9
|
-
import { writeFile } from "node:fs/promises";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
|
|
12
8
|
import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contracts.ts";
|
|
13
|
-
import {
|
|
14
|
-
loadDiaristIssueFace,
|
|
15
|
-
prepareDiaristSourceCatalog,
|
|
16
|
-
serializeDiaristSourceCatalog,
|
|
17
|
-
} from "../diarist.ts";
|
|
18
9
|
import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.ts";
|
|
19
10
|
import { CliUsageError } from "./cli-errors.ts";
|
|
20
11
|
import {
|
|
@@ -35,7 +26,6 @@ import {
|
|
|
35
26
|
import {
|
|
36
27
|
loadResumableDiaristRun,
|
|
37
28
|
markRunAdmitted,
|
|
38
|
-
readRoleRunIdentity,
|
|
39
29
|
type PublicResumeRequest,
|
|
40
30
|
type SameTicketSummonsMaterials,
|
|
41
31
|
} from "./run-lifecycle.ts";
|
|
@@ -57,107 +47,29 @@ export type DiaristRunEnv = PostAdmissionEnv & {
|
|
|
57
47
|
/**
|
|
58
48
|
* Typed handoff from a caller that already holds a verified ticket key
|
|
59
49
|
* (countersign refresh / post-assert). Never derived from summons prose.
|
|
60
|
-
* When set: same-ticket resume under that key, else bind
|
|
61
|
-
*
|
|
50
|
+
* When set: same-ticket resume under that key, else bind before the turn
|
|
51
|
+
* so identity is already on the run pages (ADR 0075 / 0081).
|
|
62
52
|
*/
|
|
63
53
|
boundTicketNumber?: number;
|
|
64
54
|
};
|
|
65
55
|
|
|
66
|
-
/** Frozen catalog filename inside the run dossier (durable material, not argv). */
|
|
67
|
-
const DIARIST_SOURCE_CATALOG_FILE = "diarist-sources.json" as const;
|
|
68
|
-
|
|
69
|
-
/** Neutral path identifier only (ADR 0073) — catalog bytes stay on disk. */
|
|
70
|
-
function withDiaristCatalogPath(
|
|
71
|
-
prompt: string,
|
|
72
|
-
sourcesPath: string | undefined,
|
|
73
|
-
): string {
|
|
74
|
-
if (sourcesPath === undefined || sourcesPath.trim() === "") return prompt;
|
|
75
|
-
return `${prompt}\n\n已冻结来源文件(路径):\n- ${sourcesPath}`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
56
|
/** Project admitted invocation onto the host-neutral turn request. */
|
|
79
57
|
export function buildDiaristTurnRequest(
|
|
80
58
|
admitted: AdmittedDiaristInvocation,
|
|
81
59
|
options: RoleTurnRequestProjectionOptions,
|
|
82
|
-
sourcesPath?: string,
|
|
83
60
|
): RoleTurnRequest {
|
|
84
|
-
const continuation =
|
|
85
|
-
sourcesPath === undefined
|
|
86
|
-
? options.continuation
|
|
87
|
-
: {
|
|
88
|
-
...options.continuation,
|
|
89
|
-
prompt: withDiaristCatalogPath(options.continuation.prompt, sourcesPath),
|
|
90
|
-
};
|
|
91
61
|
return projectRoleTurnRequest(
|
|
92
62
|
admitted,
|
|
93
|
-
{
|
|
94
|
-
|
|
95
|
-
role: "diarist" as const,
|
|
96
|
-
...(sourcesPath === undefined ? {} : { sourcesPath }),
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
{ ...options, continuation },
|
|
63
|
+
{ activation: { role: "diarist" as const } },
|
|
64
|
+
options,
|
|
100
65
|
);
|
|
101
66
|
}
|
|
102
67
|
|
|
103
|
-
|
|
104
|
-
* Mechanical source enumeration into a frozen catalog for this turn.
|
|
105
|
-
* Bound summons: establish the per-ticket volume and load the issue face.
|
|
106
|
-
* First-summons (unbound): freeze session candidates + summons text so the LLM
|
|
107
|
-
* turn can assert ticketNumber; accept verifies and mints the volume (ADR 0075).
|
|
108
|
-
* True-unbound is decided by the LLM assertion (null/absent ticketNumber), not
|
|
109
|
-
* by skipping the freeze here.
|
|
110
|
-
*
|
|
111
|
-
* A run that has not settled yet (crash-resume, open-court continuation) keeps
|
|
112
|
-
* the catalog its candidateIndexes were minted against — re-enumerating there
|
|
113
|
-
* would rebind the role's indexes to bytes it never saw. Only a settled run
|
|
114
|
-
* re-enumerates on the next same-ticket summons (ADR 0075 增量).
|
|
115
|
-
*/
|
|
116
|
-
async function freezeDiaristSourceCatalog(
|
|
117
|
-
admitted: AdmittedDiaristInvocation,
|
|
118
|
-
env: Pick<DiaristRunEnv, "cwd" | "home">,
|
|
119
|
-
): Promise<string> {
|
|
120
|
-
const path = join(admitted.runDirectory, DIARIST_SOURCE_CATALOG_FILE);
|
|
121
|
-
if (existsSync(path)) {
|
|
122
|
-
const identity = await readRoleRunIdentity(admitted.runDirectory);
|
|
123
|
-
if (identity !== undefined && identity.state !== "terminal") return path;
|
|
124
|
-
}
|
|
125
|
-
const issueFace =
|
|
126
|
-
admitted.ticketNumber === undefined
|
|
127
|
-
? undefined
|
|
128
|
-
: await loadDiaristIssueFace({
|
|
129
|
-
ticketNumber: admitted.ticketNumber,
|
|
130
|
-
projectRoot: admitted.projectRoot,
|
|
131
|
-
home: env.home,
|
|
132
|
-
});
|
|
133
|
-
const catalog = await prepareDiaristSourceCatalog({
|
|
134
|
-
...(admitted.ticketNumber === undefined
|
|
135
|
-
? {}
|
|
136
|
-
: { ticketNumber: admitted.ticketNumber }),
|
|
137
|
-
instruction: admitted.instruction,
|
|
138
|
-
runDirectory: admitted.runDirectory,
|
|
139
|
-
projectRoot: admitted.projectRoot,
|
|
140
|
-
cwd: admitted.projectRoot,
|
|
141
|
-
home: env.home,
|
|
142
|
-
...(issueFace === undefined ? {} : { issueFace }),
|
|
143
|
-
sessionCwds: [admitted.projectRoot, env.cwd],
|
|
144
|
-
});
|
|
145
|
-
await writeFile(path, serializeDiaristSourceCatalog(catalog), "utf8");
|
|
146
|
-
return path;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function diaristAdapters(options?: {
|
|
150
|
-
beforeDispatch?: (
|
|
151
|
-
admitted: AdmittedDiaristInvocation,
|
|
152
|
-
) => void | Promise<void>;
|
|
153
|
-
}): PostAdmissionAdapters<AdmittedDiaristInvocation> {
|
|
68
|
+
function diaristAdapters(): PostAdmissionAdapters<AdmittedDiaristInvocation> {
|
|
154
69
|
return {
|
|
155
70
|
trySettle: (admitted, authority, scope) =>
|
|
156
71
|
trySettleDiaristTerminalResult(admitted, authority, scope),
|
|
157
72
|
shouldPresentSettled: () => true,
|
|
158
|
-
...(options?.beforeDispatch === undefined
|
|
159
|
-
? {}
|
|
160
|
-
: { beforeDispatch: options.beforeDispatch }),
|
|
161
73
|
};
|
|
162
74
|
}
|
|
163
75
|
|
|
@@ -182,11 +94,10 @@ export async function runPublicDiarist(
|
|
|
182
94
|
throw error;
|
|
183
95
|
}
|
|
184
96
|
|
|
185
|
-
// #637 / #771 / ADR 0075: ticket identity is the LLM's typed assertion
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
// until assert.
|
|
97
|
+
// #637 / #771 / ADR 0075 / #779: ticket identity is the LLM's typed assertion
|
|
98
|
+
// on this turn, OR a typed handoff key already held by the caller (countersign
|
|
99
|
+
// refresh). Code never pre-judges the summons text and never freezes a
|
|
100
|
+
// candidate catalog. First summons without handoff stays unbound until assert.
|
|
190
101
|
|
|
191
102
|
const projectRoot = parsed.project ?? env.cwd;
|
|
192
103
|
const handoffTicket = env.boundTicketNumber;
|
|
@@ -240,7 +151,7 @@ export async function runPublicDiarist(
|
|
|
240
151
|
|
|
241
152
|
await markRunAdmitted(admitted, env.principalAuthority);
|
|
242
153
|
|
|
243
|
-
// Typed handoff: bind before
|
|
154
|
+
// Typed handoff: bind before the turn so identity is on durable pages.
|
|
244
155
|
if (
|
|
245
156
|
typeof handoffTicket === "number" &&
|
|
246
157
|
Number.isSafeInteger(handoffTicket) &&
|
|
@@ -270,7 +181,6 @@ export async function runPublicDiarist(
|
|
|
270
181
|
),
|
|
271
182
|
},
|
|
272
183
|
};
|
|
273
|
-
// Mutable shell: ticket bind + catalog freeze re-project activation before executeTurn.
|
|
274
184
|
const turnRequest = buildDiaristTurnRequest(admitted, turnProjection);
|
|
275
185
|
|
|
276
186
|
return await runPostAdmissionOneShot({
|
|
@@ -278,15 +188,7 @@ export async function runPublicDiarist(
|
|
|
278
188
|
env,
|
|
279
189
|
io,
|
|
280
190
|
request: turnRequest,
|
|
281
|
-
adapters: diaristAdapters(
|
|
282
|
-
beforeDispatch: async (admittedSeat) => {
|
|
283
|
-
const sourcesPath = await freezeDiaristSourceCatalog(admittedSeat, env);
|
|
284
|
-
Object.assign(
|
|
285
|
-
turnRequest,
|
|
286
|
-
buildDiaristTurnRequest(admittedSeat, turnProjection, sourcesPath),
|
|
287
|
-
);
|
|
288
|
-
},
|
|
289
|
-
}),
|
|
191
|
+
adapters: diaristAdapters(),
|
|
290
192
|
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
291
193
|
}).then(async (result) => {
|
|
292
194
|
// Accept may have bound ticket onto durable pages after LLM assertion.
|
|
@@ -323,9 +225,7 @@ export async function runPublicDiarist(
|
|
|
323
225
|
|
|
324
226
|
/**
|
|
325
227
|
* Resume a previously admitted Diarist run (#708 / ADR 0079 同票传召 = resume).
|
|
326
|
-
*
|
|
327
|
-
* watermark keeps the pass incremental so already-seen blocks are not
|
|
328
|
-
* re-offered. An unfinished run keeps its frozen catalog (see freeze above).
|
|
228
|
+
* LLM re-finds materials; entry identity keeps the volume idempotent.
|
|
329
229
|
*/
|
|
330
230
|
export async function runPublicDiaristResume(
|
|
331
231
|
request: PublicResumeRequest,
|
|
@@ -347,7 +247,6 @@ export async function runPublicDiaristResume(
|
|
|
347
247
|
admitted.runDirectory,
|
|
348
248
|
effective.summons,
|
|
349
249
|
);
|
|
350
|
-
const sourcesPath = await freezeDiaristSourceCatalog(admitted, env);
|
|
351
250
|
return buildDiaristTurnRequest(
|
|
352
251
|
admitted,
|
|
353
252
|
resumeTurnRequestProjectionOptions(
|
|
@@ -356,7 +255,6 @@ export async function runPublicDiaristResume(
|
|
|
356
255
|
env,
|
|
357
256
|
summonsPrepared,
|
|
358
257
|
),
|
|
359
|
-
sourcesPath,
|
|
360
258
|
);
|
|
361
259
|
},
|
|
362
260
|
adapters: diaristAdapters(),
|
package/src/role-runtime.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { writeSync } from "node:fs";
|
|
1
|
+
import { readFileSync, writeSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
2
3
|
import {
|
|
3
4
|
ExplicitInternalActivationError,
|
|
4
5
|
type HostContext,
|
|
@@ -13,7 +14,10 @@ import { createSubmissionLedgerHost } from "./submission-ledger.ts";
|
|
|
13
14
|
import { createCollectorLedger } from "./collector-ledger.ts";
|
|
14
15
|
|
|
15
16
|
import { activationTraceRecordSchema, namedActivationCause, type ActivationTraceRecord, type ActivationTraceWriter } from "./activation-trace.ts";
|
|
16
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
homeFromRunDirectory,
|
|
19
|
+
resolveActivationLedgerHomeForPath,
|
|
20
|
+
} from "./activation-ledger-topology.ts";
|
|
17
21
|
import {
|
|
18
22
|
appendAcceptedActivationToBook,
|
|
19
23
|
buildAcceptedActivationFact,
|
|
@@ -76,16 +80,9 @@ import {
|
|
|
76
80
|
} from "./diarist-role.ts";
|
|
77
81
|
import {
|
|
78
82
|
DIARIST_ACCEPTED_TEXT,
|
|
79
|
-
|
|
80
|
-
projectDiaristSelections,
|
|
83
|
+
projectDiaristEntries,
|
|
81
84
|
} from "./diarist-contracts.ts";
|
|
82
|
-
import {
|
|
83
|
-
commitDiaristSelections,
|
|
84
|
-
DiaristTicketVerificationError,
|
|
85
|
-
loadDiaristSourceCatalog,
|
|
86
|
-
verifyAssertedTicketNumber,
|
|
87
|
-
type DiaristSourceCatalog,
|
|
88
|
-
} from "./diarist.ts";
|
|
85
|
+
import { commitDiaristEntries } from "./diarist.ts";
|
|
89
86
|
import { bindTicketNumberOnRunDirectory } from "./public-cli/invocation.ts";
|
|
90
87
|
import {
|
|
91
88
|
GATEKEEPER_TOOL_SPEC,
|
|
@@ -180,14 +177,6 @@ const GLEANER_LEFT_TRANSPORT_FLAGS = Object.freeze([
|
|
|
180
177
|
}),
|
|
181
178
|
] as const);
|
|
182
179
|
|
|
183
|
-
/** Diarist private transport: frozen source catalog path (ADR 0018 / #708). */
|
|
184
|
-
const DIARIST_TRANSPORT_FLAGS = Object.freeze([
|
|
185
|
-
Object.freeze({
|
|
186
|
-
name: DIARIST_SOURCES_FLAG.name,
|
|
187
|
-
definition: DIARIST_SOURCES_FLAG.definition,
|
|
188
|
-
}),
|
|
189
|
-
] as const);
|
|
190
|
-
|
|
191
180
|
/**
|
|
192
181
|
* Decode private transport flags into frozen admitted inputs.
|
|
193
182
|
* Envelope-owned; necessary JSON decode only (public --authority-ref owns grammar).
|
|
@@ -926,8 +915,9 @@ export function createAuditorRoleRuntime(
|
|
|
926
915
|
}
|
|
927
916
|
|
|
928
917
|
/**
|
|
929
|
-
* LLM typed court-target assertion from diarist output (ADR 0075).
|
|
918
|
+
* LLM typed court-target assertion from diarist output (ADR 0075 / #779).
|
|
930
919
|
* null/absent = true-unbound; positive safe integer = ticket N.
|
|
920
|
+
* Shape-only read of the typed field — not content judgment of free text.
|
|
931
921
|
* Any other shape fails honestly — never washes into unbound.
|
|
932
922
|
*/
|
|
933
923
|
function readDiaristTicketAssertion(
|
|
@@ -947,29 +937,56 @@ function readDiaristTicketAssertion(
|
|
|
947
937
|
return { kind: "ticket", ticketNumber: n };
|
|
948
938
|
}
|
|
949
939
|
}
|
|
950
|
-
throw new
|
|
951
|
-
"assertion-uninterpretable",
|
|
940
|
+
throw new Error(
|
|
952
941
|
"diarist ticketNumber must be a safe integer >= 1, null, or absent",
|
|
953
942
|
);
|
|
954
943
|
}
|
|
955
944
|
|
|
945
|
+
/** Run coordinates + optional pre-bound ticket from durable pages (#779). */
|
|
946
|
+
function readDiaristRunCoordinates(): {
|
|
947
|
+
readonly runDirectory: string;
|
|
948
|
+
readonly projectRoot: string;
|
|
949
|
+
readonly home: string;
|
|
950
|
+
readonly boundTicketNumber?: number;
|
|
951
|
+
} {
|
|
952
|
+
const runDirectory = process.env.AK_ROLE_RUN_DIR;
|
|
953
|
+
if (typeof runDirectory !== "string" || runDirectory.trim() === "") {
|
|
954
|
+
throw new Error("diarist accept requires AK_ROLE_RUN_DIR");
|
|
955
|
+
}
|
|
956
|
+
const admittedPath = join(runDirectory, "admitted-request.json");
|
|
957
|
+
const admitted = JSON.parse(readFileSync(admittedPath, "utf8")) as Record<
|
|
958
|
+
string,
|
|
959
|
+
unknown
|
|
960
|
+
>;
|
|
961
|
+
if (typeof admitted.projectRoot !== "string" || admitted.projectRoot.trim() === "") {
|
|
962
|
+
throw new Error(`diarist admitted-request missing projectRoot (${admittedPath})`);
|
|
963
|
+
}
|
|
964
|
+
const bound =
|
|
965
|
+
typeof admitted.ticketNumber === "number" &&
|
|
966
|
+
Number.isSafeInteger(admitted.ticketNumber) &&
|
|
967
|
+
admitted.ticketNumber >= 1
|
|
968
|
+
? admitted.ticketNumber
|
|
969
|
+
: undefined;
|
|
970
|
+
return {
|
|
971
|
+
runDirectory,
|
|
972
|
+
projectRoot: admitted.projectRoot,
|
|
973
|
+
home: homeFromRunDirectory(runDirectory),
|
|
974
|
+
...(bound === undefined ? {} : { boundTicketNumber: bound }),
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
|
|
956
978
|
/**
|
|
957
|
-
* #708: 起居郎 public seat on the shared filed-officer envelope.
|
|
958
|
-
* Semantic collection happened in this role's own turn
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
979
|
+
* #708 / #779: 起居郎 public seat on the shared filed-officer envelope.
|
|
980
|
+
* Semantic collection happened in this role's own turn (LLM finds materials).
|
|
981
|
+
* Accept hook: bind typed ticket assertion → idempotent sitian append of whole
|
|
982
|
+
* blocks the LLM submitted. No frozen catalog, no quote/ticket reverse-verify
|
|
983
|
+
* of LLM output. Machine facts never come from model self-report (锚定宪法).
|
|
962
984
|
*/
|
|
963
985
|
export function createDiaristRoleRuntime(
|
|
964
986
|
roleHost: RoleHost,
|
|
965
987
|
dependencies: DiaristRuntimeDependencies,
|
|
966
|
-
getSourcesFlag: () => unknown,
|
|
967
988
|
) {
|
|
968
|
-
|
|
969
|
-
// saw and the rows accept commits are the same bytes. Accept never re-reads
|
|
970
|
-
// the file, so anything written to it mid-turn cannot become a diary entry.
|
|
971
|
-
let catalog: DiaristSourceCatalog | undefined;
|
|
972
|
-
const runtime = createFiledOfficerRuntime(
|
|
989
|
+
return createFiledOfficerRuntime(
|
|
973
990
|
roleHost,
|
|
974
991
|
{
|
|
975
992
|
role: "diarist",
|
|
@@ -983,8 +1000,8 @@ export function createDiaristRoleRuntime(
|
|
|
983
1000
|
: undefined;
|
|
984
1001
|
|
|
985
1002
|
// LLM cannot identify the court target — escalate without machine facts.
|
|
986
|
-
// Strip sitian (锚定宪法) and ticketNumber (
|
|
987
|
-
//
|
|
1003
|
+
// Strip sitian (锚定宪法) and ticketNumber (must not leak into
|
|
1004
|
+
// caller-visible admitted typed key via decisiveFacts mirror).
|
|
988
1005
|
if (submitted?.status === "escalate") {
|
|
989
1006
|
if (!("sitian" in submitted) && !("ticketNumber" in submitted)) {
|
|
990
1007
|
return submitted;
|
|
@@ -996,40 +1013,20 @@ export function createDiaristRoleRuntime(
|
|
|
996
1013
|
}
|
|
997
1014
|
|
|
998
1015
|
const assertion = readDiaristTicketAssertion(submitted);
|
|
999
|
-
|
|
1000
|
-
// No frozen catalog: only true-unbound is lawful without volume work.
|
|
1001
|
-
// A self-reported `sitian` is not machine fact (锚定宪法).
|
|
1002
|
-
if (catalog === undefined) {
|
|
1003
|
-
if (assertion.kind === "ticket") {
|
|
1004
|
-
throw new DiaristTicketVerificationError(
|
|
1005
|
-
"assertion-uninterpretable",
|
|
1006
|
-
`diarist asserted ticket #${assertion.ticketNumber} but no source catalog was frozen for this turn`,
|
|
1007
|
-
);
|
|
1008
|
-
}
|
|
1009
|
-
if (submitted === undefined || !("sitian" in submitted)) return undefined;
|
|
1010
|
-
const stripped = { ...submitted };
|
|
1011
|
-
delete stripped.sitian;
|
|
1012
|
-
return stripped;
|
|
1013
|
-
}
|
|
1016
|
+
const coords = readDiaristRunCoordinates();
|
|
1014
1017
|
|
|
1015
1018
|
// Already bound before the turn (typed handoff / resume): skip re-recognition
|
|
1016
|
-
// (ADR 0075 已绑定 ticket 优先)
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
...(submitted ?? { receipt: parameters }),
|
|
1024
|
-
ticketNumber: catalog.ticketNumber,
|
|
1025
|
-
sitian: facts,
|
|
1026
|
-
};
|
|
1027
|
-
}
|
|
1019
|
+
// and commit under that identity (ADR 0075 已绑定 ticket 优先).
|
|
1020
|
+
const ticketNumber =
|
|
1021
|
+
coords.boundTicketNumber !== undefined
|
|
1022
|
+
? coords.boundTicketNumber
|
|
1023
|
+
: assertion.kind === "ticket"
|
|
1024
|
+
? assertion.ticketNumber
|
|
1025
|
+
: undefined;
|
|
1028
1026
|
|
|
1029
|
-
//
|
|
1030
|
-
//
|
|
1031
|
-
|
|
1032
|
-
if (assertion.kind === "true-unbound") {
|
|
1027
|
+
// true-unbound → 无录 (no volume). Cannot-identify must arrive as
|
|
1028
|
+
// status=escalate above — never as silent unbound.
|
|
1029
|
+
if (ticketNumber === undefined) {
|
|
1033
1030
|
if (submitted === undefined || !("sitian" in submitted)) {
|
|
1034
1031
|
return { ...(submitted ?? { receipt: parameters }), ticketNumber: null };
|
|
1035
1032
|
}
|
|
@@ -1041,50 +1038,25 @@ export function createDiaristRoleRuntime(
|
|
|
1041
1038
|
return stripped;
|
|
1042
1039
|
}
|
|
1043
1040
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
const boundCatalog: DiaristSourceCatalog = {
|
|
1054
|
-
...catalog,
|
|
1055
|
-
ticketNumber: assertion.ticketNumber,
|
|
1056
|
-
};
|
|
1057
|
-
const facts = await commitDiaristSelections({
|
|
1058
|
-
catalog: boundCatalog,
|
|
1059
|
-
selections: projectDiaristSelections(parameters),
|
|
1041
|
+
if (coords.boundTicketNumber === undefined) {
|
|
1042
|
+
await bindTicketNumberOnRunDirectory(coords.runDirectory, ticketNumber);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
const facts = await commitDiaristEntries({
|
|
1046
|
+
ticketNumber,
|
|
1047
|
+
cwd: coords.projectRoot,
|
|
1048
|
+
home: coords.home,
|
|
1049
|
+
entries: projectDiaristEntries(parameters),
|
|
1060
1050
|
});
|
|
1061
1051
|
return {
|
|
1062
1052
|
...(submitted ?? { receipt: parameters }),
|
|
1063
|
-
ticketNumber
|
|
1053
|
+
ticketNumber,
|
|
1064
1054
|
sitian: facts,
|
|
1065
1055
|
};
|
|
1066
1056
|
},
|
|
1067
1057
|
},
|
|
1068
1058
|
dependencies,
|
|
1069
1059
|
);
|
|
1070
|
-
return {
|
|
1071
|
-
async activate() {
|
|
1072
|
-
catalog = loadFrozenDiaristCatalog(getSourcesFlag);
|
|
1073
|
-
return runtime.activate();
|
|
1074
|
-
},
|
|
1075
|
-
};
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
/** Envelope-owned decode + one-shot load of the frozen catalog (ADR 0018). */
|
|
1079
|
-
function loadFrozenDiaristCatalog(
|
|
1080
|
-
getFlag: () => unknown,
|
|
1081
|
-
): DiaristSourceCatalog | undefined {
|
|
1082
|
-
const raw = getFlag();
|
|
1083
|
-
if (raw === undefined) return undefined;
|
|
1084
|
-
if (typeof raw !== "string" || raw.trim() === "") {
|
|
1085
|
-
throw new Error("Diarist source catalog flag must be a nonempty path");
|
|
1086
|
-
}
|
|
1087
|
-
return loadDiaristSourceCatalog(raw);
|
|
1088
1060
|
}
|
|
1089
1061
|
|
|
1090
1062
|
/** Countersign status words the queue reads (#753). */
|
|
@@ -1172,9 +1144,6 @@ export function createRoleRuntimeExtension(
|
|
|
1172
1144
|
for (const flag of GLEANER_LEFT_TRANSPORT_FLAGS) {
|
|
1173
1145
|
roleHost.registerFlag(flag.name, flag.definition);
|
|
1174
1146
|
}
|
|
1175
|
-
for (const flag of DIARIST_TRANSPORT_FLAGS) {
|
|
1176
|
-
roleHost.registerFlag(flag.name, flag.definition);
|
|
1177
|
-
}
|
|
1178
1147
|
|
|
1179
1148
|
let admitted = false;
|
|
1180
1149
|
let selectedRole: string | undefined;
|
|
@@ -1651,7 +1620,6 @@ export function createRoleRuntimeExtension(
|
|
|
1651
1620
|
return dependencies.loadDiaristSoul();
|
|
1652
1621
|
},
|
|
1653
1622
|
},
|
|
1654
|
-
() => envelopeHost.host.getFlag(DIARIST_SOURCES_FLAG.name),
|
|
1655
1623
|
);
|
|
1656
1624
|
let sessionMergerGitState = dependencies.mergerGitState;
|
|
1657
1625
|
const merger = createMergerRoleRuntime(roleHost, {
|
|
@@ -9,12 +9,6 @@ export const TICKET_PROVENANCE_KIND = "ticket-provenance" as const;
|
|
|
9
9
|
/** Human-read view filename co-located with the JSONL volume. */
|
|
10
10
|
export const TICKET_PROVENANCE_HUMAN_VIEW = "起居录.md" as const;
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Incremental watermark: identities already offered to the collector this ticket
|
|
14
|
-
* (selected or not). Process state next to the volume — not a diary dual-source.
|
|
15
|
-
*/
|
|
16
|
-
export const TICKET_PROVENANCE_OFFERED_WATERMARK = "offered-identities.jsonl" as const;
|
|
17
|
-
|
|
18
12
|
/** Payload discriminator: diagnostic residue (not a diary body entry). */
|
|
19
13
|
export const TICKET_PROVENANCE_RECORD_CLASS_DIAGNOSTIC = "diagnostic" as const;
|
|
20
14
|
|
|
@@ -27,17 +21,17 @@ export type TicketProvenanceSourceKind =
|
|
|
27
21
|
|
|
28
22
|
/**
|
|
29
23
|
* How a block entered the volume.
|
|
30
|
-
* - llm-semantic: LLM
|
|
31
|
-
* basis.anchors carry ticket #
|
|
24
|
+
* - llm-semantic: LLM selected and submitted the whole block (#779: no mechanical reverse-verify).
|
|
25
|
+
* basis.anchors may carry ticket # / human notes for audit only — not a gate.
|
|
32
26
|
*/
|
|
33
27
|
export type TicketProvenanceBasisMethod = "llm-semantic";
|
|
34
28
|
|
|
35
|
-
/** Basis for inclusion — LLM judgment
|
|
29
|
+
/** Basis for inclusion — LLM judgment; anchors/notes are audit-only. */
|
|
36
30
|
export type TicketProvenanceBasis = {
|
|
37
31
|
readonly method: TicketProvenanceBasisMethod;
|
|
38
|
-
/**
|
|
32
|
+
/** Audit notes (ticket #, human labels). Not a machine gate. */
|
|
39
33
|
readonly anchors?: readonly string[];
|
|
40
|
-
/** Free diagnostic note
|
|
34
|
+
/** Free diagnostic note. Not a machine gate. */
|
|
41
35
|
readonly note?: string;
|
|
42
36
|
};
|
|
43
37
|
|
|
@@ -66,7 +60,7 @@ export type TicketProvenanceEntry = {
|
|
|
66
60
|
* Separated by recordClass discriminator — never disguised as a source entry.
|
|
67
61
|
*/
|
|
68
62
|
export type TicketProvenanceDiagnosticKind =
|
|
69
|
-
/** Historical rows only —
|
|
63
|
+
/** Historical rows only — retained for reading older volumes (#779 deleted writers). */
|
|
70
64
|
| "collector-failed"
|
|
71
65
|
| "issue-source-failed"
|
|
72
66
|
| "quote-verify-failed";
|