@akagilnc/pi-workflow-roles 0.1.4321 → 0.1.4387

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +4 -1
  2. package/README.zh-CN.md +5 -1
  3. package/dist/acp-host/production-host.js +1308 -587
  4. package/dist/doctor-contracts.js +2 -2
  5. package/dist/engine-detour-tool.js +2 -8
  6. package/dist/gatekeeper-role.js +4 -19
  7. package/dist/headless-host/production-host.js +2613 -1892
  8. package/dist/merger-contracts.js +2 -2
  9. package/dist/migrate-book-topology.js +34 -9
  10. package/dist/notary-contracts.js +2 -2
  11. package/dist/package-contracts/auditor-output.js +2 -2
  12. package/dist/package-contracts/fixer-output.js +2 -2
  13. package/dist/package-contracts/gatekeeper-output.js +2 -2
  14. package/dist/package-contracts/navigator-output.js +2 -2
  15. package/dist/package-contracts/terminating-infrastructure.js +7 -2
  16. package/dist/package-contracts/terminating-tools.js +6 -0
  17. package/dist/packaged-role-registry.js +20 -1
  18. package/dist/public-cli/case-dossier-delivery.js +45 -28
  19. package/dist/public-cli/countersign-run.js +439 -0
  20. package/dist/public-cli/invocation.js +10 -0
  21. package/dist/public-cli/main.js +388 -101
  22. package/dist/public-cli/option-definitions.js +14 -0
  23. package/dist/public-cli/post-admission.js +44 -49
  24. package/dist/public-cli/run-lifecycle.js +13 -1
  25. package/dist/public-cli/settlement.js +14 -53
  26. package/dist/public-role-summons.js +14 -0
  27. package/dist/secretariat-contracts.js +8 -0
  28. package/dist/sitian-appender.js +21 -4
  29. package/extensions/role-runtime.ts +1 -0
  30. package/package.json +1 -1
  31. package/scripts/build-package.mjs +1 -0
  32. package/souls/secretariat.md +26 -0
  33. package/souls/ticket-law.md +9 -0
  34. package/src/collector-tool-schemas.ts +2 -2
  35. package/src/countersign-role.ts +2 -2
  36. package/src/diarist-role.ts +2 -2
  37. package/src/doctor-contracts.ts +2 -2
  38. package/src/engine-detour-tool.ts +3 -9
  39. package/src/gatekeeper-pass-envelope.ts +35 -2
  40. package/src/gatekeeper-role.ts +8 -31
  41. package/src/gleaner-left-role.ts +2 -2
  42. package/src/host-contracts.ts +2 -1
  43. package/src/inspector-role.ts +2 -2
  44. package/src/judge-role.ts +2 -2
  45. package/src/merger-contracts.ts +2 -2
  46. package/src/notary-contracts.ts +2 -2
  47. package/src/package-contracts/auditor-output.ts +2 -2
  48. package/src/package-contracts/fixer-output.ts +2 -2
  49. package/src/package-contracts/gatekeeper-output.ts +2 -2
  50. package/src/package-contracts/navigator-output.ts +2 -2
  51. package/src/package-contracts/terminating-infrastructure.ts +13 -3
  52. package/src/package-contracts/terminating-tools.ts +9 -1
  53. package/src/packaged-role-registry.ts +20 -1
  54. package/src/public-cli/case-dossier-delivery.ts +48 -29
  55. package/src/public-cli/cli.ts +10 -0
  56. package/src/public-cli/countersign-run.ts +29 -5
  57. package/src/public-cli/invocation.ts +28 -2
  58. package/src/public-cli/option-definitions.ts +17 -0
  59. package/src/public-cli/post-admission.ts +45 -58
  60. package/src/public-cli/run-lifecycle.ts +29 -2
  61. package/src/public-cli/secretariat-run.ts +266 -0
  62. package/src/public-cli/settlement.ts +31 -55
  63. package/src/public-cli/terminal.ts +2 -1
  64. package/src/public-role-summons.ts +23 -1
  65. package/src/reviewer-role.ts +2 -2
  66. package/src/role-runtime-dependencies.ts +1 -0
  67. package/src/role-runtime.ts +203 -18
  68. package/src/secretariat-contracts.ts +27 -0
  69. package/src/secretariat-role.ts +210 -0
  70. package/src/sitian-appender.ts +36 -3
  71. package/src/worker-role.ts +2 -2
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Public Secretariat (中书省) Role run: admit → 起居郎 typed identity → bind/relocate
3
+ * → shared post-admission (dossier delivery) → settle (#924).
4
+ * Instruction-seat face like Judge; nested countersign is role-tool driven.
5
+ * Ticket identity reuses the court diarist seam (no prose regex; no second lifecycle).
6
+ */
7
+ import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contracts.ts";
8
+ import { engineSessionMaterialFromOptions, pickEngineAxis } from "../package-resources/engine-material.ts";
9
+ import { CliUsageError } from "./cli-errors.ts";
10
+ import {
11
+ admitSecretariatInvocation,
12
+ bindAdmittedTicketNumber,
13
+ buildSecretariatTransportPrompt,
14
+ relocateAdmittedRunToTicket,
15
+ type AdmittedSecretariatInvocation,
16
+ type ParseSecretariatArgvResult,
17
+ } from "./invocation.ts";
18
+ import {
19
+ invokeCourtDiarist,
20
+ type CourtDiaristSummonEnv,
21
+ } from "./countersign-run.ts";
22
+ import {
23
+ presentControlledFailure,
24
+ runPostAdmissionOneShot,
25
+ type PostAdmissionEnv,
26
+ runPostAdmissionSeatResume,
27
+ resumeTurnRequestProjectionOptions,
28
+ } from "./post-admission.ts";
29
+ import {
30
+ loadResumableSecretariatRun,
31
+ markRunAdmitted,
32
+ type PublicResumeRequest,
33
+ } from "./run-lifecycle.ts";
34
+ import {
35
+ presentStructuralRejection,
36
+ trySettleSecretariatTerminalResult,
37
+ } from "./settlement.ts";
38
+ import type { CliIo } from "./cli-io.ts";
39
+ import type { TerminalResult } from "./terminal.ts";
40
+ import {
41
+ projectRoleTurnRequest,
42
+ type RoleTurnRequestProjectionOptions,
43
+ } from "./turn-request.ts";
44
+
45
+ export type SecretariatRunEnv = PostAdmissionEnv & {
46
+ principalAuthority: DurablePrincipalAuthority;
47
+ createRunId?: () => string;
48
+ };
49
+
50
+ /** Project admitted invocation onto the host-neutral turn request. */
51
+ export function buildSecretariatTurnRequest(
52
+ admitted: AdmittedSecretariatInvocation,
53
+ options: RoleTurnRequestProjectionOptions,
54
+ ): RoleTurnRequest {
55
+ return projectRoleTurnRequest(
56
+ admitted,
57
+ {
58
+ activation: {
59
+ role: "secretariat" as const,
60
+ ...(admitted.ticketNumber === undefined
61
+ ? {}
62
+ : { ticketNumber: admitted.ticketNumber }),
63
+ },
64
+ },
65
+ options,
66
+ );
67
+ }
68
+
69
+ function diaristEnv(env: SecretariatRunEnv): CourtDiaristSummonEnv {
70
+ return {
71
+ cwd: env.cwd,
72
+ home: env.home,
73
+ agentDir: env.agentDir,
74
+ packageRoot: env.packageRoot,
75
+ ...(env.credentials === undefined ? {} : { credentials: env.credentials }),
76
+ ...(env.signal === undefined ? {} : { signal: env.signal }),
77
+ ...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
78
+ };
79
+ }
80
+
81
+ export async function runPublicSecretariat(
82
+ argv: readonly string[],
83
+ env: SecretariatRunEnv,
84
+ io: CliIo,
85
+ parseSecretariatArgv: (args: readonly string[]) => ParseSecretariatArgvResult,
86
+ ): Promise<{
87
+ exitCode: number;
88
+ admitted?: AdmittedSecretariatInvocation;
89
+ terminal?: TerminalResult;
90
+ }> {
91
+ let parsed: ParseSecretariatArgvResult;
92
+ try {
93
+ parsed = parseSecretariatArgv(argv);
94
+ } catch (error) {
95
+ if (error instanceof CliUsageError) {
96
+ presentStructuralRejection(error, io);
97
+ return { exitCode: 2 };
98
+ }
99
+ throw error;
100
+ }
101
+
102
+ let admitted: AdmittedSecretariatInvocation;
103
+ try {
104
+ admitted = await admitSecretariatInvocation({
105
+ home: env.home,
106
+ principalAuthority: env.principalAuthority,
107
+ cwd: env.cwd,
108
+ instruction: parsed.instruction,
109
+ attachmentPaths: parsed.attachmentPaths,
110
+ ...(parsed.project === undefined ? {} : { project: parsed.project }),
111
+ ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
112
+ ...(env.model === undefined ? {} : { model: env.model }),
113
+ ...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
114
+ });
115
+ } catch (error) {
116
+ if (error instanceof CliUsageError) {
117
+ presentStructuralRejection(error, io);
118
+ return { exitCode: 2 };
119
+ }
120
+ throw error;
121
+ }
122
+
123
+ await markRunAdmitted(admitted, env.principalAuthority);
124
+
125
+ // #924 G3 / ADR 0075 / 0081: ticket identity is 起居郎 typed assertion — never
126
+ // prose regex. escalate / real station failure → controlled failure; lawful
127
+ // true-unbound continues unbound (ADR 0075 真无票→无录; 第 0 条不拒收);
128
+ // ticket → bind + relocate so post-admission can deliver 起居录.
129
+ const outcome = await invokeCourtDiarist(
130
+ {
131
+ instruction: parsed.instruction,
132
+ projectRoot: admitted.projectRoot,
133
+ failureLabel: "secretariat unbound summons",
134
+ attachmentPaths: admitted.attachments.map((attachment) => attachment.frozenPath),
135
+ correlationId: admitted.runId,
136
+ },
137
+ diaristEnv(env),
138
+ io,
139
+ );
140
+
141
+ if (outcome.identity.kind === "escalate") {
142
+ return await presentControlledFailure(
143
+ admitted,
144
+ {
145
+ timedOut: false,
146
+ code: null,
147
+ stderr: "",
148
+ thrown: new Error(
149
+ "court diarist station escalated (cannot identify court target)",
150
+ ),
151
+ },
152
+ secretariatAdapters(),
153
+ env.principalAuthority,
154
+ io,
155
+ );
156
+ }
157
+
158
+ if (outcome.failedWithoutEscalate !== undefined) {
159
+ return await presentControlledFailure(
160
+ admitted,
161
+ {
162
+ timedOut: false,
163
+ code: null,
164
+ stderr: "",
165
+ thrown: new Error(outcome.failedWithoutEscalate.diagnostic),
166
+ },
167
+ secretariatAdapters(),
168
+ env.principalAuthority,
169
+ io,
170
+ );
171
+ }
172
+
173
+ if (outcome.identity.kind === "ticket") {
174
+ try {
175
+ await bindAdmittedTicketNumber(admitted, outcome.identity.ticketNumber);
176
+ await relocateAdmittedRunToTicket(admitted, env.principalAuthority);
177
+ } catch (error) {
178
+ return await presentControlledFailure(
179
+ admitted,
180
+ {
181
+ timedOut: false,
182
+ code: null,
183
+ stderr: "",
184
+ thrown: error,
185
+ },
186
+ secretariatAdapters(),
187
+ env.principalAuthority,
188
+ io,
189
+ );
190
+ }
191
+ }
192
+
193
+ const turnRequest = buildSecretariatTurnRequest(admitted, {
194
+ packageRoot: env.packageRoot,
195
+ home: env.home,
196
+ agentDir: env.agentDir,
197
+ ...(env.model === undefined ? {} : { model: env.model }),
198
+ ...pickEngineAxis(env),
199
+ ...(env.timeoutMs === undefined ? {} : { timeoutMs: env.timeoutMs }),
200
+ ...(env.correlationId === undefined || env.correlationId.trim() === ""
201
+ ? {}
202
+ : { correlationId: env.correlationId }),
203
+ continuation: {
204
+ kind: "initial",
205
+ prompt: buildSecretariatTransportPrompt(
206
+ admitted,
207
+ engineSessionMaterialFromOptions({
208
+ ...pickEngineAxis(env),
209
+ packageRoot: env.packageRoot,
210
+ }),
211
+ ),
212
+ },
213
+ });
214
+
215
+ return await runPostAdmissionOneShot({
216
+ admitted,
217
+ env,
218
+ io,
219
+ request: turnRequest,
220
+ adapters: secretariatAdapters(),
221
+ ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
222
+ });
223
+ }
224
+
225
+ function secretariatAdapters() {
226
+ return {
227
+ trySettle: (
228
+ admitted: AdmittedSecretariatInvocation,
229
+ authority: DurablePrincipalAuthority,
230
+ scope?: { readonly courtAttemptId?: string },
231
+ ) => trySettleSecretariatTerminalResult(admitted, authority, scope),
232
+ shouldPresentSettled: () => true,
233
+ };
234
+ }
235
+
236
+ /**
237
+ * Resume a previously admitted Secretariat run (#599).
238
+ */
239
+ export async function runPublicSecretariatResume(
240
+ request: PublicResumeRequest,
241
+ env: SecretariatRunEnv,
242
+ io: CliIo,
243
+ ): Promise<{
244
+ exitCode: number;
245
+ admitted?: AdmittedSecretariatInvocation;
246
+ terminal?: TerminalResult;
247
+ }> {
248
+ return await runPostAdmissionSeatResume({
249
+ request,
250
+ env,
251
+ io,
252
+ load: (effective) =>
253
+ loadResumableSecretariatRun(
254
+ env.home,
255
+ effective.runId,
256
+ env.principalAuthority,
257
+ ),
258
+ buildTurnRequest: (admitted, effective) =>
259
+ buildSecretariatTurnRequest(
260
+ admitted,
261
+ resumeTurnRequestProjectionOptions(admitted, effective, env),
262
+ ),
263
+ adapters: secretariatAdapters(),
264
+ ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
265
+ });
266
+ }
@@ -94,6 +94,9 @@ import {
94
94
  import {
95
95
  DIARIST_OUTPUT_TOOL_NAME,
96
96
  } from "../diarist-contracts.ts";
97
+ import {
98
+ SECRETARIAT_OUTPUT_TOOL_NAME,
99
+ } from "../secretariat-contracts.ts";
97
100
  import {
98
101
  INSPECTOR_OUTPUT_TOOL_NAME,
99
102
  } from "../inspector-contracts.ts";
@@ -146,6 +149,7 @@ import {
146
149
  type AdmittedMergerInvocation,
147
150
  type AdmittedCountersignInvocation,
148
151
  type AdmittedDiaristInvocation,
152
+ type AdmittedSecretariatInvocation,
149
153
  type AdmittedGleanerLeftInvocation,
150
154
  type AdmittedInspectorInvocation,
151
155
  type AdmittedGatekeeperInvocation,
@@ -1086,38 +1090,7 @@ async function loadBoundAuditorVolumes(
1086
1090
  }
1087
1091
  const parentId = parentEntries.find((entry) => entry.type === "session")?.id;
1088
1092
  if (parentId === undefined) return undefined;
1089
- const isResumeEnvelopeBytes = (value: unknown): boolean => {
1090
- if (typeof value !== "string") return false;
1091
- if (value.length === 0) return true;
1092
- const nl = value.indexOf("\n");
1093
- const firstLine = nl === -1 ? value : value.slice(0, nl);
1094
- const body = firstLine === "" && nl !== -1 ? value.slice(nl + 1) : value;
1095
- return body.startsWith("本次配置的劳务引擎及其手册:") || body.startsWith("- engine:");
1096
- };
1097
- const isResumeEnvelope = (msg: unknown): boolean => {
1098
- if (!isRecord(msg) || msg.role !== "user") return false;
1099
- const text = typeof msg.text === "string" ? msg.text : typeof (msg as { content?: unknown }).content === "string" ? (msg as { content: string }).content : undefined;
1100
- if (isResumeEnvelopeBytes(text)) return true;
1101
- const content = (msg as { content?: unknown }).content;
1102
- if (Array.isArray(content)) {
1103
- return content.some((p) => isRecord(p) && (isResumeEnvelopeBytes(p.text) || isResumeEnvelopeBytes(p.content)));
1104
- }
1105
- return false;
1106
- };
1107
- let latestParentUserIndex = -1;
1108
- for (let i = parentEntries.length - 1; i >= 0; i -= 1) {
1109
- const entry = parentEntries[i];
1110
- if (entry?.type !== "message" || entry.message?.role !== "user") continue;
1111
- if (isResumeEnvelope(entry.message)) continue;
1112
- latestParentUserIndex = i;
1113
- break;
1114
- }
1115
1093
  const childDirectories = [join(dirname(sessionFile), "auditor-roles")];
1116
- // Auto-resume seam (owner A): stale check must ignore resume envelope and
1117
- // prioritize retention. Previous `attemptEntryIndex < latest` discarded the
1118
- // first attempt's child after resume advanced latest, losing retentionFailure
1119
- // when retry had no compliance entry. Fix: ignore envelope for staleness and
1120
- // prefer any valid compliance failure before falling back to primary.
1121
1094
  const valid: BoundAuditorVolume[] = [];
1122
1095
  let sawAnyDirectory = false;
1123
1096
  for (const childDirectory of childDirectories) {
@@ -1166,10 +1139,6 @@ async function loadBoundAuditorVolumes(
1166
1139
  typeof bindingParent?.attemptEntryId === "string"
1167
1140
  ? bindingParent.attemptEntryId
1168
1141
  : undefined;
1169
- const attemptEntryIndex =
1170
- attemptEntryId === undefined
1171
- ? -1
1172
- : parentEntries.findIndex((entry) => entry.id === attemptEntryId);
1173
1142
  const boundSessionFile =
1174
1143
  typeof bindingParent?.sessionFile === "string"
1175
1144
  ? bindingParent.sessionFile
@@ -1177,12 +1146,7 @@ async function loadBoundAuditorVolumes(
1177
1146
  ? header.parentSession
1178
1147
  : undefined;
1179
1148
  if (boundSessionFile !== sessionFile) continue;
1180
- if (
1181
- bindingParent !== undefined &&
1182
- (bindingParent.sessionId !== parentId || attemptEntryIndex < latestParentUserIndex)
1183
- ) {
1184
- continue;
1185
- }
1149
+ if (bindingParent !== undefined && bindingParent.sessionId !== parentId) continue;
1186
1150
  if (bindingParent === undefined && header.parentSession !== sessionFile) continue;
1187
1151
  valid.push({
1188
1152
  entries: entries.slice(start, end),
@@ -1190,8 +1154,8 @@ async function loadBoundAuditorVolumes(
1190
1154
  sessionFile,
1191
1155
  ...(attemptEntryId === undefined ? {} : { attemptEntryId }),
1192
1156
  });
1193
- // Keep every qualifying interval in the current parent-user range.
1194
- // A single first-match break drops later same-user summons failures (#636).
1157
+ // Keep every interval bound to this parent. Auditor payload is relayed as
1158
+ // recorded; code does not expire it from later user-message shape (#858).
1195
1159
  }
1196
1160
  }
1197
1161
  }
@@ -1254,16 +1218,6 @@ function providerStopFallbackFromAuditorVolumes(
1254
1218
  return undefined;
1255
1219
  }
1256
1220
 
1257
- /** Recover a provider stop from the auditor child bound to the current parent attempt. */
1258
- export async function readBoundAuditorKnownFailure(
1259
- sessionFile: string,
1260
- ): Promise<RoleTurnKnownFailure | undefined> {
1261
- const volumes = await loadBoundAuditorVolumes(sessionFile);
1262
- if (volumes === undefined) return undefined;
1263
- return complianceFailureFromAuditorVolumes(volumes)
1264
- ?? providerStopFallbackFromAuditorVolumes(volumes);
1265
- }
1266
-
1267
1221
  /** Strong auditor tier only — retained compliance-failure entries, no provider-stop fallback. */
1268
1222
  async function readBoundAuditorComplianceFailure(
1269
1223
  sessionFile: string,
@@ -3193,7 +3147,8 @@ type SeatAcceptedSettlementSpec = {
3193
3147
  | "gatekeeper"
3194
3148
  | "navigator"
3195
3149
  | "auditor"
3196
- | "diarist";
3150
+ | "diarist"
3151
+ | "secretariat";
3197
3152
  readonly toolName: string;
3198
3153
  };
3199
3154
 
@@ -3217,7 +3172,8 @@ async function settleLawfulSeatAcceptedTerminalResult(
3217
3172
  | AdmittedGatekeeperInvocation
3218
3173
  | AdmittedNavigatorInvocation
3219
3174
  | import("./invocation.ts").AdmittedAuditorInvocation
3220
- | AdmittedDiaristInvocation,
3175
+ | AdmittedDiaristInvocation
3176
+ | AdmittedSecretariatInvocation,
3221
3177
  authority: DurablePrincipalAuthority,
3222
3178
  spec: SeatAcceptedSettlementSpec,
3223
3179
  scope?: SettlementCourtScope,
@@ -3434,6 +3390,26 @@ export async function trySettleDiaristTerminalResult(
3434
3390
  return settleLawfulDiaristTerminalResult(admitted, authority, scope);
3435
3391
  }
3436
3392
 
3393
+ async function settleLawfulSecretariatTerminalResult(
3394
+ admitted: AdmittedSecretariatInvocation,
3395
+ authority: DurablePrincipalAuthority,
3396
+ scope?: SettlementCourtScope,
3397
+ ): Promise<TerminalResult | undefined> {
3398
+ return settleLawfulSeatAcceptedTerminalResult(admitted, authority, {
3399
+ role: "secretariat",
3400
+ toolName: SECRETARIAT_OUTPUT_TOOL_NAME,
3401
+ }, scope);
3402
+ }
3403
+
3404
+ /** Try to settle a lawful Secretariat Terminal; undefined only for genuine absence. */
3405
+ export async function trySettleSecretariatTerminalResult(
3406
+ admitted: AdmittedSecretariatInvocation,
3407
+ authority: DurablePrincipalAuthority,
3408
+ scope?: SettlementCourtScope,
3409
+ ): Promise<TerminalResult | undefined> {
3410
+ return settleLawfulSecretariatTerminalResult(admitted, authority, scope);
3411
+ }
3412
+
3437
3413
  /** Lawful Inspector accepted outcome (pass/bounce/escalate). */
3438
3414
  export type LawfulInspectorRoleOutcome = Extract<TerminalRoleOutcome, { kind: "accepted" }>;
3439
3415
 
@@ -42,7 +42,8 @@ export type TerminalRoleName =
42
42
  | "gatekeeper"
43
43
  | "navigator"
44
44
  | "auditor"
45
- | "diarist";
45
+ | "diarist"
46
+ | "secretariat";
46
47
 
47
48
  export type NoReceiptTerminalOutcome = NoReceiptLifecycleFacts & {
48
49
  kind: "no_receipt";
@@ -32,7 +32,8 @@ export type PublicSummonRole =
32
32
  | "gatekeeper"
33
33
  | "judge"
34
34
  | "doctor"
35
- | "diarist";
35
+ | "diarist"
36
+ | "countersign";
36
37
 
37
38
  export type PublicSummonRequest = {
38
39
  readonly role: PublicSummonRole;
@@ -82,6 +83,8 @@ export type PublicSummonRequest = {
82
83
  readonly createRunId?: () => string;
83
84
  /** Typed ticket number handoff for diarist child run (#840). */
84
85
  readonly boundTicketNumber?: number;
86
+ /** Caller correlation id for nested leg ledger (ADR 0010 / #924). */
87
+ readonly correlationId?: string;
85
88
  };
86
89
 
87
90
  export type PublicSummonResult = {
@@ -339,6 +342,11 @@ export async function summonPublicRole(
339
342
  env: {
340
343
  ...built.env,
341
344
  stationChild: true,
345
+ // Forward composition-root adapters so nested court stations (e.g.
346
+ // countersign → diarist) select the same faux/production table (#924).
347
+ ...(options.hostAdapters === undefined
348
+ ? {}
349
+ : { hostAdapters: options.hostAdapters }),
342
350
  ...(config.autoResumeLimit === undefined
343
351
  ? {}
344
352
  : { autoResumeLimit: config.autoResumeLimit }),
@@ -350,6 +358,9 @@ export async function summonPublicRole(
350
358
  ...(options.boundTicketNumber === undefined
351
359
  ? {}
352
360
  : { boundTicketNumber: options.boundTicketNumber }),
361
+ ...(options.correlationId === undefined || options.correlationId.trim() === ""
362
+ ? {}
363
+ : { correlationId: options.correlationId }),
353
364
  ...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
354
365
  },
355
366
  };
@@ -481,6 +492,17 @@ export async function summonPublicRole(
481
492
  result = stepped.ok;
482
493
  break;
483
494
  }
495
+ case "countersign": {
496
+ const [{ runPublicCountersign }, { parseCountersignArgv }] = await Promise.all([
497
+ import("./public-cli/countersign-run.ts"),
498
+ import("./public-cli/invocation.ts"),
499
+ ]);
500
+ const stepped = await runPrepared(parseCountersignArgv, (env, once) =>
501
+ runPublicCountersign(options.argv, env as never, io, once));
502
+ if ("fail" in stepped) return stepped.fail;
503
+ result = stepped.ok;
504
+ break;
505
+ }
484
506
  }
485
507
 
486
508
  const stderr = captured?.stderrText();
@@ -1,7 +1,7 @@
1
1
  import type { RoleHost, HostContext, HostToolResult } from "./host-contracts.ts";
2
2
  import { Type } from "typebox";
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
  import type { AnyCanonicalSkillBinding, CanonicalSkillBinding } from "./canonical-skill-binding.ts";
7
7
  export type { CanonicalSkillBinding };
@@ -43,7 +43,7 @@ const reviewerOutputVariants = Type.Union([
43
43
  amendments: Type.Optional(reviewerAmendmentsSchema),
44
44
  }, { additionalProperties: false }),
45
45
  ]);
46
- export const reviewerOutputSchema = withInfrastructureFailureDeclaration(
46
+ export const reviewerOutputSchema = withTerminatingOutputDeclarations(
47
47
  openToolObjectFromUnion(reviewerOutputVariants),
48
48
  );
49
49
  export type ReviewerRoleDependencies = {
@@ -47,6 +47,7 @@ export function createRoleRuntimeDependencies(packageRoot: string): RoleRuntimeD
47
47
  loadCountersignSoul: () => loadMainRoleSessionMaterials("countersign"),
48
48
  loadGleanerLeftSoul: () => loadMainRoleSessionMaterials("gleaner-left"),
49
49
  loadDiaristSoul: () => loadMainRoleSessionMaterials("diarist"),
50
+ loadSecretariatSoul: () => loadMainRoleSessionMaterials("secretariat"),
50
51
  loadNotarySourceRun: loadNotarySourceRunLocator,
51
52
  loadMergerSoul: () => loadMainRoleSessionMaterials("merger"),
52
53
  loadMergerInput: async (path) => JSON.parse(await readFile(path, "utf8")),