@akagilnc/pi-workflow-roles 0.1.4321 → 0.1.4387

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.
Files changed (71) hide show
  1. package/README.md +4 -1
  2. package/README.zh-CN.md +5 -1
  3. package/dist/acp-host/production-host.js +1308 -587
  4. package/dist/doctor-contracts.js +2 -2
  5. package/dist/engine-detour-tool.js +2 -8
  6. package/dist/gatekeeper-role.js +4 -19
  7. package/dist/headless-host/production-host.js +2613 -1892
  8. package/dist/merger-contracts.js +2 -2
  9. package/dist/migrate-book-topology.js +34 -9
  10. package/dist/notary-contracts.js +2 -2
  11. package/dist/package-contracts/auditor-output.js +2 -2
  12. package/dist/package-contracts/fixer-output.js +2 -2
  13. package/dist/package-contracts/gatekeeper-output.js +2 -2
  14. package/dist/package-contracts/navigator-output.js +2 -2
  15. package/dist/package-contracts/terminating-infrastructure.js +7 -2
  16. package/dist/package-contracts/terminating-tools.js +6 -0
  17. package/dist/packaged-role-registry.js +20 -1
  18. package/dist/public-cli/case-dossier-delivery.js +45 -28
  19. package/dist/public-cli/countersign-run.js +439 -0
  20. package/dist/public-cli/invocation.js +10 -0
  21. package/dist/public-cli/main.js +388 -101
  22. package/dist/public-cli/option-definitions.js +14 -0
  23. package/dist/public-cli/post-admission.js +44 -49
  24. package/dist/public-cli/run-lifecycle.js +13 -1
  25. package/dist/public-cli/settlement.js +14 -53
  26. package/dist/public-role-summons.js +14 -0
  27. package/dist/secretariat-contracts.js +8 -0
  28. package/dist/sitian-appender.js +21 -4
  29. package/extensions/role-runtime.ts +1 -0
  30. package/package.json +1 -1
  31. package/scripts/build-package.mjs +1 -0
  32. package/souls/secretariat.md +26 -0
  33. package/souls/ticket-law.md +9 -0
  34. package/src/collector-tool-schemas.ts +2 -2
  35. package/src/countersign-role.ts +2 -2
  36. package/src/diarist-role.ts +2 -2
  37. package/src/doctor-contracts.ts +2 -2
  38. package/src/engine-detour-tool.ts +3 -9
  39. package/src/gatekeeper-pass-envelope.ts +35 -2
  40. package/src/gatekeeper-role.ts +8 -31
  41. package/src/gleaner-left-role.ts +2 -2
  42. package/src/host-contracts.ts +2 -1
  43. package/src/inspector-role.ts +2 -2
  44. package/src/judge-role.ts +2 -2
  45. package/src/merger-contracts.ts +2 -2
  46. package/src/notary-contracts.ts +2 -2
  47. package/src/package-contracts/auditor-output.ts +2 -2
  48. package/src/package-contracts/fixer-output.ts +2 -2
  49. package/src/package-contracts/gatekeeper-output.ts +2 -2
  50. package/src/package-contracts/navigator-output.ts +2 -2
  51. package/src/package-contracts/terminating-infrastructure.ts +13 -3
  52. package/src/package-contracts/terminating-tools.ts +9 -1
  53. package/src/packaged-role-registry.ts +20 -1
  54. package/src/public-cli/case-dossier-delivery.ts +48 -29
  55. package/src/public-cli/cli.ts +10 -0
  56. package/src/public-cli/countersign-run.ts +29 -5
  57. package/src/public-cli/invocation.ts +28 -2
  58. package/src/public-cli/option-definitions.ts +17 -0
  59. package/src/public-cli/post-admission.ts +45 -58
  60. package/src/public-cli/run-lifecycle.ts +29 -2
  61. package/src/public-cli/secretariat-run.ts +266 -0
  62. package/src/public-cli/settlement.ts +31 -55
  63. package/src/public-cli/terminal.ts +2 -1
  64. package/src/public-role-summons.ts +23 -1
  65. package/src/reviewer-role.ts +2 -2
  66. package/src/role-runtime-dependencies.ts +1 -0
  67. package/src/role-runtime.ts +203 -18
  68. package/src/secretariat-contracts.ts +27 -0
  69. package/src/secretariat-role.ts +210 -0
  70. package/src/sitian-appender.ts +36 -3
  71. package/src/worker-role.ts +2 -2
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
16
16
  import { bookDirectOfficerRunPointer } from "./archivist-record-entry.ts";
17
- import type { HostContext } from "./host-contracts.ts";
17
+ import type { HostContext, RoleTurnHost } from "./host-contracts.ts";
18
18
  import {
19
19
  GatekeeperDecisionError,
20
20
  OFFICER_CONCLUSION_REASK,
@@ -28,6 +28,34 @@ import {
28
28
  import type { PublicSummonResult } from "./public-role-summons.ts";
29
29
  import { sessionFileFromPublicSummon } from "./session-assistant-usage.ts";
30
30
 
31
+ /**
32
+ * Shared-envelope default officer summon (ADR 0018).
33
+ * Role modules must not own this drive seam — only project results.
34
+ */
35
+ export function createDefaultGateOfficerSummon(options: {
36
+ readonly cwd: string;
37
+ readonly home?: string;
38
+ readonly packageRoot?: string;
39
+ readonly roleTurnHost?: RoleTurnHost;
40
+ readonly createRunId?: () => string;
41
+ }): GateOfficerSummon {
42
+ return async (officer, sourceRunDirectory, signal, reask, submission) => {
43
+ const { summonGateOfficer } = await import("./public-role-summons.ts");
44
+ return summonGateOfficer({
45
+ officer,
46
+ sourceRunDirectory,
47
+ cwd: options.cwd,
48
+ ...(signal === undefined ? {} : { signal }),
49
+ ...(reask === undefined ? {} : { reask }),
50
+ ...(submission === undefined ? {} : { submission }),
51
+ ...(options.home === undefined ? {} : { home: options.home }),
52
+ ...(options.packageRoot === undefined ? {} : { packageRoot: options.packageRoot }),
53
+ ...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
54
+ ...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
55
+ });
56
+ };
57
+ }
58
+
31
59
  /**
32
60
  * Book a typed pointer under parent session/auditor-roles (archivist-owned write).
33
61
  * Offline mocks without a real session leave no nested volume (lawful zero).
@@ -84,12 +112,17 @@ export async function requireGatekeeperPass(options: {
84
112
  }): Promise<void> {
85
113
  let reask: string | undefined;
86
114
  // No round cap — end only on pass, bounce/escalate-to-parent, or real failure.
115
+ const summonOfficer =
116
+ options.summonOfficer ??
117
+ createDefaultGateOfficerSummon({
118
+ cwd: options.context.cwd ?? process.cwd(),
119
+ });
87
120
  for (;;) {
88
121
  const projected = await projectGatekeeperRun({
89
122
  context: options.context,
90
123
  subject: options.subject,
124
+ summonOfficer,
91
125
  ...(options.signal === undefined ? {} : { signal: options.signal }),
92
- ...(options.summonOfficer === undefined ? {} : { summonOfficer: options.summonOfficer }),
93
126
  ...(options.submission === undefined ? {} : { submission: options.submission }),
94
127
  ...(reask === undefined ? {} : { reask }),
95
128
  });
@@ -111,18 +111,11 @@ export type RunGatekeeperOptions = {
111
111
  */
112
112
  readonly submission?: unknown;
113
113
  /**
114
- * Test seam for public-role summons. Production calls the shared public
115
- * activation path (#675); inject only in offline tracers.
114
+ * Officer summon seam. Production default lives on the shared envelope
115
+ * (`createDefaultGateOfficerSummon` / requireGatekeeperPass — ADR 0018).
116
+ * Role module only projects; callers must supply the summon.
116
117
  */
117
- readonly summonOfficer?: GateOfficerSummon;
118
- /**
119
- * Offline test injects forwarded through the production default summonGateOfficer
120
- * path (same faces as public CLI). Production leaves these unset.
121
- */
122
- readonly home?: string;
123
- readonly packageRoot?: string;
124
- readonly roleTurnHost?: import("./host-contracts.ts").RoleTurnHost;
125
- readonly createRunId?: () => string;
118
+ readonly summonOfficer: GateOfficerSummon;
126
119
  };
127
120
 
128
121
  export type GatekeeperPassHostActions = {
@@ -323,8 +316,9 @@ export const OFFICER_CONCLUSION_REASK =
323
316
  "上次交卷的结论不是 pass、bounce、escalate 三态之一。请重新输出,结论字段写明其一;打回或上呈的话就是给对方看的原文。" as const;
324
317
 
325
318
  /**
326
- * Summon + project only. Lifecycle book and host abort face live on the shared
327
- * submit envelope (`gatekeeper-pass-envelope.ts`, ADR 0018 / #675).
319
+ * Summon (via injected seam) + project. Default summonGateOfficer drive lives
320
+ * on the shared envelope (`gatekeeper-pass-envelope.ts`, ADR 0018 / #675) —
321
+ * role module keeps projection only.
328
322
  */
329
323
  export async function projectGatekeeperRun(
330
324
  options: RunGatekeeperOptions,
@@ -345,24 +339,7 @@ export async function projectGatekeeperRun(
345
339
  // typed payload passed explicitly (never latest-toolCall recovery, A7.1–A7.3).
346
340
  let summoned: PublicSummonResult;
347
341
  try {
348
- const summon =
349
- options.summonOfficer
350
- ?? (async (nextOfficer, sourceRunDirectory, officerSignal, reask, nextSubmission) => {
351
- const { summonGateOfficer } = await import("./public-role-summons.ts");
352
- return summonGateOfficer({
353
- officer: nextOfficer,
354
- sourceRunDirectory,
355
- cwd: options.context.cwd ?? process.cwd(),
356
- ...(officerSignal === undefined ? {} : { signal: officerSignal }),
357
- ...(reask === undefined ? {} : { reask }),
358
- ...(nextSubmission === undefined ? {} : { submission: nextSubmission }),
359
- ...(options.home === undefined ? {} : { home: options.home }),
360
- ...(options.packageRoot === undefined ? {} : { packageRoot: options.packageRoot }),
361
- ...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
362
- ...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
363
- });
364
- });
365
- summoned = await summon(
342
+ summoned = await options.summonOfficer(
366
343
  officer,
367
344
  runDirectory,
368
345
  options.signal,
@@ -2,7 +2,7 @@ import type { Static } from "typebox";
2
2
  import { Type } from "typebox";
3
3
 
4
4
  import { openToolObject } from "./open-tool-schema.ts";
5
- import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
5
+ import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
6
6
  import {
7
7
  GLEANER_LEFT_OUTPUT_TOOL_NAME,
8
8
  validateRecordedGleanerLeftOutput,
@@ -19,7 +19,7 @@ export { validateRecordedGleanerLeftOutput };
19
19
  // #836 r16 class 1: pointer/statement are LLM/human-read narrative content — no
20
20
  // code branches on their presence.
21
21
  /** 左拾遗弹章交卷形状;形状指引,非 schema 闸。 */
22
- export const gleanerLeftOutputSchema = withInfrastructureFailureDeclaration(
22
+ export const gleanerLeftOutputSchema = withTerminatingOutputDeclarations(
23
23
  openToolObject(
24
24
  Type.Object({
25
25
  status: Type.Unknown({
@@ -134,7 +134,8 @@ export type RoleTurnActivation =
134
134
  | { readonly role: "gatekeeper" }
135
135
  | { readonly role: "navigator" }
136
136
  | { readonly role: "auditor" }
137
- | { readonly role: "diarist" };
137
+ | { readonly role: "diarist" }
138
+ | { readonly role: "secretariat" };
138
139
 
139
140
  export type RoleTurnContinuation =
140
141
  | { readonly kind: "initial"; readonly prompt: string }
@@ -2,7 +2,7 @@ import type { Static } from "typebox";
2
2
  import { Type } from "typebox";
3
3
 
4
4
  import { openToolObject } from "./open-tool-schema.ts";
5
- import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
5
+ import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
6
6
  import {
7
7
  INSPECTOR_ACCEPTED_TEXT,
8
8
  INSPECTOR_OUTPUT_TOOL_NAME,
@@ -19,7 +19,7 @@ export type { InspectorOutput };
19
19
  export { validateRecordedInspectorOutput };
20
20
 
21
21
  /** 台院事后察举交卷形状;形状指引,非 schema 闸。 */
22
- export const inspectorOutputSchema = withInfrastructureFailureDeclaration(
22
+ export const inspectorOutputSchema = withTerminatingOutputDeclarations(
23
23
  openToolObject(
24
24
  Type.Object({
25
25
  status: Type.Unknown({
package/src/judge-role.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { RoleHost, HostContext, HostToolResult, HostGatekeeperActions } from "./host-contracts.ts";
2
2
  import { Type, type Static } from "typebox";
3
3
 
4
- import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
4
+ import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
5
5
  import { ParentQueueReaskError } from "./submission-errors.ts";
6
6
 
7
7
  import {
@@ -25,7 +25,7 @@ export type { JudgeVerdict };
25
25
  // #836 (ADR 0003 Amendment): judgeStatus kept open like countersignStatus
26
26
  // (src/countersign-role.ts) — a closed domain here would reject an unknown
27
27
  // value before the rawStatus/ParentQueueReaskError check below ever runs.
28
- export const judgeVerdictSchema = withInfrastructureFailureDeclaration(
28
+ export const judgeVerdictSchema = withTerminatingOutputDeclarations(
29
29
  Type.Object(
30
30
  {
31
31
  judgeStatus: Type.Unknown({ description: "converged | continue | escalate — 形状指引,非 schema 闸" }),
@@ -1,7 +1,7 @@
1
1
  import { Type, type Static } from "typebox";
2
2
  import { exactUtf8 } from "./exact-utf8.ts";
3
3
  import { openToolObjectFromUnion } from "./open-tool-schema.ts";
4
- import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
4
+ import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
5
5
 
6
6
  const materialSchema = Type.Object({ bytesBase64: Type.String(), sha256: Type.String() }, { additionalProperties: false });
7
7
  const checkSchema = Type.Object({ name: Type.String({ minLength: 1 }), argv: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }) }, { additionalProperties: false });
@@ -25,7 +25,7 @@ const mergerOutputVariants = Type.Union([
25
25
  Type.Object({ status: Type.Unknown({ description: MERGER_STATUS_DESCRIPTION }), attemptId: Type.String({ description: "已受理合并 attempt 身份" }), report: Type.String({ description: "如实结果报告" }), mergeCommitId: Type.String({ description: "完成合并 commit object ID" }) }, { additionalProperties: false }),
26
26
  Type.Object({ status: Type.Unknown({ description: MERGER_STATUS_DESCRIPTION }), attemptId: Type.String({ description: "已受理合并 attempt 身份" }), diagnosis: Type.String({ description: "合并无法或不应由本席完成的原因(含无进行中合并、无活可干、需新的产品/权力决定)" }), report: Type.String({ description: "如实结果报告" }) }, { additionalProperties: false }),
27
27
  ]);
28
- export const mergerOutputSchema = withInfrastructureFailureDeclaration(openToolObjectFromUnion(mergerOutputVariants));
28
+ export const mergerOutputSchema = withTerminatingOutputDeclarations(openToolObjectFromUnion(mergerOutputVariants));
29
29
 
30
30
  export type DeepReadonly<T> = T extends (...args: never[]) => unknown ? T : T extends readonly (infer U)[] ? readonly DeepReadonly<U>[] : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T;
31
31
  export type MergerMaterial = DeepReadonly<Static<typeof materialSchema>>;
@@ -6,7 +6,7 @@
6
6
  import { Type } from "typebox";
7
7
 
8
8
  import { openToolObject } from "./open-tool-schema.ts";
9
- import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
9
+ import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
10
10
 
11
11
  export const NOTARY_OUTPUT_TOOL_NAME = "ak_notary_output";
12
12
  export const NOTARY_ACCEPTED_TEXT = "符宝郎回执已接受";
@@ -31,7 +31,7 @@ export const NOTARY_TICKET_FLAG = {
31
31
  export const NOTARY_FIXED_KICKOFF =
32
32
  "符宝郎案卷已受理;来源 run 定位见会话材料。";
33
33
 
34
- export const notaryOutputSchema = withInfrastructureFailureDeclaration(
34
+ export const notaryOutputSchema = withTerminatingOutputDeclarations(
35
35
  openToolObject(
36
36
  Type.Object({
37
37
  status: Type.Unknown({
@@ -5,12 +5,12 @@
5
5
  import { Type } from "typebox";
6
6
 
7
7
  import { openToolObject } from "../open-tool-schema.ts";
8
- import { withInfrastructureFailureDeclaration } from "./terminating-infrastructure.ts";
8
+ import { withTerminatingOutputDeclarations } from "./terminating-infrastructure.ts";
9
9
 
10
10
  export const AUDITOR_OUTPUT_TOOL_NAME = "ak_auditor_output" as const;
11
11
  export const AUDITOR_ACCEPTED_TEXT = "审刑院回执已接受";
12
12
 
13
- export const auditorOutputSchema = withInfrastructureFailureDeclaration(
13
+ export const auditorOutputSchema = withTerminatingOutputDeclarations(
14
14
  openToolObject(
15
15
  Type.Object({
16
16
  status: Type.Unknown({
@@ -1,6 +1,6 @@
1
1
  import { Type, type Static } from "typebox";
2
2
  import { openToolObjectFromUnion } from "../open-tool-schema.ts";
3
- import { withInfrastructureFailureDeclaration } from "./terminating-infrastructure.ts";
3
+ import { withTerminatingOutputDeclarations } from "./terminating-infrastructure.ts";
4
4
 
5
5
  export const FIXER_OUTPUT_TOOL_NAME = "ak_fixer_output";
6
6
  export const FIXER_ACCEPTED_TEXT = "修内司回执已接受";
@@ -51,7 +51,7 @@ const fixerOutputVariants = Type.Union([
51
51
  Type.Object({ status: Type.Unknown({ description: FIXER_STATUS_DESCRIPTION }), report: Type.String({ description: "如实结果报告" }), classResults: Type.Array(classResultSchema, { description: "各类拒绝结算" }) }),
52
52
  Type.Object({ status: Type.Unknown({ description: FIXER_STATUS_DESCRIPTION }), report: Type.String({ description: "如实结果报告" }), classResults: Type.Array(classResultSchema, { description: "各类完成或拒绝结算" }), testEvidence: Type.Optional(testEvidenceSchema) }),
53
53
  ]);
54
- export const fixerOutputSchema = withInfrastructureFailureDeclaration(
54
+ export const fixerOutputSchema = withTerminatingOutputDeclarations(
55
55
  openToolObjectFromUnion(fixerOutputVariants),
56
56
  );
57
57
 
@@ -6,7 +6,7 @@
6
6
  import { Type } from "typebox";
7
7
 
8
8
  import { openToolObject } from "../open-tool-schema.ts";
9
- import { withInfrastructureFailureDeclaration } from "./terminating-infrastructure.ts";
9
+ import { withTerminatingOutputDeclarations } from "./terminating-infrastructure.ts";
10
10
 
11
11
  export const GATEKEEPER_OUTPUT_TOOL_NAME = "ak_gatekeeper_output";
12
12
  export const GATEKEEPER_ACCEPTED_TEXT = "门下省决议已受理";
@@ -26,7 +26,7 @@ export const gatekeeperDecisionSchema = openToolObject(
26
26
  }),
27
27
  );
28
28
 
29
- export const gatekeeperOutputSchema = withInfrastructureFailureDeclaration(
29
+ export const gatekeeperOutputSchema = withTerminatingOutputDeclarations(
30
30
  gatekeeperDecisionSchema,
31
31
  );
32
32
 
@@ -7,12 +7,12 @@
7
7
  import { Type } from "typebox";
8
8
 
9
9
  import { openToolObject } from "../open-tool-schema.ts";
10
- import { withInfrastructureFailureDeclaration } from "./terminating-infrastructure.ts";
10
+ import { withTerminatingOutputDeclarations } from "./terminating-infrastructure.ts";
11
11
 
12
12
  export const NAVIGATOR_OUTPUT_TOOL_NAME = "ak_navigator_output";
13
13
  export const NAVIGATOR_ACCEPTED_TEXT = "游奕使建议已受理";
14
14
 
15
- export const navigatorOutputSchema = withInfrastructureFailureDeclaration(
15
+ export const navigatorOutputSchema = withTerminatingOutputDeclarations(
16
16
  openToolObject(
17
17
  Type.Object({
18
18
  status: Type.Unknown({
@@ -49,19 +49,29 @@ const infrastructureFailureDeclarationSchema = Type.Object(
49
49
  );
50
50
 
51
51
  /**
52
- * Compose the shared infrastructure-failure declaration into an open output
53
- * tool-object schema. Returns an open object (additionalProperties: true,
52
+ * Compose declarations shared by terminating output tools: infrastructure failure
53
+ * and the optional role-asserted ticket identity. Returns an open object (additionalProperties: true,
54
54
  * required: []) with the base schema's properties plus the shared declaration.
55
55
  * Static typing is preserved on the base (`as S`), so existing
56
56
  * `Static<typeof ...>` derived parameter types are unchanged.
57
57
  */
58
- export function withInfrastructureFailureDeclaration<
58
+ export function withTerminatingOutputDeclarations<
59
59
  S extends TSchema & { properties?: Record<string, TSchema> },
60
60
  >(schema: S): S {
61
61
  const baseProperties = (schema as { properties?: Record<string, TSchema> })
62
62
  .properties;
63
63
  const properties: Record<string, TSchema> = {
64
64
  ...(baseProperties ?? {}),
65
+ ...(
66
+ baseProperties?.ticketNumber === undefined
67
+ ? {
68
+ ticketNumber: Type.Unknown({
69
+ description:
70
+ "可选本票号:尚未绑定时由角色在既有回执中申报;机械层只记录合法正整数,不从散文推导、不验真。缺失或错形不拒收。",
71
+ }),
72
+ }
73
+ : {}
74
+ ),
65
75
  [INFRASTRUCTURE_FAILURE_DECLARATION_KEY]:
66
76
  infrastructureFailureDeclarationSchema.properties[
67
77
  INFRASTRUCTURE_FAILURE_DECLARATION_KEY
@@ -32,6 +32,7 @@ import { COUNTERSIGN_ACCEPTED_TEXT, COUNTERSIGN_OUTPUT_TOOL_NAME, validateRecord
32
32
  import { GLEANER_LEFT_ACCEPTED_TEXT, GLEANER_LEFT_OUTPUT_TOOL_NAME, validateRecordedGleanerLeftOutput, type GleanerLeftOutput } from "../gleaner-left-contracts.ts";
33
33
  import { INSPECTOR_ACCEPTED_TEXT, INSPECTOR_OUTPUT_TOOL_NAME, validateRecordedInspectorOutput, type InspectorOutput } from "../inspector-contracts.ts";
34
34
  import { DIARIST_ACCEPTED_TEXT, DIARIST_OUTPUT_TOOL_NAME, validateRecordedDiaristOutput, type DiaristOutput } from "../diarist-contracts.ts";
35
+ import { SECRETARIAT_ACCEPTED_TEXT, SECRETARIAT_OUTPUT_TOOL_NAME, type SecretariatVerdict } from "../secretariat-contracts.ts";
35
36
  import {
36
37
  CODER_ACCEPTED_TEXT,
37
38
  CODER_OUTPUT_TOOL_NAME,
@@ -86,6 +87,7 @@ export type {
86
87
  NavigatorAdvice,
87
88
  AuditorOutput,
88
89
  DiaristOutput,
90
+ SecretariatVerdict,
89
91
  };
90
92
 
91
93
  export const TERMINATING_TOOL_NAMES = [
@@ -104,6 +106,7 @@ export const TERMINATING_TOOL_NAMES = [
104
106
  NAVIGATOR_OUTPUT_TOOL_NAME,
105
107
  AUDITOR_OUTPUT_TOOL_NAME,
106
108
  DIARIST_OUTPUT_TOOL_NAME,
109
+ SECRETARIAT_OUTPUT_TOOL_NAME,
107
110
  ] as const;
108
111
 
109
112
  export type TerminatingToolName = (typeof TERMINATING_TOOL_NAMES)[number];
@@ -122,7 +125,8 @@ export type AcceptedDetails =
122
125
  | GatekeeperDirectOutput
123
126
  | NavigatorAdvice
124
127
  | AuditorOutput
125
- | DiaristOutput;
128
+ | DiaristOutput
129
+ | SecretariatVerdict;
126
130
 
127
131
  export function isTerminatingToolName(
128
132
  name: string,
@@ -162,6 +166,8 @@ export function acceptedTextFor(toolName: TerminatingToolName): string {
162
166
  return AUDITOR_ACCEPTED_TEXT;
163
167
  case DIARIST_OUTPUT_TOOL_NAME:
164
168
  return DIARIST_ACCEPTED_TEXT;
169
+ case SECRETARIAT_OUTPUT_TOOL_NAME:
170
+ return SECRETARIAT_ACCEPTED_TEXT;
165
171
  }
166
172
  }
167
173
 
@@ -214,6 +220,8 @@ export function acceptedFacts(toolName: TerminatingToolName, details: AcceptedDe
214
220
  return typeof record.judgeStatus === "string" ? { status: record.judgeStatus } : {};
215
221
  case COUNTERSIGN_OUTPUT_TOOL_NAME:
216
222
  return typeof record.countersignStatus === "string" ? { status: record.countersignStatus } : {};
223
+ case SECRETARIAT_OUTPUT_TOOL_NAME:
224
+ return typeof record.secretariatStatus === "string" ? { status: record.secretariatStatus } : {};
217
225
  case MERGER_OUTPUT_TOOL_NAME: {
218
226
  const status = typeof record.status === "string" ? record.status : undefined;
219
227
  return {
@@ -13,12 +13,14 @@ 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
15
  import { DIARIST_OUTPUT_TOOL_NAME } from "./diarist-contracts.ts";
16
+ import { SECRETARIAT_OUTPUT_TOOL_NAME } from "./secretariat-contracts.ts";
16
17
 
17
18
  /** Shared by public notary and gatekeeper-province notary. */
18
19
  export const NOTARY_SESSION_MATERIALS = [
19
20
  "CLAUDE.md",
20
21
  "souls/notary.md",
21
22
  "souls/audit-law.md",
23
+ "souls/ticket-law.md",
22
24
  "souls/gate-output-guide.md",
23
25
  ] as const;
24
26
 
@@ -154,7 +156,24 @@ export const PUBLIC_ROLE_RECORDS = [
154
156
  inputFlag: undefined,
155
157
  phaseFlag: undefined,
156
158
  activationStage: "load-and-install",
157
- sessionMaterials: ["CLAUDE.md", "souls/countersign.md"],
159
+ // #924: 公用《票面法》三席同装
160
+ sessionMaterials: ["CLAUDE.md", "souls/countersign.md", "souls/ticket-law.md"],
161
+ },
162
+ {
163
+ role: "secretariat",
164
+ phases: [null],
165
+ outputTool: SECRETARIAT_OUTPUT_TOOL_NAME,
166
+ inputFlag: undefined,
167
+ phaseFlag: undefined,
168
+ activationStage: "load-and-install",
169
+ // #924: owner-finalized Soul + 票面法 + 给事中/符宝郎行为指南
170
+ sessionMaterials: [
171
+ "CLAUDE.md",
172
+ "souls/secretariat.md",
173
+ "souls/ticket-law.md",
174
+ "souls/countersign.md",
175
+ "souls/notary.md",
176
+ ],
158
177
  },
159
178
  {
160
179
  role: "gleaner-left",
@@ -1,52 +1,66 @@
1
1
  /**
2
- * 系统随案递送本票起居录的中立指针段(ADR 0081:公共入口直接挂案卷,Soul 只说明用途不负责派送;
3
- * #709 公共入口通用递送与 #742 给事中受理链路同源;
4
- * 指针输入沿 ADR 0079(指针是绑定材料由代码精准递送),不把卷宗正文塞进提示词)。
5
- * 只读已有案卷:不刷新、不生成、不校验内容、不新增拒收或停工条件。
2
+ * 系统随案递送起居录路径的中立指针段(ADR 0081:公共入口直接挂案卷,Soul 只说明用途不负责派送;
3
+ * #709 公共入口通用递送与 #742 给事中受理链路同源;#858 告诉 LLM 路径,不另建取号/lookup)。
4
+ * 指针输入沿 ADR 0079(指针是绑定材料由代码精准递送),不把卷宗正文塞进提示词。
5
+ * 只告诉路径:不刷新、不生成、不校验内容、不新增拒收或停工条件、不从 instruction 抽票号。
6
6
  * 机器文本仅中立标识材料(ADR 0073),用途说明归角色材料所有。
7
7
  *
8
8
  * 递送挂载点唯一:`post-admission` 在 beforeDispatch 之后为每个公共入口挂载。
9
- * 普通入口把本段追加进 continuation;station-child 审核轮次走 attachments
10
- * 冻结 + role-runtime `loadCaseDossierReadingMaterial` → readingMaterial →
11
- * systemPrompt.materials fold(#879:对话 instruction 保持父腿 payload 原文;
12
- * 起居录作独立附件面,不新造 RoleTurnRequest.materials)。
9
+ * 全部公共入口(ordinary 已绑定/未绑定与 station-child)统一走 attachments 冻结 +
10
+ * role-runtime `loadCaseDossierReadingMaterial` → readingMaterial →
11
+ * systemPrompt.materials fold(#879 / #858:对话 instruction、空请求、resume
12
+ * message 保持调用者原文;起居录作独立附件面,不新造 RoleTurnRequest.materials,
13
+ * 不拼进 user-dialogue continuation)。
14
+ *
15
+ * #858: deliver returns typed FrozenAttachment[]; load currently re-derives the
16
+ * freeze leaf under runDirectory because agent-start only has HostContext.runDirectory.
17
+ * Consuming the typed frozenPath across that process boundary needs a design
18
+ * revision of the existing envelope/persistence seam — not a local replica of
19
+ * freezeAttachments' `00-` naming, not a drift throw, not readdir guessing.
13
20
  */
14
21
  import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
15
22
  import { tmpdir } from "node:os";
16
23
  import { join } from "node:path";
17
24
 
25
+ import { projectTicketRecordsPathShape } from "../sitian-facade.ts";
18
26
  import { resolveTicketProvenanceVolume } from "../ticket-provenance.ts";
19
27
  import {
20
28
  freezeAttachmentsIntoRun,
21
29
  type FrozenAttachment,
22
30
  } from "./invocation.ts";
23
31
 
24
- /** Section heading of the system-delivered dossier pointer (presentation only). */
32
+ /** Bound-ticket section heading (presentation only). */
25
33
  const CASE_DOSSIER_SECTION_HEADING = "## 本票起居录(系统随案提供)" as const;
26
34
 
35
+ /** Unbound path-only heading — no 本票 claim when no typed ticket is on the run. */
36
+ const UNBOUND_DOSSIER_PATH_HEADING = "## 起居录路径(系统随案提供)" as const;
37
+
27
38
  /** Stable freeze key under run/attachments/ for station-child 0081 delivery. */
28
39
  const CASE_DOSSIER_ATTACH_KEY = "case-dossier" as const;
29
40
 
30
41
  /** Leaf name of the frozen pointer section (content = purpose + paths). */
31
42
  const CASE_DOSSIER_ATTACH_FILE = "case-dossier-pointer.md" as const;
32
43
 
33
- /** Pointer only — presence/absence is for the role to observe at the path. */
34
- function describeDossierFile(path: string): string {
35
- return path;
36
- }
37
-
38
44
  /**
39
- * Pointer section for a bound ticket's existing 起居录, or undefined when the
40
- * run carries no ticket identity (unbound calls stay legal and get no dossier).
41
- * A bound ticket whose volume is missing or unreadable is stated as such —
42
- * the section never claims a dossier that is not there.
45
+ * Neutral 起居录 path pointer for every public entry (ADR 0081 delivery seam).
46
+ * Always tells the canonical path shape so the seat LLM can read the diary
47
+ * itself (陛下 2026-09-15: 只需要告诉llm这个路径). When a typed ticket identity is
48
+ * already on the run, also project the concrete resolved file. Never claims a
49
+ * volume or「本票」exists when unbound; never extracts a ticket from instruction.
50
+ * Under-book shape comes only from sitian writer joins — no local replica.
43
51
  */
44
52
  export async function projectCaseDossierPointerSection(input: {
45
53
  readonly ticketNumber: number | undefined;
46
54
  readonly projectRoot: string;
47
55
  readonly home: string;
48
- }): Promise<string | undefined> {
49
- if (input.ticketNumber === undefined) return undefined;
56
+ }): Promise<string> {
57
+ if (input.ticketNumber === undefined) {
58
+ return [
59
+ UNBOUND_DOSSIER_PATH_HEADING,
60
+ "",
61
+ `记录卷宗:${projectTicketRecordsPathShape()}`,
62
+ ].join("\n");
63
+ }
50
64
  const volume = resolveTicketProvenanceVolume(
51
65
  input.ticketNumber,
52
66
  input.projectRoot,
@@ -56,30 +70,30 @@ export async function projectCaseDossierPointerSection(input: {
56
70
  CASE_DOSSIER_SECTION_HEADING,
57
71
  "",
58
72
  `票号:#${input.ticketNumber}`,
59
- `记录卷宗:${describeDossierFile(volume.recordFile)}`,
73
+ `记录卷宗:${volume.recordFile}`,
60
74
  ].join("\n");
61
75
  }
62
76
 
63
77
  /**
64
- * ADR 0081 delivery for station-child officer turns (#879): freeze the same
65
- * pointer section through the existing attachments seam. Role-runtime loads
66
- * that freeze via loadCaseDossierReadingMaterial onto readingMaterial; the
67
- * envelope then folds it into systemPrompt.materials. Peer dialogue instruction
68
- * stays the parent payload; never RoleTurnRequest.materials.
69
- * Returns frozen attachments, or undefined when unbound (no dossier).
78
+ * ADR 0081 delivery for every public entry (#879 / #858): freeze the pointer
79
+ * section through the existing attachments seam. Role-runtime loads that freeze
80
+ * via loadCaseDossierReadingMaterial onto readingMaterial; the envelope then
81
+ * folds it into systemPrompt.materials. Caller / peer dialogue instruction stays
82
+ * opaque; never RoleTurnRequest.materials, never user-dialogue continuation.
83
+ * Always freezes the path pointer (canonical shape, or concrete file when a
84
+ * typed ticket is already bound).
70
85
  */
71
86
  export async function deliverCaseDossierAsAttachment(input: {
72
87
  readonly ticketNumber: number | undefined;
73
88
  readonly projectRoot: string;
74
89
  readonly home: string;
75
90
  readonly runDirectory: string;
76
- }): Promise<readonly FrozenAttachment[] | undefined> {
91
+ }): Promise<readonly FrozenAttachment[]> {
77
92
  const section = await projectCaseDossierPointerSection({
78
93
  ticketNumber: input.ticketNumber,
79
94
  projectRoot: input.projectRoot,
80
95
  home: input.home,
81
96
  });
82
- if (section === undefined) return undefined;
83
97
  // Stage in OS temp only — never leave a run-local .case-dossier-stage copy.
84
98
  const stagingDir = await mkdtemp(join(tmpdir(), "ak-case-dossier-"));
85
99
  try {
@@ -106,6 +120,11 @@ export type CaseDossierReadingMaterial = {
106
120
  * Load a previously frozen case-dossier attachment as reading material
107
121
  * (existing agent-start / systemPrompt.materials fold — not dialogue prompt,
108
122
  * not RoleTurnRequest.materials). Undefined when the run has no such freeze.
123
+ *
124
+ * #858: this join re-derives freezeAttachments' leaf naming because the
125
+ * agent-start handler only receives runDirectory. Replacing it requires
126
+ * threading deliver's typed frozenPath across the existing envelope seam
127
+ * (design revision) — do not copy freeze's `00-` algorithm here again.
109
128
  */
110
129
  export async function loadCaseDossierReadingMaterial(
111
130
  runDirectory: string,
@@ -64,6 +64,7 @@ import {
64
64
  parseCollectorArgv,
65
65
  parseCountersignArgv,
66
66
  parseDiaristArgv,
67
+ parseSecretariatArgv,
67
68
  parseGleanerLeftArgv,
68
69
  parseDoctorArgv,
69
70
  parseFixerArgv,
@@ -91,6 +92,7 @@ import { runPublicInstructionSeat, runPublicInstructionSeatResume } from "./inst
91
92
  import { runPublicCollector, runPublicCollectorResume } from "./collector-run.ts";
92
93
  import { runPublicCountersign, runPublicCountersignResume } from "./countersign-run.ts";
93
94
  import { runPublicDiarist, runPublicDiaristResume } from "./diarist-run.ts";
95
+ import { runPublicSecretariat, runPublicSecretariatResume } from "./secretariat-run.ts";
94
96
  import { runPublicGleanerLeft, runPublicGleanerLeftResume } from "./gleaner-left-run.ts";
95
97
  import { runPublicDoctor, runPublicDoctorResume } from "./doctor-run.ts";
96
98
  import { runPublicFixer, runPublicFixerResume } from "./fixer-run.ts";
@@ -141,6 +143,7 @@ const RESUME_SEAT_DISPATCH: Record<
141
143
  navigator: { seat: "navigator", run: runPublicInstructionSeatResume },
142
144
  auditor: { seat: "auditor", run: runPublicInstructionSeatResume },
143
145
  diarist: { seat: "diarist", run: runPublicDiaristResume },
146
+ secretariat: { seat: "secretariat", run: runPublicSecretariatResume },
144
147
  };
145
148
  import {
146
149
  INTERNAL_ROLE_ENTRYPOINT_RELATIVE,
@@ -186,6 +189,7 @@ export const PUBLIC_ROLE_ARGV = {
186
189
  navigator: { parse: parseNavigatorArgv, options: optionsForOwner("navigator") },
187
190
  auditor: { parse: parseAuditorArgv, options: optionsForOwner("auditor") },
188
191
  diarist: { parse: parseDiaristArgv, options: optionsForOwner("diarist") },
192
+ secretariat: { parse: parseSecretariatArgv, options: optionsForOwner("secretariat") },
189
193
  /** Deterministic analysis seat (#336) — argv parse only; no LLM admission. */
190
194
  analyst: { parse: parseAnalystArgv, options: optionsForOwner("analyst") },
191
195
  } as const;
@@ -1349,6 +1353,12 @@ export async function runAkRole(
1349
1353
  (args, roleEnv, once) => runPublicDiarist(args, roleEnv, io, once),
1350
1354
  );
1351
1355
  }
1356
+ if (parsed.command === "secretariat") {
1357
+ return await dispatchPublicRoleCommand(
1358
+ env, home, io, parsed, "secretariat", PUBLIC_ROLE_ARGV.secretariat.parse,
1359
+ (args, roleEnv, once) => runPublicSecretariat(args, roleEnv, io, once),
1360
+ );
1361
+ }
1352
1362
  if (parsed.command === "coder") {
1353
1363
  return await dispatchPublicRoleCommand(
1354
1364
  env, home, io, parsed, "coder", PUBLIC_ROLE_ARGV.coder.parse,