@akagilnc/pi-workflow-roles 0.1.3572 → 0.1.3621

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 (129) hide show
  1. package/dist/acp-host/production-host.js +20239 -10556
  2. package/dist/analyst-gate-cycles-read.js +387 -0
  3. package/dist/archivist-record-entry.js +22 -1
  4. package/dist/audit-escalation.js +6 -0
  5. package/dist/auditor-soul.js +74 -0
  6. package/dist/collector-config.js +86 -0
  7. package/dist/collector-evidence.js +316 -0
  8. package/dist/collector-github.js +527 -0
  9. package/dist/collector-ledger.js +853 -0
  10. package/dist/collector-tool-schemas.js +33 -0
  11. package/dist/compliance-transport.js +104 -52
  12. package/dist/diarist-mechanical.js +411 -0
  13. package/dist/diarist-ticket-resolution.js +177 -0
  14. package/dist/diarist.js +311 -0
  15. package/dist/doctor-auditor.js +25 -0
  16. package/dist/doctor-contracts.js +2 -0
  17. package/dist/doctor-evidence.js +142 -0
  18. package/dist/gatekeeper-role.js +104 -81
  19. package/dist/host-transition-prior-native.js +78 -0
  20. package/dist/institutional-resolution.js +1 -95
  21. package/dist/judge-auditor.js +26 -0
  22. package/dist/ledger-session-read.js +227 -0
  23. package/dist/merger-git-state.js +78 -0
  24. package/dist/navigator-attendance.js +15 -7
  25. package/dist/navigator-public-session.js +170 -0
  26. package/dist/navigator-session-contracts.js +36 -40
  27. package/dist/notary-source-run.js +122 -0
  28. package/dist/package-contracts/auditor-output.js +66 -0
  29. package/dist/package-contracts/evidence-child-output.js +34 -0
  30. package/dist/package-contracts/judge-output.js +2 -0
  31. package/dist/package-contracts/terminating-tools.js +25 -2
  32. package/dist/package-resources/method-skill.js +254 -0
  33. package/dist/packaged-role-registry.js +36 -0
  34. package/dist/pi/durable-principal.js +61 -0
  35. package/dist/pi/in-process-session.js +25 -6
  36. package/dist/pi/known-failure.js +52 -0
  37. package/dist/pi/role-turn-host.js +430 -0
  38. package/dist/public-cli/auto-resume.js +414 -0
  39. package/dist/public-cli/cli-errors.js +8 -0
  40. package/dist/public-cli/cli-io.js +1 -0
  41. package/dist/public-cli/command-renderer.js +5 -0
  42. package/dist/public-cli/doctor-run.js +84 -0
  43. package/dist/public-cli/inspector-run.js +136 -0
  44. package/dist/public-cli/instruction-seat-run.js +256 -0
  45. package/dist/public-cli/invocation.js +2225 -0
  46. package/dist/public-cli/judge-run.js +118 -0
  47. package/dist/public-cli/load-production-acp-host.js +40 -0
  48. package/dist/public-cli/main.js +1140 -652
  49. package/dist/public-cli/notary-run.js +161 -0
  50. package/dist/public-cli/option-definitions.js +1192 -0
  51. package/dist/public-cli/post-admission.js +667 -0
  52. package/dist/public-cli/public-run-credentials.js +49 -0
  53. package/dist/public-cli/registry.js +4 -1
  54. package/dist/public-cli/reviewer-dispatch-rejection.js +77 -0
  55. package/dist/public-cli/run-lifecycle.js +1344 -0
  56. package/dist/public-cli/seat-ticket-binding.js +113 -0
  57. package/dist/public-cli/settlement.js +3441 -0
  58. package/dist/public-cli/terminal.js +135 -0
  59. package/dist/public-cli/turn-request.js +25 -0
  60. package/dist/public-role-summons.js +301 -0
  61. package/dist/receipt-delivery-policy.js +10 -0
  62. package/dist/reviewer-child-executor.js +80 -8
  63. package/dist/reviewer-execution-ledger.js +2 -1
  64. package/dist/run-terminal-artifacts.js +195 -0
  65. package/dist/run-ticket-number.js +40 -0
  66. package/dist/session-assistant-usage.js +107 -0
  67. package/dist/shape-unreadable-failure.js +34 -0
  68. package/dist/submission-errors.js +3 -0
  69. package/dist/submission-ledger.js +388 -0
  70. package/dist/ticket-provenance-contracts.js +115 -0
  71. package/dist/ticket-provenance.js +340 -0
  72. package/extensions/role-runtime.ts +7 -0
  73. package/package.json +1 -1
  74. package/scripts/build-package.mjs +2 -1
  75. package/souls/doctor-auditor.md +1 -0
  76. package/souls/evidence-child.md +1 -0
  77. package/src/acp-host/production-host.ts +3 -0
  78. package/src/acp-host/role-envelope.ts +2 -2
  79. package/src/acp-host/role-turn-host.ts +9 -1
  80. package/src/analyst-gate-cycles-read.ts +37 -2
  81. package/src/archivist-record-entry.ts +45 -1
  82. package/src/audit-escalation.ts +17 -0
  83. package/src/auditor-role.ts +22 -0
  84. package/src/auditor-soul.ts +42 -0
  85. package/src/compliance-transport.ts +177 -66
  86. package/src/doctor-auditor.ts +10 -14
  87. package/src/doctor-contracts.ts +1 -0
  88. package/src/doctor-role.ts +2 -2
  89. package/src/evidence-child-role.ts +22 -0
  90. package/src/gatekeeper-pass-envelope.ts +109 -0
  91. package/src/gatekeeper-role.ts +145 -104
  92. package/src/host-contracts.ts +9 -1
  93. package/src/institutional-resolution.ts +8 -163
  94. package/src/judge-auditor.ts +10 -15
  95. package/src/judge-role.ts +8 -0
  96. package/src/navigator-attendance.ts +25 -9
  97. package/src/navigator-public-session.ts +217 -0
  98. package/src/navigator-session-contracts.ts +61 -54
  99. package/src/notary-source-run.ts +3 -1
  100. package/src/package-contracts/auditor-output.ts +82 -0
  101. package/src/package-contracts/evidence-child-output.ts +51 -0
  102. package/src/package-contracts/judge-output.ts +1 -0
  103. package/src/package-contracts/reviewer-output.ts +2 -2
  104. package/src/package-contracts/terminating-tools.ts +31 -1
  105. package/src/packaged-role-registry.ts +43 -0
  106. package/src/pi/adapter.ts +2 -1
  107. package/src/pi/in-process-session.ts +38 -12
  108. package/src/pi/role-turn-host.ts +34 -0
  109. package/src/public-cli/cli.ts +14 -6
  110. package/src/public-cli/instruction-seat-run.ts +289 -50
  111. package/src/public-cli/invocation.ts +66 -25
  112. package/src/public-cli/option-definitions.ts +61 -0
  113. package/src/public-cli/post-admission.ts +7 -1
  114. package/src/public-cli/registry.ts +4 -1
  115. package/src/public-cli/run-lifecycle.ts +22 -3
  116. package/src/public-cli/settlement.ts +120 -5
  117. package/src/public-cli/terminal.ts +2 -0
  118. package/src/public-role-summons.ts +431 -0
  119. package/src/receipt-delivery-policy.ts +10 -0
  120. package/src/reviewer-agent.ts +1 -1
  121. package/src/reviewer-child-executor.ts +114 -12
  122. package/src/reviewer-execution-ledger.ts +3 -2
  123. package/src/role-runtime.ts +158 -17
  124. package/src/session-assistant-usage.ts +119 -0
  125. package/src/session-opening-materials.ts +1 -1
  126. package/src/shape-unreadable-failure.ts +49 -0
  127. package/src/submission-errors.ts +3 -0
  128. package/dist/evidence-child-executor.js +0 -829
  129. package/src/evidence-child-executor.ts +0 -1140
@@ -16,7 +16,7 @@ import {
16
16
  resolveActivationLedgerHome,
17
17
  } from "./activation-ledger-topology.ts";
18
18
  import type { HostContext } from "./host-contracts.ts";
19
- import { createNativeNavigatorSessionFactory } from "./evidence-child-executor.ts";
19
+ import { createNativeNavigatorSessionFactory } from "./navigator-public-session.ts";
20
20
  import {
21
21
  NAVIGATOR_DEFAULT_MODEL,
22
22
  NAVIGATOR_PREPARE_TOOL_NAME,
@@ -78,7 +78,9 @@ export function resolveNavigatorAuthorityMaterial(
78
78
  return undefined;
79
79
  }
80
80
 
81
- export const NAVIGATOR_TARGETS = PACKAGED_ROLE_REGISTRY.map(({ role, phases }) => ({ role, phases }));
81
+ /** Every public role is a lawful navigator route target (#675 — no nested-only seats). */
82
+ export const NAVIGATOR_TARGETS = PACKAGED_ROLE_REGISTRY
83
+ .map(({ role, phases }) => ({ role, phases }));
82
84
 
83
85
  export type NavigatorTargetRole = PackagedRole;
84
86
  export type NavigatorPhase = "plan" | "apply" | null;
@@ -591,11 +593,7 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
591
593
  const modelPromise = (async () => {
592
594
  try {
593
595
  // Same resolution authority as createNativeNavigatorSessionFactory (#590).
594
- const resolved = await resolveNavigatorSeatSelection(
595
- options.context,
596
- options.modelSettingPath,
597
- options.modelSettingPath ?? navigatorModelSettingPath(),
598
- );
596
+ const resolved = await resolveNavigatorSeatSelection(options.context);
599
597
  return resolved.configuredLabel;
600
598
  } catch (error) {
601
599
  if (error instanceof NavigatorUnavailableError) throw error;
@@ -735,6 +733,14 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
735
733
  return;
736
734
  }
737
735
  if (promptFailure !== undefined) throw promptFailure;
736
+ const sessionNoReceipt = activeSession.noReceipt?.();
737
+ if (sessionNoReceipt !== undefined) {
738
+ // This session already settled without an accepted receipt on its own
739
+ // budget; one more prompt opens an independent summon, not a delivery
740
+ // request on the settled session (#675).
741
+ delivery.recordNestedNoReceipt(sessionNoReceipt);
742
+ return;
743
+ }
738
744
  if (deliveryRequest && output === undefined) delivery.recordDeliveryRequest();
739
745
  };
740
746
  await promptAllowingRejectedPrepare(request, false);
@@ -831,6 +837,8 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
831
837
  };
832
838
 
833
839
  async function settleOnce(settlement: NavigatorSettlement): Promise<void> {
840
+ // Dispose during post-role grace must ignore late completion entirely (#675).
841
+ if (disposed) return;
834
842
  const invocationId = activeInvocationId ?? invocationPrincipal;
835
843
  let report: NavigatorReport;
836
844
  if (settlement.kind === "human_decision" || settlement.kind === "role_infrastructure_failure") {
@@ -876,8 +884,10 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
876
884
  // table. After selection (speculative or rebound), advice is passed
877
885
  // through as-is (ADR 0010 / ADR 0061: caller may ignore).
878
886
  if (selected?.candidate.next !== undefined && !selected.matchedToSettlement) {
887
+ if (disposed) return;
879
888
  prepareBoundSettlement = settlement;
880
889
  prepared = await prepare();
890
+ if (disposed) return;
881
891
  selected = selectNavigatorCandidate(prepared, settlement);
882
892
  }
883
893
  // Budget exhaustion is affirmative typed no-advice; malformed submitted
@@ -933,10 +943,16 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
933
943
  ...(report.routePlaybookReadFailure === undefined ? {} : { routePlaybookReadFailure: report.routePlaybookReadFailure }),
934
944
  ...(report.arrivalMessage === undefined ? {} : { arrivalMessage: report.arrivalMessage }),
935
945
  };
936
- // Dispose during post-role grace must ignore late completion (ADR 0052 / #106).
946
+ // Dispose during post-role grace must ignore late completion (ADR 0052 / #106 / #675).
937
947
  // Every settled disposition (recommendation | no-advice | unavailable | arrival) is affirmative.
938
- if (!disposed) {
948
+ // Only the disposed bit is typed authority to drop — never match free-text Error.message.
949
+ if (disposed) return;
950
+ try {
939
951
  await options.onEvent(event, report);
952
+ } catch (error) {
953
+ // Race: dispose landed between the check and onEvent; drop only then.
954
+ if (disposed) return;
955
+ throw error;
940
956
  }
941
957
  preparation = undefined;
942
958
  sessionReady = undefined;
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Navigator attendance session factory (#675 r3).
3
+ * Each prepare turn uses the public navigator activation path (summonPublicRole) —
4
+ * same seat table and shared envelope as `ak-role navigator`.
5
+ * Archivist createRecordSession only books route-memory nest under the parent;
6
+ * no openPiInProcessSession second lifecycle / no agentDir patch on the old path.
7
+ */
8
+ import { sitianReport } from "./sitian-facade.ts";
9
+ import {
10
+ NavigatorUnavailableError,
11
+ navigatorProviderFailureFromDiagnostics,
12
+ navigatorProviderFailureFromError,
13
+ navigatorProviderFailureFromStatus,
14
+ navigatorUnavailableError,
15
+ parseNavigatorModelSetting,
16
+ resolveNavigatorSeatSelection,
17
+ type NavigatorProviderFailureFact,
18
+ type NavigatorSessionFactory,
19
+ } from "./navigator-session-contracts.ts";
20
+ import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
21
+
22
+ /**
23
+ * Classify public-navigator failure terminal from structured decisiveFacts only
24
+ * (httpStatus / diagnostics / secondaryEvidence on the terminal) — same fields
25
+ * settlement already stamps from typed HTTP observation / provider stop.
26
+ */
27
+ function providerFailureFromPublicTerminal(outcome: {
28
+ readonly cause: string;
29
+ readonly diagnostic: string;
30
+ readonly decisiveFacts: Readonly<Record<string, unknown>>;
31
+ }): NavigatorProviderFailureFact {
32
+ const facts = outcome.decisiveFacts;
33
+ const secondary =
34
+ typeof facts.secondaryEvidence === "object" && facts.secondaryEvidence !== null
35
+ ? (facts.secondaryEvidence as Record<string, unknown>)
36
+ : undefined;
37
+ const httpStatus =
38
+ typeof secondary?.httpStatus === "number"
39
+ ? secondary.httpStatus
40
+ : typeof facts.httpStatus === "number"
41
+ ? facts.httpStatus
42
+ : typeof facts.errorCode === "number"
43
+ ? facts.errorCode
44
+ : undefined;
45
+ const fromStatus = navigatorProviderFailureFromStatus(httpStatus);
46
+ if (fromStatus !== undefined) return fromStatus;
47
+ const diagnostics = secondary?.diagnostics ?? facts.diagnostics;
48
+ const fromDiagnostics = navigatorProviderFailureFromDiagnostics(diagnostics);
49
+ if (fromDiagnostics !== undefined) return fromDiagnostics;
50
+ const fromCode = navigatorProviderFailureFromError({
51
+ code: secondary?.code ?? facts.errorCode,
52
+ });
53
+ if (fromCode !== undefined) return fromCode;
54
+ if (outcome.cause === "provider") return { source: "transport", cause: "transport" };
55
+ return { source: "session", cause: "session" };
56
+ }
57
+
58
+ export function createNativeNavigatorSessionFactory(): NavigatorSessionFactory {
59
+ return async ({ context, subject, tool }) => {
60
+ const resolved = await resolveNavigatorSeatSelection(context);
61
+ let thinkingLevel = resolved.thinkingLevel;
62
+
63
+ // Archivist nest for attendance route memory only (ADR 0018 / 0065) — not a session open.
64
+ const { createRecordSession } = await import("./archivist-record-entry.ts");
65
+ const sessionManager = createRecordSession({
66
+ cwd: context.cwd,
67
+ kind: "navigator",
68
+ subject,
69
+ parent: context.sessionManager,
70
+ });
71
+
72
+ let providerFailure: NavigatorProviderFailureFact | undefined;
73
+ let noReceipt: NoReceiptLifecycleFacts | undefined;
74
+ let disposed = false;
75
+ let inFlightPrompt: Promise<unknown> | undefined;
76
+
77
+ const resolveHome = async (): Promise<string | undefined> => {
78
+ const parentFile = context.sessionManager?.getSessionFile?.();
79
+ if (typeof parentFile === "string" && parentFile.trim() !== "") {
80
+ try {
81
+ const { tryHomeFromAkRolesPath, homeFromRunDirectory } = await import(
82
+ "./activation-ledger-topology.ts"
83
+ );
84
+ const fromParent = tryHomeFromAkRolesPath(parentFile);
85
+ if (fromParent !== undefined && fromParent.length > 0) return fromParent;
86
+ const runDir = parentFile.replace(/\/session\/session\.jsonl$/, "");
87
+ if (runDir !== parentFile) {
88
+ try {
89
+ return homeFromRunDirectory(runDir);
90
+ } catch {
91
+ // fall through
92
+ }
93
+ }
94
+ } catch {
95
+ // fall through
96
+ }
97
+ }
98
+ const envHome = process.env.HOME;
99
+ return typeof envHome === "string" && envHome.trim() !== "" ? envHome : undefined;
100
+ };
101
+
102
+ return {
103
+ prompt: async (text) => {
104
+ if (disposed) {
105
+ throw navigatorUnavailableError("session", new Error("Navigator attendance was disposed"));
106
+ }
107
+ providerFailure = undefined;
108
+ noReceipt = undefined;
109
+ const run = (async () => {
110
+ try {
111
+ const { summonPublicRole } = await import("./public-role-summons.ts");
112
+ const home = await resolveHome();
113
+ // Public activation — same face as `ak-role navigator <instruction>` (#675).
114
+ const summoned = await summonPublicRole({
115
+ role: "navigator",
116
+ argv: [text],
117
+ cwd: context.cwd,
118
+ ...(home === undefined ? {} : { home }),
119
+ });
120
+
121
+ const outcome = summoned.terminal?.roleOutcome;
122
+ if (outcome === undefined) {
123
+ const detail = summoned.stderr?.trim() || `exit ${summoned.exitCode}`;
124
+ providerFailure = { source: "transport", cause: "transport" };
125
+ throw navigatorUnavailableError(
126
+ "transport",
127
+ new Error(`Navigator public summon produced no terminal (${detail})`),
128
+ );
129
+ }
130
+ if (outcome.kind === "failure") {
131
+ providerFailure = providerFailureFromPublicTerminal(outcome);
132
+ throw navigatorUnavailableError(
133
+ providerFailure.source,
134
+ new Error(outcome.diagnostic),
135
+ providerFailure.cause,
136
+ );
137
+ }
138
+ if (outcome.kind === "no_receipt") {
139
+ // The nested session spent its own delivery budget; attendance settles
140
+ // no-receipt on these facts instead of opening another summon (#675).
141
+ noReceipt = outcome;
142
+ return;
143
+ }
144
+ if (outcome.kind !== "accepted") {
145
+ providerFailure = { source: "session", cause: "session" };
146
+ throw navigatorUnavailableError(
147
+ "session",
148
+ new Error("Navigator public summon returned unusable terminal"),
149
+ );
150
+ }
151
+ const candidates = outcome.decisiveFacts.candidates;
152
+ if (!Array.isArray(candidates)) {
153
+ return;
154
+ }
155
+ // Rejoin attendance prepare tool sink (same candidate shape as public advice).
156
+ await tool.execute(
157
+ "navigator-public-prepare",
158
+ { candidates },
159
+ undefined,
160
+ undefined,
161
+ context as never,
162
+ );
163
+ } catch (error) {
164
+ if (error instanceof NavigatorUnavailableError) throw error;
165
+ const fact = navigatorProviderFailureFromError(error);
166
+ providerFailure = fact ?? { source: "transport", cause: "transport" };
167
+ throw navigatorUnavailableError(providerFailure.source, error, providerFailure.cause);
168
+ }
169
+ })();
170
+ inFlightPrompt = run;
171
+ try {
172
+ await run;
173
+ } finally {
174
+ if (inFlightPrompt === run) inFlightPrompt = undefined;
175
+ }
176
+ },
177
+ providerFailure: () => providerFailure,
178
+ noReceipt: () => noReceipt,
179
+ appendEntry: (customType, data) => {
180
+ sessionManager.appendCustomEntry(customType, data);
181
+ try {
182
+ sitianReport({
183
+ level: "event",
184
+ kind: "attendance",
185
+ cwd: context.cwd,
186
+ sessionParent: sessionManager.getSessionFile(),
187
+ payload: { customType, data },
188
+ source: "navigator-public-session",
189
+ });
190
+ } catch {
191
+ // best-effort
192
+ }
193
+ },
194
+ entries: () => sessionManager.getEntries(),
195
+ setModel: async (next, nextThinking) => {
196
+ let nextParsed: ReturnType<typeof parseNavigatorModelSetting>;
197
+ try {
198
+ nextParsed = parseNavigatorModelSetting(next);
199
+ } catch (error) {
200
+ throw navigatorUnavailableError("model", error);
201
+ }
202
+ // No live provider session is pinned here: every prompt is an independent
203
+ // public summon whose nested CLI reads the current seat table (#675 验收②
204
+ // / #617 DK-3). A seat edit between prepares therefore applies on the next
205
+ // summon — it is never an unavailable session.
206
+ thinkingLevel = nextThinking ?? nextParsed.thinkingLevel;
207
+ },
208
+ getThinkingLevel: () => thinkingLevel,
209
+ recordPointer: () => sessionManager.getSessionDir(),
210
+ dispose: async () => {
211
+ disposed = true;
212
+ const pending = inFlightPrompt;
213
+ if (pending !== undefined) await pending.catch(() => undefined);
214
+ },
215
+ };
216
+ };
217
+ }
@@ -10,12 +10,9 @@ import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
10
10
  import { Type } from "typebox";
11
11
  import { Value } from "typebox/value";
12
12
 
13
- import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
14
13
  import type { HostContext, HostInstitutionalModelSelection } from "./host-contracts.ts";
15
- import {
16
- InstitutionalResolutionError,
17
- readInstitutionalSeatSelection,
18
- } from "./institutional-resolution.ts";
14
+ import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
15
+
19
16
 
20
17
  export const NAVIGATOR_PREPARE_TOOL_NAME = "ak_navigator_prepare" as const;
21
18
  export const NAVIGATOR_DEFAULT_MODEL = "openai-codex/gpt-5.6-luna:max" as const;
@@ -162,6 +159,12 @@ export type NavigatorPreparationSession = {
162
159
  appendEntry(customType: string, data: unknown): void;
163
160
  entries(): readonly unknown[];
164
161
  providerFailure?(): NavigatorProviderFailureFact | undefined;
162
+ /**
163
+ * Facts of a nested session that already settled without an accepted receipt.
164
+ * Present means its own delivery budget is spent — another prompt would open an
165
+ * independent summon rather than press the session that owes the receipt.
166
+ */
167
+ noReceipt?(): NoReceiptLifecycleFacts | undefined;
165
168
  setModel?(model: string, thinkingLevel?: string): Promise<void>;
166
169
  getThinkingLevel?(): string | undefined;
167
170
  recordPointer(): string;
@@ -204,6 +207,7 @@ export async function writeNavigatorModelSetting(model: string, path = navigator
204
207
  export function parseNavigatorModelSetting(value: string): {
205
208
  provider: string;
206
209
  model: string;
210
+ /** Host thinking passthrough — omit when bare provider/model (#675 ⑥). */
207
211
  thinkingLevel?: string;
208
212
  } {
209
213
  const slash = value.indexOf("/");
@@ -216,68 +220,71 @@ export function parseNavigatorModelSetting(value: string): {
216
220
  const suffix = colon < 0 ? undefined : modelWithThinking.slice(colon + 1);
217
221
  const model = colon < 0 ? modelWithThinking : modelWithThinking.slice(0, colon);
218
222
  if (model === "") throw new Error("Navigator model setting must include a model");
219
- // Suffix is opaque pass-through; bare provider/model omits thinkingLevel (Pi default).
220
- return suffix === undefined
221
- ? { provider, model }
222
- : { provider, model, thinkingLevel: suffix };
223
+ if (suffix !== undefined && suffix.trim() === "") {
224
+ throw new Error("Navigator model setting thinking suffix must be non-blank");
225
+ }
226
+ return {
227
+ provider,
228
+ model,
229
+ ...(suffix === undefined ? {} : { thinkingLevel: suffix }),
230
+ };
223
231
  }
224
232
 
225
233
  /**
226
- * Prefer institutional-resolution navigator seat; fall back to model file only when
227
- * the page is missing or lacks the seat (typed reasons). Corrupted pages fail loud.
234
+ * Navigator model selection from the public seat table only (#675 / #617 DK-3).
235
+ * No legacy navigator-model.json fallback — missing seat is a real model failure.
236
+ * Host/engine axes ride the shared public summons path; attendance open consumes
237
+ * model/thinking here and defers host selection to the shared envelope when present.
228
238
  */
229
239
  export async function resolveNavigatorSeatSelection(
230
240
  context: HostContext,
231
- modelSettingPath: string | undefined,
232
- defaultModelSettingPath: string,
233
241
  ): Promise<{ selection: HostInstitutionalModelSelection; configuredLabel: string; thinkingLevel?: string }> {
234
- const runDirectory = auditorRunDirectory(context);
235
- if (runDirectory !== undefined) {
236
- try {
237
- const selection = await readInstitutionalSeatSelection(runDirectory, "navigator");
242
+ try {
243
+ const { loadPublicCliConfig } = await import("./public-cli/config.ts");
244
+ const { seatModelOnly } = await import("./public-cli/registry.ts");
245
+ const { packageMachineHome } = await import("./activation-ledger-topology.ts");
246
+ const homeCandidate = (context as unknown as { home?: unknown }).home;
247
+ const envHome = process.env.HOME;
248
+ // Prefer explicit context home, then process HOME (hermetic tests), never
249
+ // jump straight to the real machine home when a temp HOME is armed.
250
+ const home =
251
+ typeof homeCandidate === "string"
252
+ ? homeCandidate
253
+ : typeof envHome === "string" && envHome.trim() !== ""
254
+ ? envHome
255
+ : packageMachineHome();
256
+ const config = await loadPublicCliConfig(home);
257
+ const modelOnly = seatModelOnly(config.seats.navigator);
258
+ // Seat table is the only model authority — no legacy navigator-model.json.
259
+ // Bare missing seat uses the package default constant (not a file path).
260
+ if (modelOnly === undefined) {
261
+ const parsed = parseNavigatorModelSetting(NAVIGATOR_DEFAULT_MODEL);
238
262
  return {
239
263
  selection: {
240
- provider: selection.provider,
241
- model: selection.model,
242
- ...(selection.thinking === undefined ? {} : { thinking: selection.thinking }),
264
+ provider: parsed.provider,
265
+ model: parsed.model,
266
+ ...(parsed.thinkingLevel === undefined ? {} : { thinking: parsed.thinkingLevel }),
243
267
  },
244
- configuredLabel: selection.thinking === undefined
245
- ? `${selection.provider}/${selection.model}`
246
- : `${selection.provider}/${selection.model}:${selection.thinking}`,
247
- ...(selection.thinking === undefined ? {} : { thinkingLevel: selection.thinking }),
268
+ configuredLabel: NAVIGATOR_DEFAULT_MODEL,
269
+ ...(parsed.thinkingLevel === undefined ? {} : { thinkingLevel: parsed.thinkingLevel }),
248
270
  };
249
- } catch (error) {
250
- if (
251
- error instanceof InstitutionalResolutionError
252
- && (error.reason === "missing-page" || error.reason === "missing-seat")
253
- ) {
254
- // documented bare-seam fallback
255
- } else {
256
- throw error instanceof NavigatorUnavailableError
257
- ? error
258
- : navigatorUnavailableError("model", error);
259
- }
260
271
  }
261
- }
262
- let configured: string;
263
- try {
264
- configured = await readNavigatorModelSetting(modelSettingPath ?? defaultModelSettingPath);
272
+ // Host params passthrough only — no map/validate/default (#675 ⑥).
273
+ const thinkingRaw = modelOnly.thinking;
274
+ const configuredLabel =
275
+ thinkingRaw === undefined
276
+ ? `${modelOnly.provider}/${modelOnly.model}`
277
+ : `${modelOnly.provider}/${modelOnly.model}:${thinkingRaw}`;
278
+ return {
279
+ selection: {
280
+ provider: modelOnly.provider,
281
+ model: modelOnly.model,
282
+ ...(thinkingRaw === undefined ? {} : { thinking: thinkingRaw }),
283
+ },
284
+ configuredLabel,
285
+ ...(thinkingRaw === undefined ? {} : { thinkingLevel: thinkingRaw }),
286
+ };
265
287
  } catch (error) {
266
288
  throw navigatorUnavailableError("model", error);
267
289
  }
268
- let parsed: ReturnType<typeof parseNavigatorModelSetting>;
269
- try {
270
- parsed = parseNavigatorModelSetting(configured);
271
- } catch (error) {
272
- throw navigatorUnavailableError("model", error);
273
- }
274
- return {
275
- selection: {
276
- provider: parsed.provider,
277
- model: parsed.model,
278
- ...(parsed.thinkingLevel === undefined ? {} : { thinking: parsed.thinkingLevel }),
279
- },
280
- configuredLabel: configured,
281
- ...(parsed.thinkingLevel === undefined ? {} : { thinkingLevel: parsed.thinkingLevel }),
282
- };
283
290
  }
@@ -15,8 +15,10 @@ import {
15
15
  import type { NotarySourceRunLocator } from "./notary-contracts.ts";
16
16
  import { readRoleRunIdentity } from "./public-cli/run-lifecycle.ts";
17
17
 
18
+ // Run directory leaf: <runId>@<role>. Production uses uuidv7 runIds; offline tracers
19
+ // may use deterministic non-uuid leaves. Role token stays identifier-shaped.
18
20
  const RUN_DIR_NAME =
19
- /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})@([A-Za-z][A-Za-z0-9_-]*)$/i;
21
+ /^([A-Za-z0-9][A-Za-z0-9._-]*)@([A-Za-z][A-Za-z0-9_-]*)$/;
20
22
 
21
23
  export class NotarySourceRunError extends Error {
22
24
  constructor(message: string, options?: ErrorOptions) {
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Public Auditor (审刑院) terminating receipt contracts (#675).
3
+ * Lawful explicit releases: pass | revise | escalate.
4
+ */
5
+ import { Type } from "typebox";
6
+
7
+ import { openToolObject } from "../open-tool-schema.ts";
8
+ import { withInfrastructureFailureDeclaration } from "./terminating-infrastructure.ts";
9
+
10
+ export const AUDITOR_OUTPUT_TOOL_NAME = "ak_auditor_output" as const;
11
+ export const AUDITOR_ACCEPTED_TEXT = "审刑院回执已接受";
12
+
13
+ export const auditorOutputSchema = withInfrastructureFailureDeclaration(
14
+ openToolObject(
15
+ Type.Object({
16
+ status: Type.Unknown({
17
+ description: "pass | revise | escalate — 形状指引,非 schema 闸",
18
+ }),
19
+ violations: Type.Optional(Type.Unknown({
20
+ description: "status 为 revise 时的违规条目",
21
+ })),
22
+ conflicts: Type.Optional(Type.Unknown({
23
+ description: "status 为 escalate 时的冲突",
24
+ })),
25
+ decisionGate: Type.Optional(Type.Unknown({
26
+ description: "status 为 escalate 时的决策闸",
27
+ })),
28
+ }),
29
+ ),
30
+ );
31
+
32
+ export type AuditorOutput =
33
+ | { readonly status: "pass" }
34
+ | { readonly status: "revise"; readonly violations?: unknown }
35
+ | {
36
+ readonly status: "escalate";
37
+ readonly conflicts?: unknown;
38
+ readonly decisionGate?: unknown;
39
+ };
40
+
41
+ function isRecord(value: unknown): value is Record<string, unknown> {
42
+ return typeof value === "object" && value !== null && !Array.isArray(value);
43
+ }
44
+
45
+ export function projectLawfulAuditorOutput(value: unknown): AuditorOutput | undefined {
46
+ if (!isRecord(value)) return undefined;
47
+ if (value.status === "pass") return { status: "pass" };
48
+ if (value.status === "revise") {
49
+ return {
50
+ status: "revise",
51
+ ...(Object.hasOwn(value, "violations") ? { violations: value.violations } : {}),
52
+ };
53
+ }
54
+ if (value.status === "escalate") {
55
+ return {
56
+ status: "escalate",
57
+ ...(Object.hasOwn(value, "conflicts") ? { conflicts: value.conflicts } : {}),
58
+ ...(Object.hasOwn(value, "decisionGate") ? { decisionGate: value.decisionGate } : {}),
59
+ };
60
+ }
61
+ return undefined;
62
+ }
63
+
64
+ export function validateRecordedAuditorOutput(value: unknown): AuditorOutput {
65
+ const projected = projectLawfulAuditorOutput(value);
66
+ if (projected === undefined) {
67
+ throw new Error("Auditor output has no recognized execution discriminator");
68
+ }
69
+ return projected;
70
+ }
71
+
72
+ export function auditorDecisiveFacts(output: AuditorOutput): Record<string, unknown> {
73
+ const facts: Record<string, unknown> = { status: output.status };
74
+ if (output.status === "revise" && output.violations !== undefined) {
75
+ facts.violations = output.violations;
76
+ }
77
+ if (output.status === "escalate") {
78
+ if (output.conflicts !== undefined) facts.conflicts = output.conflicts;
79
+ if (output.decisionGate !== undefined) facts.decisionGate = output.decisionGate;
80
+ }
81
+ return facts;
82
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Public Evidence-Child terminating receipt contracts (#675).
3
+ * One report body; shape is not an admission gate (ADR 0055).
4
+ */
5
+ import { Type } from "typebox";
6
+
7
+ import { openToolObject } from "../open-tool-schema.ts";
8
+ import { withInfrastructureFailureDeclaration } from "./terminating-infrastructure.ts";
9
+
10
+ export const EVIDENCE_CHILD_OUTPUT_TOOL_NAME = "ak_evidence_child_output" as const;
11
+ export const EVIDENCE_CHILD_ACCEPTED_TEXT = "取证回执已接受";
12
+
13
+ export const evidenceChildOutputSchema = withInfrastructureFailureDeclaration(
14
+ openToolObject(
15
+ Type.Object({
16
+ report: Type.Unknown({
17
+ description: "取证报告正文 — 形状指引,非 schema 闸",
18
+ }),
19
+ }),
20
+ ),
21
+ );
22
+
23
+ /** Report body retained as delivered — no type/blank/field-presence reshape (ADR 0055 / §0). */
24
+ export type EvidenceChildOutput = { readonly report: unknown };
25
+
26
+ function isRecord(value: unknown): value is Record<string, unknown> {
27
+ return typeof value === "object" && value !== null && !Array.isArray(value);
28
+ }
29
+
30
+ export function projectLawfulEvidenceChildOutput(
31
+ value: unknown,
32
+ ): EvidenceChildOutput | undefined {
33
+ // No field-presence gate (ADR 0055 / CLAUDE.md §0 / #675). Retain delivered bytes:
34
+ // report key when present, otherwise the whole object as the report body.
35
+ if (!isRecord(value)) return undefined;
36
+ return { report: Object.hasOwn(value, "report") ? value.report : value };
37
+ }
38
+
39
+ export function validateRecordedEvidenceChildOutput(value: unknown): EvidenceChildOutput {
40
+ const projected = projectLawfulEvidenceChildOutput(value);
41
+ if (projected === undefined) {
42
+ throw new Error("Evidence-child output is not an object");
43
+ }
44
+ return projected;
45
+ }
46
+
47
+ export function evidenceChildDecisiveFacts(
48
+ output: EvidenceChildOutput,
49
+ ): Record<string, unknown> {
50
+ return { report: output.report };
51
+ }
@@ -4,6 +4,7 @@
4
4
  export const JUDGE_OUTPUT_TOOL_NAME = "ak_judge_output";
5
5
  export const JUDGE_ACCEPTED_TEXT = "大理寺回执已接受";
6
6
  export const JUDGE_ACCEPTED_AUDIT_NO_RECEIPT_TEXT = "大理寺回执已接受;审计无回执";
7
+ export const JUDGE_ACCEPTED_AUDIT_UNREADABLE_TEXT = "大理寺回执已接受;审计形状不可读";
7
8
 
8
9
  export type JudgeClass = {
9
10
  name: string;
@@ -10,8 +10,8 @@ export type ReviewerAmendments = Readonly<Partial<Record<"standards" | "spec", s
10
10
  export type ReviewerIntent =
11
11
  | Readonly<{ status: "completed"; amendments?: ReviewerAmendments }>
12
12
  | Readonly<{ status: "refused"; diagnostic: string; amendments?: ReviewerAmendments }>;
13
- /** Child report is plain text — no length/digest identity shell (ADR 0031). */
14
- export type VerbatimChildReport = Readonly<{ text: string }>;
13
+ /** Child report body retained as delivered — no type/blank reshape (ADR 0031 / 0055 / #675). */
14
+ export type VerbatimChildReport = Readonly<{ text: unknown }>;
15
15
  /** Prompt projection on the receipt face is plain text (ADR 0031). */
16
16
  export type ReviewerReceiptPrompt = Readonly<{ text: string }>;
17
17
  /** Canonical Skill content on the receipt face is plain text (ADR 0031/0032). */