@akagilnc/pi-workflow-roles 0.1.3706 → 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.
@@ -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 selections. Machine facts never come from model self-report.
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`: 起居郎 is a seat like any other. The
218
- // frozen source catalog rides the shared input-flag seam; it is absent for a
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: DIARIST_SOURCES_FLAG.name,
220
+ inputFlag: undefined,
225
221
  phaseFlag: undefined,
226
222
  activationStage: "load-and-install",
227
223
  sessionMaterials: [
@@ -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
- * freezes under that key so issue face enters the catalog — never mechanical
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 freeze loads issue face.
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; this seat only freezes the
5
- * mechanical source catalog the turn selects from (`diarist-collector-is-own-turn`).
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-before-freeze so
61
- * issue face enters the catalog (ADR 0075 / 0081).
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
- activation: {
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 on this
186
- // turn (mechanical verify on accept), OR a typed handoff key already held by
187
- // the caller (countersign refresh). Code never pre-judges the summons text
188
- // against book-known numbers. First summons without handoff stays unbound
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 freeze so issue face enters the catalog.
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
- * A re-entry into a settled run re-enumerates fresh sources; the offered
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(),
@@ -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 { resolveActivationLedgerHomeForPath } from "./activation-ledger-topology.ts";
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
- DIARIST_SOURCES_FLAG,
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 DiaristTicketVerificationError(
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; the accept hook runs the
959
- * mechanical band (ticket verify when first-summons + verbatim reverse-verify →
960
- * idempotent sitian append → watermark) and records the resulting sitian facts
961
- * next to the receipt. Machine facts never come from model self-report (锚定宪法).
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
- // This turn's catalog, snapshotted at activate: the candidateIndexes the role
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 (unverified — must not leak
987
- // into caller-visible admitted typed key via decisiveFacts mirror).
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 优先) and commit under that identity.
1017
- if (catalog.ticketNumber !== undefined) {
1018
- const facts = await commitDiaristSelections({
1019
- catalog,
1020
- selections: projectDiaristSelections(parameters),
1021
- });
1022
- return {
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
- // First summons: LLM typed assertion → mechanical verify → bind → commit.
1030
- // true-unbound → 无录 (no volume). Verification failure throws (失败诚实).
1031
- // Cannot-identify must arrive as status=escalate above — never as silent unbound.
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
- await verifyAssertedTicketNumber({
1045
- ticketNumber: assertion.ticketNumber,
1046
- instruction: catalog.instruction,
1047
- projectRoot: catalog.projectRoot,
1048
- });
1049
- await bindTicketNumberOnRunDirectory(
1050
- catalog.runDirectory,
1051
- assertion.ticketNumber,
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: assertion.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 collector selected the block (after mechanical reverse-verify).
31
- * basis.anchors carry ticket # and mechanical/claimed quote notes for audit only.
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 plus mechanical anchor notes for audit. */
29
+ /** Basis for inclusion — LLM judgment; anchors/notes are audit-only. */
36
30
  export type TicketProvenanceBasis = {
37
31
  readonly method: TicketProvenanceBasisMethod;
38
- /** Mechanical anchors (ticket #, quotes, keywords) used for candidate/verify reference only. */
32
+ /** Audit notes (ticket #, human labels). Not a machine gate. */
39
33
  readonly anchors?: readonly string[];
40
- /** Free diagnostic note (failure cause, filter reason). Not a machine gate. */
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 — the diarist turn's own failure settles the run (#708). */
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";