@akagilnc/pi-workflow-roles 0.1.5068 → 0.1.5096

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 (80) hide show
  1. package/dist/acp-host/production-host.js +429 -383
  2. package/dist/atomic-write.js +13 -19
  3. package/dist/auditor-soul.js +2 -1
  4. package/dist/compliance-transport.js +3 -2
  5. package/dist/countersign-contracts.js +2 -2
  6. package/dist/doctor-contracts.js +1 -1
  7. package/dist/gatekeeper-role.js +14 -35
  8. package/dist/headless-host/production-host.js +461 -415
  9. package/dist/inspector-contracts.js +5 -2
  10. package/dist/merger-contracts.js +1 -1
  11. package/dist/migrate-book-topology.js +49 -33
  12. package/dist/navigator-attendance.js +28 -143
  13. package/dist/navigator-invocation-identity.js +2 -0
  14. package/dist/navigator-public-session.js +13 -1
  15. package/dist/navigator-work-base.js +113 -0
  16. package/dist/notary-contracts.js +0 -2
  17. package/dist/open-tool-schema.js +4 -1
  18. package/dist/package-contracts/fixer-output.js +1 -1
  19. package/dist/package-contracts/gatekeeper-output.js +1 -1
  20. package/dist/package-contracts/judge-output.js +2 -2
  21. package/dist/package-contracts/navigator-output.js +1 -1
  22. package/dist/package-contracts/terminating-infrastructure.js +7 -4
  23. package/dist/packaged-role-registry.js +9 -5
  24. package/dist/public-cli/countersign-run.js +1 -1
  25. package/dist/public-cli/invocation.js +6 -3
  26. package/dist/public-cli/main.js +78 -41
  27. package/dist/public-cli/settlement.js +20 -1
  28. package/dist/receipt-delivery-policy.js +9 -2
  29. package/dist/review-submission.js +19 -8
  30. package/dist/submission-errors.js +17 -0
  31. package/dist/submission-ledger.js +18 -6
  32. package/docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md +9 -0
  33. package/extensions/role-runtime.ts +0 -27
  34. package/package.json +2 -1
  35. package/resources/836-deleted-machine-instruction-inventory.md +3 -3
  36. package/resources/collector-bot-handbook.md +4 -0
  37. package/resources/navigator-route-playbook.md +4 -6
  38. package/scripts/build-package.mjs +4 -0
  39. package/souls/coder-output-guide.md +1 -0
  40. package/src/auditor-soul.ts +2 -1
  41. package/src/collector-role.ts +6 -7
  42. package/src/collector-tool-schemas.ts +5 -5
  43. package/src/compliance-transport.ts +3 -2
  44. package/src/countersign-contracts.ts +2 -2
  45. package/src/countersign-role.ts +3 -7
  46. package/src/diarist-role.ts +2 -2
  47. package/src/doctor-contracts.ts +1 -1
  48. package/src/gatekeeper-role.ts +15 -36
  49. package/src/gleaner-left-role.ts +2 -2
  50. package/src/inspector-contracts.ts +5 -2
  51. package/src/inspector-role.ts +1 -1
  52. package/src/judge-role.ts +14 -22
  53. package/src/merger-contracts.ts +1 -1
  54. package/src/navigator-attendance.ts +38 -164
  55. package/src/navigator-invocation-identity.ts +2 -0
  56. package/src/navigator-public-session.ts +17 -1
  57. package/src/navigator-role.ts +4 -0
  58. package/src/navigator-session-contracts.ts +2 -0
  59. package/src/navigator-work-base.ts +123 -0
  60. package/src/notary-contracts.ts +0 -4
  61. package/src/open-tool-schema.ts +9 -4
  62. package/src/package-contracts/fixer-output.ts +1 -1
  63. package/src/package-contracts/gatekeeper-output.ts +3 -3
  64. package/src/package-contracts/judge-output.ts +2 -4
  65. package/src/package-contracts/navigator-output.ts +1 -1
  66. package/src/package-contracts/terminating-infrastructure.ts +12 -10
  67. package/src/packaged-role-registry.ts +5 -1
  68. package/src/public-cli/countersign-run.ts +1 -1
  69. package/src/public-cli/invocation.ts +6 -3
  70. package/src/public-cli/settlement.ts +21 -0
  71. package/src/receipt-delivery-policy.ts +9 -1
  72. package/src/review-submission.ts +24 -10
  73. package/src/reviewer-role.ts +3 -3
  74. package/src/role-runtime-dependencies.ts +1 -5
  75. package/src/role-runtime.ts +61 -63
  76. package/src/secretariat-role.ts +3 -3
  77. package/src/submission-errors.ts +16 -0
  78. package/src/submission-gate.ts +2 -1
  79. package/src/submission-ledger.ts +23 -11
  80. package/src/worker-role.ts +7 -9
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Durable work subject and controlling authority for one navigator nest.
3
+ * Settlement user turns keep a path pointer; the navigator system prompt
4
+ * loads the bytes once per agent start from this file (base material).
5
+ */
6
+ import { existsSync, realpathSync } from "node:fs";
7
+ import { readFile } from "node:fs/promises";
8
+ import { basename, dirname, join, resolve } from "node:path";
9
+
10
+ import { writeFileAtomically } from "./atomic-write.ts";
11
+
12
+ export const NAVIGATOR_WORK_CONTEXT_BASENAME = "work-context.json";
13
+
14
+ export type NavigatorWorkBase = {
15
+ readonly subject: string;
16
+ readonly authority: string;
17
+ };
18
+
19
+ export function navigatorWorkContextFile(sessionDir: string): string {
20
+ return join(sessionDir, NAVIGATOR_WORK_CONTEXT_BASENAME);
21
+ }
22
+
23
+ /** Nest file only: books/.../navigator/<sha256-32>/work-context.json. */
24
+ export function isNavigatorWorkContextFile(path: string): boolean {
25
+ const resolved = resolve(path);
26
+ if (basename(resolved) !== NAVIGATOR_WORK_CONTEXT_BASENAME) return false;
27
+ const nest = dirname(resolved);
28
+ if (!/^[0-9a-f]{32}$/.test(basename(nest))) return false;
29
+ return basename(dirname(nest)) === "navigator";
30
+ }
31
+
32
+ function canonicalNavigatorWorkContextFile(path: string): string | undefined {
33
+ if (!isNavigatorWorkContextFile(path)) return undefined;
34
+ try {
35
+ const real = realpathSync(path);
36
+ return isNavigatorWorkContextFile(real) ? real : undefined;
37
+ } catch {
38
+ return undefined;
39
+ }
40
+ }
41
+
42
+ export async function persistNavigatorWorkBase(
43
+ sessionDir: string,
44
+ body: NavigatorWorkBase,
45
+ ): Promise<string | undefined> {
46
+ if (sessionDir.trim() === "" || !existsSync(sessionDir)) return undefined;
47
+ if (body.authority.trim() === "") return undefined;
48
+ const path = navigatorWorkContextFile(sessionDir);
49
+ const next = `${JSON.stringify({ subject: body.subject, authority: body.authority })}\n`;
50
+ try {
51
+ const current = await readFile(path, "utf8");
52
+ if (current === next) return path;
53
+ } catch (error) {
54
+ if (!(error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "ENOENT")) {
55
+ throw error;
56
+ }
57
+ }
58
+ await writeFileAtomically(path, next);
59
+ return path;
60
+ }
61
+
62
+ export async function readNavigatorWorkBase(path: string): Promise<NavigatorWorkBase | undefined> {
63
+ const canonical = canonicalNavigatorWorkContextFile(path);
64
+ if (canonical === undefined) return undefined;
65
+ let text: string;
66
+ try {
67
+ text = await readFile(canonical, "utf8");
68
+ } catch (error) {
69
+ if (error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "ENOENT") {
70
+ return undefined;
71
+ }
72
+ throw error;
73
+ }
74
+ let parsed: unknown;
75
+ try {
76
+ parsed = JSON.parse(text);
77
+ } catch {
78
+ return undefined;
79
+ }
80
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return undefined;
81
+ const record = parsed as Record<string, unknown>;
82
+ if (typeof record.subject !== "string" || typeof record.authority !== "string") return undefined;
83
+ if (record.authority.trim() === "") return undefined;
84
+ return { subject: record.subject, authority: record.authority };
85
+ }
86
+
87
+ function jsonObjectFromPrompt(prompt: string): Record<string, unknown> | undefined {
88
+ const head = prompt.trimStart().split("\n\n", 1)[0] ?? "";
89
+ if (!head.startsWith("{")) return undefined;
90
+ try {
91
+ const parsed: unknown = JSON.parse(head);
92
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return undefined;
93
+ return parsed as Record<string, unknown>;
94
+ } catch {
95
+ return undefined;
96
+ }
97
+ }
98
+
99
+ export function workContextPathFromPrompt(prompt: string): string | undefined {
100
+ const record = jsonObjectFromPrompt(prompt);
101
+ const path = record?.workContextPath;
102
+ if (typeof path !== "string" || path.trim() === "") return undefined;
103
+ return isNavigatorWorkContextFile(path) ? path : undefined;
104
+ }
105
+
106
+ /** Settlement JSON gains the nest pointer. Non-JSON prompts stay unchanged. */
107
+ export function attachWorkContextPointer(text: string, workContextPath: string): string {
108
+ const record = jsonObjectFromPrompt(text);
109
+ if (record === undefined) return text;
110
+ if (typeof record.workContextPath === "string" && record.workContextPath.trim() !== "") return text;
111
+ const head = text.trimStart().split("\n\n", 1)[0] ?? "";
112
+ const rest = text.trimStart().slice(head.length);
113
+ return `${JSON.stringify({ ...record, workContextPath })}${rest}`;
114
+ }
115
+
116
+ /** System-prompt base for one agent start. Absent or unreadable file yields undefined. */
117
+ export async function loadNavigatorWorkBaseSuffix(prompt: string): Promise<string | undefined> {
118
+ const path = workContextPathFromPrompt(prompt);
119
+ if (path === undefined) return undefined;
120
+ const body = await readNavigatorWorkBase(path);
121
+ if (body === undefined) return undefined;
122
+ return `<work_subject>\n${body.subject}\n</work_subject>\n\n<controlling_authority>\n${body.authority}\n</controlling_authority>`;
123
+ }
@@ -23,10 +23,6 @@ export const NOTARY_TICKET_FLAG = {
23
23
  },
24
24
  } as const;
25
25
 
26
- /** Package-owned kickoff only — callers supply zero prompt bytes (ADR 0067 / #448). */
27
- export const NOTARY_FIXED_KICKOFF =
28
- "符宝郎案卷已受理;来源 run 定位见会话材料。";
29
-
30
26
  export const notaryOutputSchema = reviewSubmissionSchema;
31
27
 
32
28
  export type NotarySourceRunLocator = {
@@ -12,7 +12,7 @@ function declarationIdentity(schema: TSchema): string {
12
12
  return JSON.stringify(semantic);
13
13
  }
14
14
 
15
- /** Collapse transport variants into one provider-compatible, zero-required open object. */
15
+ /** Collapse transport variants into one provider-compatible open object. Non-three-state tools stay zero-required. */
16
16
  export function openToolObjectFromUnion(schema: UnionSchema): TSchema {
17
17
  const declarations = new Map<string, TSchema[]>();
18
18
  for (const variant of schema.anyOf) {
@@ -35,10 +35,15 @@ export function openToolObjectFromUnion(schema: UnionSchema): TSchema {
35
35
  return object;
36
36
  }
37
37
 
38
- /** Open a pre-existing object transport schema without changing declarations. */
39
- export function openToolObject(schema: TSchema & { properties: Record<string, TSchema> }): TSchema {
38
+ /** Open a pre-existing object transport schema. Every field stays optional. */
39
+ export function openToolObject(
40
+ schema: TSchema & { properties: Record<string, TSchema> },
41
+ ): TSchema {
40
42
  const object = Type.Object(
41
- Object.fromEntries(Object.entries(schema.properties).map(([name, declaration]) => [name, Type.Optional(described(name, declaration))])),
43
+ Object.fromEntries(Object.entries(schema.properties).map(([name, declaration]) => [
44
+ name,
45
+ Type.Optional(described(name, declaration)),
46
+ ])),
42
47
  { additionalProperties: true },
43
48
  );
44
49
  (object as unknown as { required: string[] }).required = [];
@@ -41,7 +41,7 @@ const completedClassResultsSchema = Type.Array(completedClassResultSchema);
41
41
  // (src/countersign-role.ts) — one shared description across every variant
42
42
  // so openToolObjectFromUnion's identical-declaration collapse drops none of it.
43
43
  const FIXER_STATUS_DESCRIPTION =
44
- "planned | completed | refused | unfinished | partially_completed — 形状指引,非 schema 闸;unfinished 缺前置或违宪约束致本局未完成时可用,缺待决 owner 决定或答复属缺前置。" as const;
44
+ "planned | completed | refused | unfinished | partially_completed。unfinished 缺前置或违宪约束致本局未完成时可用,缺待决 owner 决定或答复属缺前置。" as const;
45
45
  const fixerOutputVariants = Type.Union([
46
46
  Type.Object({ status: Type.Unknown({ description: FIXER_STATUS_DESCRIPTION }), report: Type.String({ description: "如实结果报告" }) }),
47
47
  Type.Object({ status: Type.Unknown({ description: FIXER_STATUS_DESCRIPTION }), report: Type.String({ description: "如实结果报告" }), remainingScope: Type.String({ description: "依法不能完成的工作范围" }), blocker: Type.Unsafe({ ...blockerSchema, description: "合法阻断完成的 blocker" }) }),
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Public Gatekeeper (门下省) terminating receipt contracts (#639).
3
- * Direct public seat shares the province decision shape: dispatch | pass.
3
+ * Direct public seat decision shape. status field semantics: dispatch | pass.
4
4
  * No usable result is infrastructure failure via public settlement, not a judgment status (#475).
5
5
  */
6
6
  import { Type } from "typebox";
@@ -14,7 +14,7 @@ export const GATEKEEPER_OUTPUT_TOOL_NAME = "ak_gatekeeper_output";
14
14
  export const gatekeeperDecisionSchema = openToolObject(
15
15
  Type.Object({
16
16
  status: Type.Unknown({
17
- description: "dispatch | pass — 形状指引,非 schema 闸",
17
+ description: "dispatch | pass",
18
18
  }),
19
19
  officer: Type.Unknown({
20
20
  description: "status 为 dispatch 时为 inspector | notary",
@@ -43,7 +43,7 @@ function asStringArray(value: unknown): readonly string[] {
43
43
  }
44
44
 
45
45
  /**
46
- * Project one lawful explicit Gatekeeper decision (dispatch | pass).
46
+ * Project one explicit Gatekeeper decision.
47
47
  * No throw on shape — ADR 0055 / 第 0 条: already-submitted params are retained as-is;
48
48
  * public-terminal projects non-usable releases via typed failure cause.
49
49
  */
@@ -1,7 +1,7 @@
1
1
  /** Package-owned Judge output leaf — no role registration surface. */
2
2
 
3
3
 
4
- import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "../review-submission.ts";
4
+ import { REVIEW_QUEUE_STATUSES, REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "../review-submission.ts";
5
5
 
6
6
  export const JUDGE_OUTPUT_TOOL_NAME: string = REVIEW_SUBMISSION_OUTPUT_TOOL_NAME;
7
7
 
@@ -39,9 +39,7 @@ export function validateAcceptedJudgeDetails(verdict: unknown): JudgeVerdict {
39
39
  if (typeof status !== "string") {
40
40
  throw new Error("Judge verdict has no execution discriminator");
41
41
  }
42
- if (
43
- ["converged", "continue", "escalate"].includes(status)
44
- ) {
42
+ if (REVIEW_QUEUE_STATUSES.has(status)) {
45
43
  return verdict as JudgeVerdict;
46
44
  }
47
45
  throw new Error("Judge verdict has no execution discriminator");
@@ -16,7 +16,7 @@ export const navigatorOutputSchema = withTerminatingOutputDeclarations(
16
16
  openToolObject(
17
17
  Type.Object({
18
18
  prose: Type.Unknown({
19
- description: "游奕使散文建议,原样呈现。不要求 candidates/next 等结构化字段。形状指引,非 schema 闸",
19
+ description: "游奕使散文建议,原样呈现。不要求 candidates/next 等结构化字段。",
20
20
  }),
21
21
  }),
22
22
  ),
@@ -28,14 +28,12 @@ export const INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY = "diagnostic" as const;
28
28
  const infrastructureFailureNested = Type.Object(
29
29
  {
30
30
  [INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY]: Type.Unknown({
31
- description:
32
- "非空基础设施失败诊断字符串。无失败时必须省略整个 infrastructureFailure。形状指引,非 schema 闸。",
31
+ description: "基础设施失败诊断字符串。",
33
32
  }),
34
33
  },
35
34
  {
36
35
  additionalProperties: true,
37
- description:
38
- "基础设施真实失败声明(如需)。规范形:{ diagnostic: 非空诊断字符串 };无失败时必须省略。形状指引,非 schema 闸。",
36
+ description: "仅基础设施真实失败时出现。",
39
37
  },
40
38
  );
41
39
  // Open nested required so host cannot pure-shape-reject the declaration fragment.
@@ -50,8 +48,9 @@ const infrastructureFailureDeclarationSchema = Type.Object(
50
48
 
51
49
  /**
52
50
  * Compose declarations shared by terminating output tools: infrastructure failure
53
- * and the optional role-asserted ticket identity. Returns an open object (additionalProperties: true,
54
- * required: []) with the base schema's properties plus the shared declaration.
51
+ * and the optional role-asserted ticket identity. Returns an open object (additionalProperties: true)
52
+ * with the base schema's properties plus the shared declaration. Incoming required
53
+ * keys that still exist are kept; every other key stays optional.
55
54
  * Static typing is preserved on the base (`as S`), so existing
56
55
  * `Static<typeof ...>` derived parameter types are unchanged.
57
56
  */
@@ -66,8 +65,7 @@ export function withTerminatingOutputDeclarations<
66
65
  baseProperties?.ticketNumber === undefined
67
66
  ? {
68
67
  ticketNumber: Type.Unknown({
69
- description:
70
- "可选本票号:尚未绑定时由角色在既有回执中申报;机械层只记录合法正整数,不从散文推导、不验真。缺失或错形不拒收。",
68
+ description: "可选本票号。尚未绑定时由角色在既有回执中申报。",
71
69
  }),
72
70
  }
73
71
  : {}
@@ -78,7 +76,11 @@ export function withTerminatingOutputDeclarations<
78
76
  ],
79
77
  };
80
78
  const object = Type.Object(properties, { additionalProperties: true });
81
- (object as unknown as { required: string[] }).required = [];
79
+ const incoming = (schema as { required?: unknown }).required;
80
+ const preserved = Array.isArray(incoming)
81
+ ? incoming.filter((key): key is string => typeof key === "string" && Object.hasOwn(properties, key))
82
+ : [];
83
+ (object as unknown as { required: string[] }).required = preserved;
82
84
  return object as unknown as S;
83
85
  }
84
86
 
@@ -115,7 +117,7 @@ function isInfrastructureFailureDeclaration(
115
117
  }
116
118
 
117
119
  /** Non-empty trimmed diagnostic from the declaration, else undefined. */
118
- function infrastructureFailureDiagnostic(
120
+ export function infrastructureFailureDiagnostic(
119
121
  parameters: unknown,
120
122
  ): string | undefined {
121
123
  if (!isInfrastructureFailureDeclaration(parameters)) return undefined;
@@ -52,6 +52,7 @@ export const NOTARY_SESSION_MATERIALS = [
52
52
  "souls/audit-law.md",
53
53
  "souls/ticket-law.md",
54
54
  "souls/gate-output-guide.md",
55
+ "docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md",
55
56
  ] as const;
56
57
 
57
58
  /** Shared by public inspector and gatekeeper-province inspector. */
@@ -61,6 +62,7 @@ export const INSPECTOR_SESSION_MATERIALS = [
61
62
  "souls/audit-law.md",
62
63
  "souls/quality-law.md",
63
64
  "souls/gate-output-guide.md",
65
+ "docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md",
64
66
  ] as const;
65
67
 
66
68
  /**
@@ -75,6 +77,7 @@ export const AUDITOR_PUBLIC_SESSION_MATERIALS = [
75
77
  "souls/doctor-auditor.md",
76
78
  "souls/audit-law.md",
77
79
  "souls/quality-law.md",
80
+ "docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md",
78
81
  ] as const;
79
82
 
80
83
  /**
@@ -107,6 +110,7 @@ export const PUBLIC_ROLE_RECORDS = [
107
110
  "souls/audit-law.md",
108
111
  "souls/quality-law.md",
109
112
  "souls/judge-output-guide.md",
113
+ "docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md",
110
114
  ],
111
115
  },
112
116
  {
@@ -384,7 +388,7 @@ export const PUBLIC_ROLE_RECORDS = [
384
388
  activationStage: "load-and-install",
385
389
  receiptStatusKey: "status",
386
390
  // #924: 公用《票面法》三席同装
387
- sessionMaterials: ["CLAUDE.md", "souls/countersign.md", "souls/ticket-law.md", "resources/countersign-ticket-issue.md"],
391
+ sessionMaterials: ["CLAUDE.md", "souls/countersign.md", "souls/ticket-law.md", "resources/countersign-ticket-issue.md", "docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md"],
388
392
  },
389
393
  {
390
394
  role: "secretariat",
@@ -369,7 +369,7 @@ export async function runCountersignCourtDiaristStation(
369
369
  for (const ticketNumber of refreshTickets.slice(completedIndex + 1)) {
370
370
  const outcome = await invokeCourtDiarist(
371
371
  {
372
- instruction: `整理 #${ticketNumber} 的本案依据。`,
372
+ instruction: admitted.instruction,
373
373
  projectRoot: admitted.projectRoot,
374
374
  failureLabel: `ticket #${ticketNumber}`,
375
375
  correlationId: admitted.runId,
@@ -87,7 +87,6 @@ import { sha256Hex } from "../sha256.ts";
87
87
  import { rehomeUnboundTicketProvenance } from "../ticket-provenance.ts";
88
88
  import { uuidv7 } from "../uuidv7.ts";
89
89
  import {
90
- NOTARY_FIXED_KICKOFF,
91
90
  type NotarySourceRunLocator,
92
91
  } from "../notary-contracts.ts";
93
92
  import {
@@ -2150,6 +2149,7 @@ type InstructionTransportSource = {
2150
2149
  readonly baseRevision?: string;
2151
2150
  readonly lens?: ReviewerLens;
2152
2151
  readonly authorityRefs?: readonly string[];
2152
+ readonly sourceRunPath?: string;
2153
2153
  };
2154
2154
 
2155
2155
  function admittedTransportPromptKind(
@@ -2172,13 +2172,16 @@ export function buildInstructionTransportPrompt(
2172
2172
  ): string {
2173
2173
  const kind = admittedTransportPromptKind(admitted);
2174
2174
  if (kind === "fixed-kickoff") {
2175
- return appendEngineSessionMaterial([NOTARY_FIXED_KICKOFF], engineMaterial).join("\n");
2175
+ if (admitted.sourceRunPath === undefined || admitted.sourceRunPath.trim() === "") {
2176
+ throw new Error("fixed-kickoff transport prompt is missing the source run pointer");
2177
+ }
2178
+ return appendEngineSessionMaterial([admitted.sourceRunPath], engineMaterial).join("\n");
2176
2179
  }
2177
2180
  if (kind === "baseline") {
2178
2181
  if (admitted.baseRevision === undefined) {
2179
2182
  throw new Error("baseline transport prompt is missing the bound revision");
2180
2183
  }
2181
- const lines = [`左拾遗案已受理。比较基线:${admitted.baseRevision}`];
2184
+ const lines = [admitted.baseRevision];
2182
2185
  if (!admitted.instructionEmpty) {
2183
2186
  lines.push("", admitted.instruction);
2184
2187
  }
@@ -99,6 +99,7 @@ import {
99
99
  isAcceptedPackagedRoleTerminalResult,
100
100
  isReceiptSettlementBindingClear,
101
101
  NAVIGATOR_INVOCATION_ENTRY,
102
+ NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY,
102
103
  parseInvocationMarkerIdentity,
103
104
  type InvocationMarkerIdentity,
104
105
  } from "../navigator-invocation-identity.ts";
@@ -2249,8 +2250,28 @@ async function withOptionalGateProjection<
2249
2250
  return attachEngineDetourToolUsage(next, sessionDirectory, gateContext);
2250
2251
  }
2251
2252
 
2253
+ function routePlaybookFailureMessage(entries: readonly SessionEntry[]): string | undefined {
2254
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
2255
+ const entry = entries[i];
2256
+ if (entry?.type !== "custom" || entry.customType !== NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY) continue;
2257
+ const data = entry.data;
2258
+ if (!isRecord(data) || typeof data.message !== "string" || data.message.trim() === "") return undefined;
2259
+ return data.message;
2260
+ }
2261
+ return undefined;
2262
+ }
2263
+
2252
2264
  export function extractNavigatorFact(
2253
2265
  entries: readonly SessionEntry[],
2266
+ ): TerminalNavigatorFact {
2267
+ const fact = extractNavigatorAttendanceFact(entries);
2268
+ if (fact.advisoryDiagnostic !== undefined) return fact;
2269
+ const message = routePlaybookFailureMessage(entries);
2270
+ return message === undefined ? fact : { ...fact, advisoryDiagnostic: message };
2271
+ }
2272
+
2273
+ function extractNavigatorAttendanceFact(
2274
+ entries: readonly SessionEntry[],
2254
2275
  ): TerminalNavigatorFact {
2255
2276
  // Affirmative attendance only. Missing / uncorrelated / unparseable is never no-advice.
2256
2277
  // Minimal provenance: latest durable terminal + nearest preceding marker +
@@ -1,6 +1,5 @@
1
1
  /** Shared accepted-receipt delivery budget for role, auditor, and Navigator sessions (#288). */
2
2
  export const RECEIPT_DELIVERY_TURN_LIMIT = 2 as const;
3
- export const RECEIPT_DELIVERY_PROMPT = "本会话尚无已接受的 typed 回执。";
4
3
 
5
4
  export const NO_RECEIPT_LIFECYCLE_ENTRY_TYPE = "ak-no-receipt-lifecycle" as const;
6
5
 
@@ -104,6 +103,15 @@ export function createReceiptDeliveryPolicy() {
104
103
  if (accepted) return "accepted";
105
104
  return deliveryTurns < RECEIPT_DELIVERY_TURN_LIMIT ? "request-delivery" : "no-receipt";
106
105
  },
106
+ /** Current typed delivery facts. No prose. */
107
+ deliveryState() {
108
+ return {
109
+ terminalToolCalled,
110
+ rejectedReceipts: rejectedReceipts.map((item) => ({ ...item })),
111
+ deliveryTurns,
112
+ acceptedReceipt: false as const,
113
+ };
114
+ },
107
115
  facts(binding: { runPointer: string; attemptPointer: string }): NoReceiptLifecycleFacts {
108
116
  return noReceiptLifecycleFacts({ terminalToolCalled, rejectedReceipts: [...rejectedReceipts], deliveryTurns, ...binding });
109
117
  },
@@ -1,18 +1,33 @@
1
- /** One shared terminating receipt contract for review officers (#1028). */
1
+ /** One shared terminating receipt contract for review officers (#1028 / #1055). */
2
2
  import { Type } from "typebox";
3
3
 
4
- import { openToolObject } from "./open-tool-schema.ts";
5
4
  import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
6
5
 
7
6
  export const REVIEW_SUBMISSION_OUTPUT_TOOL_NAME = "ak_submission_output" as const;
8
7
 
9
- /** Open receipt: the queue reads only status; all other submitted fields ride unchanged. */
8
+ /** Single schema copy of the review queue words. Runtime membership reads this set. */
9
+ export const REVIEW_QUEUE_WORDS = ["converged", "continue", "escalate"] as const;
10
+ export type ReviewQueueWord = (typeof REVIEW_QUEUE_WORDS)[number];
11
+ export const REVIEW_QUEUE_STATUSES: ReadonlySet<string> = new Set(REVIEW_QUEUE_WORDS);
12
+
13
+ /**
14
+ * One object root. Codex structured output rejects a root anyOf, and status
15
+ * stays a required three-state enum with no omitted-status failure branch.
16
+ * External-dependency failure while the seat can still submit:
17
+ * docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md
18
+ */
10
19
  export const reviewSubmissionSchema = withTerminatingOutputDeclarations(
11
- openToolObject(
12
- Type.Object({
13
- status: Type.Optional(Type.Unknown({
14
- description: "converged | continue | escalate — 非三态时请重读后重交,勿改标。",
15
- })),
20
+ Type.Object({
21
+ status: Type.Union(
22
+ [
23
+ Type.Literal(REVIEW_QUEUE_WORDS[0]),
24
+ Type.Literal(REVIEW_QUEUE_WORDS[1]),
25
+ Type.Literal(REVIEW_QUEUE_WORDS[2]),
26
+ ],
27
+ {
28
+ description: "审核席本轮裁决的判别状态。",
29
+ },
30
+ ),
16
31
  findings: Type.Optional(Type.Unknown({ description: "审核发现,原样留存" })),
17
32
  reason: Type.Optional(Type.Unknown({ description: "上呈理由" })),
18
33
  violations: Type.Optional(Type.Unknown({ description: "违规条目" })),
@@ -22,8 +37,7 @@ export const reviewSubmissionSchema = withTerminatingOutputDeclarations(
22
37
  note: Type.Optional(Type.Unknown({ description: "裁决附注" })),
23
38
  evidence: Type.Optional(Type.Unknown({ description: "留存证据" })),
24
39
  decisionGate: Type.Optional(Type.Unknown({ description: "需人权威处置的问题与选项;可说明 question、options,原样留存" })),
25
- }),
26
- ),
40
+ }, { additionalProperties: true }),
27
41
  );
28
42
 
29
43
  export type ReviewSubmission = { readonly status?: unknown; readonly [key: string]: unknown };
@@ -29,7 +29,7 @@ const reviewerAmendmentsSchema = Type.Object({
29
29
  }, {
30
30
  additionalProperties: true,
31
31
  description:
32
- "已运行 lens 的 amendments 承载 candidates、逐条处置与 verdict 的完整报告(非仅 verdict 行);显式单轴时另一轴可省略。hard-stop/usage error 为 refused 时,已产出报告仍照录对应 lens 字段。形状指引,非 schema 闸。",
32
+ "已运行 lens 的 amendments 承载 candidates、逐条处置与 verdict 的完整报告(非仅 verdict 行);显式单轴时另一轴可省略。hard-stop/usage error 为 refused 时,已产出报告仍照录对应 lens 字段。",
33
33
  });
34
34
  // #836 r16 class 1: diagnostic is LLM/human-read narrative content — no code
35
35
  // branches on its length (src/reviewer-role.ts consumer: reviewer content is
@@ -40,7 +40,7 @@ const reviewerAmendmentsSchema = Type.Object({
40
40
  // #917 §6: both normal lens verdicts → completed; hard-stop/usage error → refused
41
41
  // (report already produced still lands in selected-lens amendments).
42
42
  const REVIEWER_STATUS_DESCRIPTION =
43
- "completed | refused — 形状指引,非 schema 闸。两种正常 lens verdict(completeness / correctness)均 completed;hard-stop 与 usage error 为 refused(已产出报告仍照录进所选 lens amendments)。" as const;
43
+ "completed | refused。两种正常 lens verdict(completeness / correctness)均 completed;hard-stop 与 usage error 为 refused(已产出报告仍照录进所选 lens amendments)。" as const;
44
44
  const reviewerOutputVariants = Type.Union([
45
45
  Type.Object({
46
46
  status: Type.Unknown({ description: REVIEWER_STATUS_DESCRIPTION }),
@@ -90,7 +90,7 @@ export function createReviewerRoleRuntime(
90
90
 
91
91
  if (!registered) {
92
92
  registered = true;
93
- pi.registerTool({ name: REVIEWER_OUTPUT_TOOL_NAME, label: "御史台输出", description: "提交御史台终局回执。本席自调 ak-cross-m-review skill。", promptSnippet: "提交御史台终局回执", parameters: reviewerOutputSchema,
93
+ pi.registerTool({ name: REVIEWER_OUTPUT_TOOL_NAME, label: "御史台输出", description: "提交御史台终局回执。", promptSnippet: "提交御史台终局回执", parameters: reviewerOutputSchema,
94
94
  async execute(_id: string, parameters: unknown): Promise<HostToolResult<unknown>> {
95
95
  if (!soul) throw new Error("御史台输入未装载");
96
96
  return {
@@ -7,7 +7,7 @@ import { loadDoctorCase } from "./doctor-evidence.ts";
7
7
  import { createNativeNavigatorSessionFactory, createNavigatorAttendance } from "./navigator-attendance.ts";
8
8
  import { loadNavigatorWorkContext } from "./navigator-work-context.ts";
9
9
  import { loadNotarySourceRunLocator } from "./notary-source-run.ts";
10
- import { formatNavigatorRoleHelp, type RoleRuntimeDependencies } from "./role-runtime.ts";
10
+ import { type RoleRuntimeDependencies } from "./role-runtime.ts";
11
11
  import {
12
12
  loadAuditorReferenceMaterialsFromSubjectInput,
13
13
  loadAuditorSoulFromSubjectInput,
@@ -42,7 +42,6 @@ export function createRoleRuntimeDependencies(packageRoot: string): RoleRuntimeD
42
42
  // packageRoot is the install root (resources/ lives there). Never resolve via
43
43
  // import.meta.url — headless/acp production-host bundles live under dist/*/
44
44
  // and would otherwise look for dist/resources/ (#962).
45
- const navigatorRoutePlaybookPath = join(packageRoot, "resources/navigator-route-playbook.md");
46
45
  const collectorHandbookSeedPath = join(packageRoot, "resources/collector-bot-handbook.md");
47
46
  const doctorAuditor = createPiDoctorAuditor();
48
47
  const navigatorSessionFactory = createNativeNavigatorSessionFactory();
@@ -72,9 +71,6 @@ export function createRoleRuntimeDependencies(packageRoot: string): RoleRuntimeD
72
71
  subject: options.subject,
73
72
  authority: options.authority,
74
73
  invocationId: options.invocationId,
75
- loadSoul: () => loadRegisteredRoleSoul("navigator"),
76
- loadRoutePlaybook: () => readFile(navigatorRoutePlaybookPath, "utf8"),
77
- loadRoleHelp: async (role) => formatNavigatorRoleHelp(role),
78
74
  createSession: navigatorSessionFactory,
79
75
  ...(options.contextError === undefined ? {} : { contextError: options.contextError }),
80
76
  onEvent: options.onEvent,