@akagilnc/pi-workflow-roles 0.1.4321 → 0.1.4363

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 (37) hide show
  1. package/dist/acp-host/production-host.js +81 -99
  2. package/dist/doctor-contracts.js +2 -2
  3. package/dist/headless-host/production-host.js +81 -99
  4. package/dist/merger-contracts.js +2 -2
  5. package/dist/migrate-book-topology.js +13 -8
  6. package/dist/notary-contracts.js +2 -2
  7. package/dist/package-contracts/auditor-output.js +2 -2
  8. package/dist/package-contracts/fixer-output.js +2 -2
  9. package/dist/package-contracts/gatekeeper-output.js +2 -2
  10. package/dist/package-contracts/navigator-output.js +2 -2
  11. package/dist/package-contracts/terminating-infrastructure.js +7 -2
  12. package/dist/public-cli/case-dossier-delivery.js +45 -28
  13. package/dist/public-cli/main.js +71 -89
  14. package/dist/public-cli/post-admission.js +41 -41
  15. package/dist/public-cli/settlement.js +3 -53
  16. package/dist/sitian-appender.js +21 -4
  17. package/package.json +1 -1
  18. package/src/collector-tool-schemas.ts +2 -2
  19. package/src/countersign-role.ts +2 -2
  20. package/src/diarist-role.ts +2 -2
  21. package/src/doctor-contracts.ts +2 -2
  22. package/src/gleaner-left-role.ts +2 -2
  23. package/src/inspector-role.ts +2 -2
  24. package/src/judge-role.ts +2 -2
  25. package/src/merger-contracts.ts +2 -2
  26. package/src/notary-contracts.ts +2 -2
  27. package/src/package-contracts/auditor-output.ts +2 -2
  28. package/src/package-contracts/fixer-output.ts +2 -2
  29. package/src/package-contracts/gatekeeper-output.ts +2 -2
  30. package/src/package-contracts/navigator-output.ts +2 -2
  31. package/src/package-contracts/terminating-infrastructure.ts +13 -3
  32. package/src/public-cli/case-dossier-delivery.ts +48 -29
  33. package/src/public-cli/post-admission.ts +42 -50
  34. package/src/public-cli/settlement.ts +3 -53
  35. package/src/reviewer-role.ts +2 -2
  36. package/src/sitian-appender.ts +36 -3
  37. package/src/worker-role.ts +2 -2
@@ -672,44 +672,7 @@ async function loadBoundAuditorVolumes(sessionFile) {
672
672
  const parentId = parentEntries.find((entry) => entry.type === "session")?.id;
673
673
  if (parentId === undefined)
674
674
  return undefined;
675
- const isResumeEnvelopeBytes = (value) => {
676
- if (typeof value !== "string")
677
- return false;
678
- if (value.length === 0)
679
- return true;
680
- const nl = value.indexOf("\n");
681
- const firstLine = nl === -1 ? value : value.slice(0, nl);
682
- const body = firstLine === "" && nl !== -1 ? value.slice(nl + 1) : value;
683
- return body.startsWith("本次配置的劳务引擎及其手册:") || body.startsWith("- engine:");
684
- };
685
- const isResumeEnvelope = (msg) => {
686
- if (!isRecord(msg) || msg.role !== "user")
687
- return false;
688
- const text = typeof msg.text === "string" ? msg.text : typeof msg.content === "string" ? msg.content : undefined;
689
- if (isResumeEnvelopeBytes(text))
690
- return true;
691
- const content = msg.content;
692
- if (Array.isArray(content)) {
693
- return content.some((p) => isRecord(p) && (isResumeEnvelopeBytes(p.text) || isResumeEnvelopeBytes(p.content)));
694
- }
695
- return false;
696
- };
697
- let latestParentUserIndex = -1;
698
- for (let i = parentEntries.length - 1; i >= 0; i -= 1) {
699
- const entry = parentEntries[i];
700
- if (entry?.type !== "message" || entry.message?.role !== "user")
701
- continue;
702
- if (isResumeEnvelope(entry.message))
703
- continue;
704
- latestParentUserIndex = i;
705
- break;
706
- }
707
675
  const childDirectories = [join(dirname(sessionFile), "auditor-roles")];
708
- // Auto-resume seam (owner A): stale check must ignore resume envelope and
709
- // prioritize retention. Previous `attemptEntryIndex < latest` discarded the
710
- // first attempt's child after resume advanced latest, losing retentionFailure
711
- // when retry had no compliance entry. Fix: ignore envelope for staleness and
712
- // prefer any valid compliance failure before falling back to primary.
713
676
  const valid = [];
714
677
  let sawAnyDirectory = false;
715
678
  for (const childDirectory of childDirectories) {
@@ -759,9 +722,6 @@ async function loadBoundAuditorVolumes(sessionFile) {
759
722
  const attemptEntryId = typeof bindingParent?.attemptEntryId === "string"
760
723
  ? bindingParent.attemptEntryId
761
724
  : undefined;
762
- const attemptEntryIndex = attemptEntryId === undefined
763
- ? -1
764
- : parentEntries.findIndex((entry) => entry.id === attemptEntryId);
765
725
  const boundSessionFile = typeof bindingParent?.sessionFile === "string"
766
726
  ? bindingParent.sessionFile
767
727
  : typeof header.parentSession === "string"
@@ -769,10 +729,8 @@ async function loadBoundAuditorVolumes(sessionFile) {
769
729
  : undefined;
770
730
  if (boundSessionFile !== sessionFile)
771
731
  continue;
772
- if (bindingParent !== undefined &&
773
- (bindingParent.sessionId !== parentId || attemptEntryIndex < latestParentUserIndex)) {
732
+ if (bindingParent !== undefined && bindingParent.sessionId !== parentId)
774
733
  continue;
775
- }
776
734
  if (bindingParent === undefined && header.parentSession !== sessionFile)
777
735
  continue;
778
736
  valid.push({
@@ -781,8 +739,8 @@ async function loadBoundAuditorVolumes(sessionFile) {
781
739
  sessionFile,
782
740
  ...(attemptEntryId === undefined ? {} : { attemptEntryId }),
783
741
  });
784
- // Keep every qualifying interval in the current parent-user range.
785
- // A single first-match break drops later same-user summons failures (#636).
742
+ // Keep every interval bound to this parent. Auditor payload is relayed as
743
+ // recorded; code does not expire it from later user-message shape (#858).
786
744
  }
787
745
  }
788
746
  }
@@ -843,14 +801,6 @@ function providerStopFallbackFromAuditorVolumes(volumes) {
843
801
  }
844
802
  return undefined;
845
803
  }
846
- /** Recover a provider stop from the auditor child bound to the current parent attempt. */
847
- export async function readBoundAuditorKnownFailure(sessionFile) {
848
- const volumes = await loadBoundAuditorVolumes(sessionFile);
849
- if (volumes === undefined)
850
- return undefined;
851
- return complianceFailureFromAuditorVolumes(volumes)
852
- ?? providerStopFallbackFromAuditorVolumes(volumes);
853
- }
854
804
  /** Strong auditor tier only — retained compliance-failure entries, no provider-stop fallback. */
855
805
  async function readBoundAuditorComplianceFailure(sessionFile) {
856
806
  const volumes = await loadBoundAuditorVolumes(sessionFile);
@@ -152,25 +152,41 @@ function resolveSitianVolumeCategory(kind) {
152
152
  }
153
153
  return kind;
154
154
  }
155
+ const SITIAN_RECORDS_LEAF = "records.jsonl";
156
+ function ticketProvenanceUnderBookPaths(ledgerHome, bookKey, ticketId) {
157
+ const sessionDir = join(activationBookDirectory(ledgerHome, bookKey), ticketId);
158
+ return { sessionDir, recordFile: join(sessionDir, SITIAN_RECORDS_LEAF) };
159
+ }
155
160
  function resolveSitianRecordPathInLedger(input, ledgerHome) {
156
161
  const category = resolveSitianVolumeCategory(input.kind);
157
162
  const ticketNumber = category === "ticket-provenance" ? typeof input.subject === "string" && /^[1-9][0-9]*$/.test(input.subject) ? input.subject : typeof input.subject === "object" && typeof input.subject.ticketNumber === "number" && Number.isSafeInteger(input.subject.ticketNumber) && input.subject.ticketNumber > 0 ? String(input.subject.ticketNumber) : void 0 : void 0;
158
163
  let sessionDir;
164
+ let recordFile;
159
165
  if (ticketNumber !== void 0) {
160
- const bookDir = activationBookDirectory(
166
+ const paths = ticketProvenanceUnderBookPaths(
161
167
  ledgerHome,
162
- resolveBookKeyFromGit(input.cwd ?? process.cwd())
168
+ resolveBookKeyFromGit(input.cwd ?? process.cwd()),
169
+ ticketNumber
163
170
  );
164
- sessionDir = join(bookDir, ticketNumber);
171
+ sessionDir = paths.sessionDir;
172
+ recordFile = paths.recordFile;
165
173
  } else {
166
174
  if (input.sessionParent === void 0 || input.sessionParent.length === 0 || !physicallyContainedIn(ledgerHome, input.sessionParent)) {
167
175
  throw new Error("Sitian record ownership requires a parent session inside the ledger home");
168
176
  }
169
177
  sessionDir = join(dirname(input.sessionParent), category);
178
+ recordFile = join(sessionDir, SITIAN_RECORDS_LEAF);
170
179
  }
171
- const recordFile = join(sessionDir, "records.jsonl");
172
180
  return { sessionDir, recordFile, ledgerHome };
173
181
  }
182
+ function projectTicketRecordsPathShape() {
183
+ const { recordFile } = ticketProvenanceUnderBookPaths(
184
+ join("~", ".ak-roles"),
185
+ "<\u7C3F>",
186
+ "<\u7968\u53F7>"
187
+ );
188
+ return recordFile.replace(/\\/g, "/");
189
+ }
174
190
  function resolveSitianRecordPath(input) {
175
191
  const ledgerHome = input.home !== void 0 && input.home.length > 0 ? resolveActivationLedgerHome(input.home) : resolveActivationLedgerHomeForPath(input.sessionParent);
176
192
  return resolveSitianRecordPathInLedger(input, ledgerHome);
@@ -210,6 +226,7 @@ function appendSitianRecord(input) {
210
226
  export {
211
227
  S4_SUBMISSION_LEDGER_KINDS,
212
228
  appendSitianRecord,
229
+ projectTicketRecordsPathShape,
213
230
  resolveSitianRecordPath,
214
231
  resolveSitianRecordPathInLedger,
215
232
  resolveSitianVolumeCategory
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.4321",
3
+ "version": "0.1.4363",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  import { Type, type Static } from "typebox";
2
2
  import { openToolObject } from "./open-tool-schema.ts";
3
- import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
3
+ import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
4
4
 
5
5
  // #836 r16 class 3: execute() reads no params for this tool
6
6
  // (src/collector-role.ts:431-459) — a closed root only rejects the role for
@@ -124,7 +124,7 @@ export const collectorOutputBaseSchema = openToolObject(
124
124
  );
125
125
 
126
126
  /** Runtime owns the observed evidence; the model submits findings and signals sole-final submission. */
127
- export const collectorOutputArgsSchema = withInfrastructureFailureDeclaration(
127
+ export const collectorOutputArgsSchema = withTerminatingOutputDeclarations(
128
128
  collectorOutputBaseSchema,
129
129
  );
130
130
 
@@ -1,7 +1,7 @@
1
1
  import type { Static } from "typebox";
2
2
  import { Type } 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 {
6
6
  COUNTERSIGN_OUTPUT_TOOL_NAME,
7
7
  validateRecordedCountersignOutput,
@@ -22,7 +22,7 @@ export type { CountersignVerdict };
22
22
  // classifies the recorded value and reasks the countersign itself when it
23
23
  // isn't one of the three states — code, not the transport, does that work.
24
24
  /** 给事中票庭审读五问的交卷形状(ADR 0074);形状指引,非 schema 闸。 */
25
- export const countersignVerdictSchema = withInfrastructureFailureDeclaration(
25
+ export const countersignVerdictSchema = withTerminatingOutputDeclarations(
26
26
  Type.Object(
27
27
  {
28
28
  countersignStatus: Type.Unknown({ description: "converged | continue | escalate。非三态时请重读后重交,勿改标。" }),
@@ -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
  DIARIST_OUTPUT_TOOL_NAME,
8
8
  validateRecordedDiaristOutput,
@@ -20,7 +20,7 @@ export { validateRecordedDiaristOutput };
20
20
  * 起居郎交卷形状;形状指引,非 schema 闸。
21
21
  * #901:交边界(sessions)+ 可选坏行补写(amendments);正文由机械投影。
22
22
  */
23
- export const diaristOutputSchema = withInfrastructureFailureDeclaration(
23
+ export const diaristOutputSchema = withTerminatingOutputDeclarations(
24
24
  openToolObject(
25
25
  Type.Object({
26
26
  status: Type.Unknown({
@@ -1,7 +1,7 @@
1
1
  import { Type } from "typebox";
2
2
  import { canonicalJson } from "./canonical-json.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
  export const DOCTOR_EVIDENCE_TOOL_NAME = "ak_doctor_evidence";
7
7
  export const DOCTOR_OUTPUT_TOOL_NAME = "ak_doctor_output";
@@ -87,7 +87,7 @@ const doctorSubmissionVariants = Type.Union([
87
87
  missingEvidence: Type.Array(Type.Object({ need: nonblank, targetKeys: evidenceIds }, { additionalProperties: true }), { description: "如实证词所需而尚缺的证据" }),
88
88
  }, { additionalProperties: false, description: "证据不足以支撑如实案证词" }),
89
89
  ]);
90
- export const doctorSubmissionSchema = withInfrastructureFailureDeclaration(
90
+ export const doctorSubmissionSchema = withTerminatingOutputDeclarations(
91
91
  openToolObjectFromUnion(doctorSubmissionVariants),
92
92
  );
93
93
  // #836 r16 class 3: action tool — code reads evidenceId to look up the Map entry
@@ -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({
@@ -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
@@ -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,