@akagilnc/pi-workflow-roles 0.1.2296 → 0.1.2310

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.
@@ -38,6 +38,7 @@ import {
38
38
  parseFixerArgv,
39
39
  parseJudgeArgv,
40
40
  parseMergerArgv,
41
+ parseNotaryArgv,
41
42
  parseReviewerArgv,
42
43
  parseAnalystArgv,
43
44
  } from "./invocation.ts";
@@ -55,6 +56,7 @@ import { runPublicCoder, runPublicCoderResume } from "./coder-run.ts";
55
56
  import { runPublicCollector } from "./collector-run.ts";
56
57
  import { runPublicDoctor } from "./doctor-run.ts";
57
58
  import { runPublicFixer, runPublicFixerResume } from "./fixer-run.ts";
59
+ import { runPublicNotary } from "./notary-run.ts";
58
60
  import { runPublicJudge, runPublicResume } from "./judge-run.ts";
59
61
  import { runPublicMerger, runPublicMergerResume } from "./merger-run.ts";
60
62
  import { runPublicReviewer, runPublicReviewerResume } from "./reviewer-run.ts";
@@ -94,6 +96,7 @@ export const PUBLIC_ROLE_ARGV = {
94
96
  collector: { parse: parseCollectorArgv, options: optionsForOwner("collector") },
95
97
  doctor: { parse: parseDoctorArgv, options: optionsForOwner("doctor") },
96
98
  merger: { parse: parseMergerArgv, options: optionsForOwner("merger") },
99
+ notary: { parse: parseNotaryArgv, options: optionsForOwner("notary") },
97
100
  reviewer: { parse: parseReviewerArgv, options: optionsForOwner("reviewer") },
98
101
  /** Deterministic analysis seat (#336) — argv parse only; no LLM admission. */
99
102
  analyst: { parse: parseAnalystArgv, options: optionsForOwner("analyst") },
@@ -190,6 +193,10 @@ export type CliEnv = {
190
193
  mergerExtraPiArgs?: readonly string[];
191
194
  /** Override Merger role-run timeout (tests). */
192
195
  mergerTimeoutMs?: number;
196
+ /** Extra Pi args for Notary runs (tests: faux provider). */
197
+ notaryExtraPiArgs?: readonly string[];
198
+ /** Override Notary role-run timeout (tests). */
199
+ notaryTimeoutMs?: number;
193
200
  createRunId?: () => string;
194
201
  };
195
202
 
@@ -1203,6 +1210,50 @@ export async function runAkRole(
1203
1210
  };
1204
1211
  }
1205
1212
 
1213
+ // Notary public run path: source-run locator only, direct officer seat (#448).
1214
+ if (parsed.command === "notary") {
1215
+ const agentDir = resolveAgentDir(env, home);
1216
+ const cwd = env.cwd ?? process.cwd();
1217
+ const config = await loadAndValidateConfig(home, env.packageRoot);
1218
+ const credentials =
1219
+ env.credentials ?? (await loadCredentialProviders(agentDir));
1220
+ const seat = resolveEffectiveSeat(
1221
+ config,
1222
+ "notary",
1223
+ credentials,
1224
+ invocationFromParsed(parsed),
1225
+ );
1226
+ const result = await runPublicNotary(
1227
+ parsed.args,
1228
+ {
1229
+ home,
1230
+ agentDir,
1231
+ packageRoot: env.packageRoot,
1232
+ cwd,
1233
+ credentials,
1234
+ ...(env.correlationId === undefined
1235
+ ? {}
1236
+ : { correlationId: env.correlationId }),
1237
+ ...(env.piRunner === undefined ? {} : { piRunner: env.piRunner }),
1238
+ ...(seat.selection === undefined ? {} : { model: seat.selection }),
1239
+ ...projectSeatEngine(seat),
1240
+ ...(env.notaryExtraPiArgs === undefined
1241
+ ? {}
1242
+ : { extraPiArgs: env.notaryExtraPiArgs }),
1243
+ ...(env.notaryTimeoutMs === undefined
1244
+ ? {}
1245
+ : { timeoutMs: env.notaryTimeoutMs }),
1246
+ ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
1247
+ },
1248
+ io,
1249
+ PUBLIC_ROLE_ARGV.notary.parse,
1250
+ );
1251
+ return {
1252
+ exitCode: result.exitCode,
1253
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
1254
+ };
1255
+ }
1256
+
1206
1257
  // Merger public run path: derive active-merge envelope + forced merge-only method (#114).
1207
1258
  if (parsed.command === "merger") {
1208
1259
  const agentDir = resolveAgentDir(env, home);
@@ -1,19 +1,9 @@
1
1
  /**
2
- * Public Doctor Role run: admit Issue → retained case via #78 → explicit
3
- * Internal activate → settle Terminal result (#113). One-shot; no resume path.
4
- * Failure settlement reuses the #107 shared owner.
2
+ * Public Doctor Role run: admit Issue → retained case via #78 → shared one-shot
3
+ * dispatch → settle Terminal result (#113). Lifecycle is the shared
4
+ * Doctor-isomorphic seam; this module keeps only Doctor adapters.
5
5
  */
6
- import { writeFile } from "node:fs/promises";
7
- import { join } from "node:path";
8
-
9
- import { applyEngineChildEnv } from "../engine-detour.ts";
10
6
  import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.ts";
11
- import {
12
- runExplicitInternalActivation,
13
- type ExplicitInternalKnownFailure,
14
- type ExplicitInternalPiRunner,
15
- type ExplicitInternalPiResult,
16
- } from "./explicit-internal.ts";
17
7
  import { CliUsageError } from "./cli-errors.ts";
18
8
  import {
19
9
  admitDoctorInvocation,
@@ -23,59 +13,28 @@ import {
23
13
  } from "./invocation.ts";
24
14
  import {
25
15
  buildSeatModelCliArgs,
26
- type CredentialProviders,
27
16
  type SeatModelConfig,
28
17
  } from "./config.ts";
29
18
  import {
30
- missingCredentialPreDispatchFailure,
31
- postRunMissingCredentialFailure,
32
- } from "./public-run-credentials.ts";
19
+ runAdmittedOneShotRole,
20
+ type OneShotRunEnv,
21
+ } from "./one-shot-dispatch.ts";
33
22
  import {
34
- acquireRunWriterLease,
35
- clearTypedProviderHttpObservation,
36
- markRunAdmitted,
37
- markRunRunning,
38
- markRunTerminal,
39
- RunWriterLeaseHeldError,
40
- type RunWriterLease,
41
- } from "./run-lifecycle.ts";
42
- import {
43
- classifyPostAdmissionFailure,
44
- exitCodeForTerminalOutcome,
45
- formatTerminalResult,
46
- inspectJudgeSession,
47
- isLawfulTypedTerminalOutcome,
48
- presentFailureTerminal,
49
23
  presentStructuralRejection,
50
- resolveAuditedRunnerKnownFailure,
51
- explicitInternalKnownFailureClassificationInput,
52
- settleFailureTerminalResult,
53
- trySettleDoctorTerminalResult,
54
24
  trySettleComplianceAuditIncompleteTerminalResult,
25
+ trySettleDoctorTerminalResult,
55
26
  } from "./settlement.ts";
56
27
  import type { CliIo } from "./cli-io.ts";
57
- import type {
58
- ControlledFailureCause,
59
- TerminalResult,
28
+ import {
29
+ isLawfulTypedTerminalOutcome,
30
+ type TerminalResult,
60
31
  } from "./terminal.ts";
61
32
 
62
- export type DoctorRunEnv = {
63
- home: string;
64
- agentDir: string;
65
- packageRoot: string;
66
- cwd: string;
67
- correlationId?: string;
68
- piRunner?: ExplicitInternalPiRunner;
69
- model?: SeatModelConfig;
70
- /** Optional labor engine name (config→activation; session material + env signal). */
71
- engine?: string;
72
- credentials?: CredentialProviders;
33
+ export type DoctorRunEnv = OneShotRunEnv & {
73
34
  createRunId?: () => string;
74
35
  extraPiArgs?: readonly string[];
75
- timeoutMs?: number;
76
36
  };
77
37
 
78
-
79
38
  /**
80
39
  * Build Internal activation extra-args for an admitted Doctor run.
81
40
  * Always --no-skills (Doctor forbids every Skill). Session under #78 book.
@@ -115,189 +74,6 @@ export function buildDoctorActivationExtraArgs(
115
74
  ];
116
75
  }
117
76
 
118
- async function presentControlledFailure(
119
- admitted: AdmittedDoctorInvocation,
120
- failureInput: {
121
- timedOut: boolean;
122
- code: number | null;
123
- stderr: string;
124
- thrown?: unknown;
125
- knownFailure?: ExplicitInternalKnownFailure;
126
- },
127
- io: CliIo,
128
- ): Promise<{
129
- exitCode: number;
130
- admitted: AdmittedDoctorInvocation;
131
- terminal: TerminalResult;
132
- }> {
133
- const hasThrown = Object.hasOwn(failureInput, "thrown");
134
- const session =
135
- !hasThrown &&
136
- !failureInput.timedOut &&
137
- failureInput.knownFailure === undefined
138
- ? await inspectJudgeSession(admitted.sessionFile)
139
- : undefined;
140
- const failure = classifyPostAdmissionFailure({
141
- timedOut: failureInput.timedOut,
142
- code: failureInput.code,
143
- stderr: failureInput.stderr,
144
- ...(hasThrown ? { thrown: failureInput.thrown } : {}),
145
- ...explicitInternalKnownFailureClassificationInput(failureInput.knownFailure),
146
- ...(session === undefined ? {} : { session }),
147
- });
148
-
149
- // Doctor does not support resume — always terminal.
150
- await markRunTerminal(admitted.runDirectory).catch(() => undefined);
151
-
152
- const terminal = await settleFailureTerminalResult(admitted, failure);
153
- presentFailureTerminal(terminal, io);
154
- return {
155
- exitCode: exitCodeForTerminalOutcome(terminal.roleOutcome),
156
- admitted,
157
- terminal,
158
- };
159
- }
160
-
161
- async function dispatchAdmittedDoctor(input: {
162
- admitted: AdmittedDoctorInvocation;
163
- env: DoctorRunEnv;
164
- io: CliIo;
165
- extraArgs: string[];
166
- lease: RunWriterLease;
167
- effectiveEngine?: string;
168
- }): Promise<{
169
- exitCode: number;
170
- admitted: AdmittedDoctorInvocation;
171
- terminal?: TerminalResult;
172
- }> {
173
- const { admitted, env, io, extraArgs, lease, effectiveEngine } = input;
174
- try {
175
- const missingCredential = missingCredentialPreDispatchFailure(
176
- env.model,
177
- env.credentials,
178
- );
179
- if (missingCredential !== undefined) {
180
- return await presentControlledFailure(
181
- admitted,
182
- missingCredential,
183
- io,
184
- );
185
- }
186
- await markRunRunning(admitted.runDirectory, env.model, effectiveEngine);
187
- await clearTypedProviderHttpObservation(admitted.runDirectory);
188
-
189
- const childEnv: NodeJS.ProcessEnv = {
190
- ...process.env,
191
- HOME: env.home,
192
- PI_CODING_AGENT_DIR: env.agentDir,
193
- AK_ROLE_RUN_DIR: admitted.runDirectory,
194
- };
195
- applyEngineChildEnv(childEnv, env.engine);
196
- if (env.correlationId !== undefined && env.correlationId.trim() !== "") {
197
- childEnv.AK_CORRELATION_ID = env.correlationId;
198
- }
199
-
200
- let result: ExplicitInternalPiResult;
201
- try {
202
- result = await runExplicitInternalActivation({
203
- packageRoot: env.packageRoot,
204
- extraArgs,
205
- cwd: admitted.projectRoot,
206
- home: env.home,
207
- agentDir: env.agentDir,
208
- env: childEnv,
209
- timeoutMs: env.timeoutMs,
210
- ...(env.piRunner === undefined ? {} : { runner: env.piRunner }),
211
- });
212
- } catch (error) {
213
- return await presentControlledFailure(
214
- admitted,
215
- {
216
- timedOut: false,
217
- code: null,
218
- stderr: "",
219
- thrown: error,
220
- },
221
- io,
222
- );
223
- }
224
-
225
- try {
226
- await writeFile(
227
- join(admitted.runDirectory, "stderr.log"),
228
- result.stderr,
229
- "utf8",
230
- );
231
- } catch {
232
- // continue to lawful / controlled-failure settlement
233
- }
234
-
235
- let lawful: TerminalResult | undefined;
236
- try {
237
- lawful = await trySettleDoctorTerminalResult(admitted);
238
- } catch (error) {
239
- return await presentControlledFailure(
240
- admitted,
241
- {
242
- timedOut: false,
243
- code: result.code,
244
- stderr: result.stderr,
245
- thrown: error,
246
- },
247
- io,
248
- );
249
- }
250
- if (lawful !== undefined && isLawfulTypedTerminalOutcome(lawful.roleOutcome)) {
251
- await markRunTerminal(admitted.runDirectory).catch(() => undefined);
252
- io.stdout(formatTerminalResult(lawful));
253
- return {
254
- exitCode: exitCodeForTerminalOutcome(lawful.roleOutcome),
255
- admitted,
256
- terminal: lawful,
257
- };
258
- }
259
-
260
- const auditIncomplete = await trySettleComplianceAuditIncompleteTerminalResult(admitted);
261
- if (auditIncomplete !== undefined) {
262
- await markRunTerminal(admitted.runDirectory).catch(() => undefined);
263
- if (auditIncomplete.roleOutcome.kind === "failure") {
264
- presentFailureTerminal(auditIncomplete, io);
265
- } else {
266
- io.stdout(formatTerminalResult(auditIncomplete));
267
- }
268
- return {
269
- exitCode: exitCodeForTerminalOutcome(auditIncomplete.roleOutcome),
270
- admitted,
271
- terminal: auditIncomplete,
272
- };
273
- }
274
-
275
- const credentialFailure = postRunMissingCredentialFailure(
276
- result,
277
- env.model,
278
- env.credentials,
279
- );
280
- const knownFailure = await resolveAuditedRunnerKnownFailure({
281
- runner: result.knownFailure,
282
- sessionFile: admitted.sessionFile,
283
- credential: credentialFailure,
284
- runDirectory: admitted.runDirectory,
285
- });
286
- return await presentControlledFailure(
287
- admitted,
288
- {
289
- timedOut: result.timedOut,
290
- code: result.code,
291
- stderr: result.stderr,
292
- ...(knownFailure === undefined ? {} : { knownFailure }),
293
- },
294
- io,
295
- );
296
- } finally {
297
- await lease.release();
298
- }
299
- }
300
-
301
77
  export async function runPublicDoctor(
302
78
  argv: readonly string[],
303
79
  env: DoctorRunEnv,
@@ -330,19 +106,6 @@ export async function runPublicDoctor(
330
106
  throw error;
331
107
  }
332
108
 
333
- await markRunAdmitted(admitted);
334
-
335
- let lease: RunWriterLease;
336
- try {
337
- lease = await acquireRunWriterLease(admitted.runDirectory, (diagnostic) => io.stderr(diagnostic));
338
- } catch (error) {
339
- if (error instanceof RunWriterLeaseHeldError) {
340
- presentStructuralRejection(error, io);
341
- return { exitCode: 2 };
342
- }
343
- throw error;
344
- }
345
-
346
109
  const extraArgs = buildDoctorActivationExtraArgs(admitted, {
347
110
  packageRoot: env.packageRoot,
348
111
  ...(env.model === undefined ? {} : { model: env.model }),
@@ -350,12 +113,17 @@ export async function runPublicDoctor(
350
113
  ...(env.extraPiArgs === undefined ? {} : { extraPiArgs: env.extraPiArgs }),
351
114
  });
352
115
 
353
- return await dispatchAdmittedDoctor({
116
+ return await runAdmittedOneShotRole({
354
117
  admitted,
355
118
  env,
356
119
  io,
357
120
  extraArgs,
358
- lease,
121
+ adapters: {
122
+ trySettle: trySettleDoctorTerminalResult,
123
+ shouldPresentSettled: (terminal) =>
124
+ isLawfulTypedTerminalOutcome(terminal.roleOutcome),
125
+ trySettleSecondary: trySettleComplianceAuditIncompleteTerminalResult,
126
+ },
359
127
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
360
128
  });
361
129
  }
@@ -47,6 +47,14 @@ import {
47
47
  } from "../merger-contracts.ts";
48
48
  import { sha256Hex } from "../sha256.ts";
49
49
  import { uuidv7 } from "../uuidv7.ts";
50
+ import {
51
+ NOTARY_FIXED_KICKOFF,
52
+ type NotarySourceRunLocator,
53
+ } from "../notary-contracts.ts";
54
+ import {
55
+ NotarySourceRunError,
56
+ resolveNotarySourceRunLocator,
57
+ } from "../notary-source-run.ts";
50
58
  import {
51
59
  appendEngineSessionMaterial,
52
60
  type EngineSessionMaterial,
@@ -143,6 +151,14 @@ export type AdmittedDoctorInvocation = AdmittedRoleInvocationBase & {
143
151
  readonly caseIdentity: DoctorCaseIdentity;
144
152
  };
145
153
 
154
+ export type AdmittedNotaryInvocation = AdmittedRoleInvocationBase & {
155
+ readonly role: "notary";
156
+ /** Absolute source run directory passed to internal --ak-notary-source-run. */
157
+ readonly sourceRunPath: string;
158
+ /** Typed locator identity bound at admission (self-fetch target). */
159
+ readonly sourceRun: NotarySourceRunLocator;
160
+ };
161
+
146
162
  export type AdmittedReviewerInvocation = AdmittedRoleInvocationBase & {
147
163
  readonly role: "reviewer";
148
164
  /** Required fixed base revision for the pinned review target (ADR 0037). */
@@ -177,6 +193,7 @@ export type AdmittedRoleInvocation =
177
193
  | AdmittedFixerInvocation
178
194
  | AdmittedCollectorInvocation
179
195
  | AdmittedDoctorInvocation
196
+ | AdmittedNotaryInvocation
180
197
  | AdmittedReviewerInvocation
181
198
  | AdmittedMergerInvocation;
182
199
 
@@ -1742,6 +1759,159 @@ export function buildDoctorTransportPrompt(
1742
1759
  return appendEngineSessionMaterial(lines, engineMaterial).join("\n");
1743
1760
  }
1744
1761
 
1762
+ export type ParseNotaryArgvResult = {
1763
+ readonly sourceRun: string;
1764
+ readonly project?: string;
1765
+ };
1766
+
1767
+ /**
1768
+ * Parse Notary-specific argv after the `notary` token.
1769
+ * Input contract = zero prompt, zero attachment projection (#448 / #276).
1770
+ */
1771
+ export function parseNotaryArgv(args: readonly string[]): ParseNotaryArgvResult {
1772
+ let project: string | undefined;
1773
+ let sourceRun: string | undefined;
1774
+ const tokens = [...args];
1775
+ const definitions = roleOptions("notary");
1776
+ const options = createTypedOptionConsumer(definitions);
1777
+
1778
+ while (tokens.length > 0) {
1779
+ if (tokens[0] === "--") {
1780
+ tokens.shift();
1781
+ if (tokens.length > 0) {
1782
+ throw new CliUsageError(
1783
+ "notary rejects caller prompt/instruction; only --source-run locator is admitted",
1784
+ );
1785
+ }
1786
+ break;
1787
+ }
1788
+ const taken = options.takeDashed(tokens);
1789
+ if (taken !== undefined) {
1790
+ if (taken.def.id === "project") {
1791
+ project = requireOptionPath(taken.def.canonical, taken.value);
1792
+ continue;
1793
+ }
1794
+ if (taken.def.id === "source-run") {
1795
+ if (taken.value === undefined || taken.value.trim() === "") {
1796
+ throw new CliUsageError("notary --source-run requires a run locator");
1797
+ }
1798
+ sourceRun = taken.value;
1799
+ continue;
1800
+ }
1801
+ throw new CliUsageError(`unknown notary option: ${taken.def.canonical}`);
1802
+ }
1803
+ const token = tokens.shift()!;
1804
+ if (token.startsWith("-") && token !== "-") {
1805
+ throw new CliUsageError(`unknown notary option: ${token}`);
1806
+ }
1807
+ throw new CliUsageError(
1808
+ "notary rejects caller prompt/instruction; only --source-run locator is admitted",
1809
+ );
1810
+ }
1811
+
1812
+ options.assertRequired();
1813
+ if (sourceRun === undefined || sourceRun.trim() === "") {
1814
+ throw new CliUsageError("notary --source-run requires a run locator");
1815
+ }
1816
+ return {
1817
+ sourceRun,
1818
+ ...(project === undefined ? {} : { project }),
1819
+ };
1820
+ }
1821
+
1822
+ export async function admitNotaryInvocation(options: {
1823
+ readonly home: string;
1824
+ readonly cwd: string;
1825
+ readonly sourceRun: string;
1826
+ readonly project?: string;
1827
+ readonly runs?: string;
1828
+ readonly createRunId?: () => string;
1829
+ readonly model?: InvocationEffectiveModel;
1830
+ readonly correlationId?: string;
1831
+ }): Promise<AdmittedNotaryInvocation> {
1832
+ if (options.project !== undefined) {
1833
+ requireOptionPath("--project", options.project);
1834
+ }
1835
+ const projectRoot = resolve(options.project ?? options.cwd);
1836
+ let sourceRun: NotarySourceRunLocator;
1837
+ try {
1838
+ sourceRun = await resolveNotarySourceRunLocator({
1839
+ projectRoot,
1840
+ sourceRun: options.sourceRun,
1841
+ home: options.home,
1842
+ });
1843
+ } catch (error) {
1844
+ if (error instanceof NotarySourceRunError) {
1845
+ throw new CliUsageError(error.message, { cause: error });
1846
+ }
1847
+ throw error;
1848
+ }
1849
+
1850
+ const runId = options.createRunId?.() ?? uuidv7();
1851
+ const { ledgerHome, bookKey, runDirectory, sessionDirectory, sessionFile } =
1852
+ roleRunSessionCoordinates({
1853
+ cwd: projectRoot,
1854
+ runId,
1855
+ role: "notary",
1856
+ home: options.home,
1857
+ });
1858
+ ensureRealDirectoryTree(ledgerHome, sessionDirectory);
1859
+
1860
+ const admitted = {
1861
+ role: "notary" as const,
1862
+ runId,
1863
+ bookKey,
1864
+ projectRoot,
1865
+ runDirectory,
1866
+ sessionDirectory,
1867
+ sessionFile,
1868
+ instruction: "",
1869
+ instructionEmpty: true,
1870
+ attachments: [] as const,
1871
+ sourceRunPath: sourceRun.runDirectory,
1872
+ sourceRun,
1873
+ ...(options.correlationId === undefined
1874
+ ? {}
1875
+ : { correlationId: options.correlationId }),
1876
+ };
1877
+ const admittedRequestPath = join(runDirectory, "admitted-request.json");
1878
+ await writeFile(
1879
+ admittedRequestPath,
1880
+ `${JSON.stringify(admitted, null, 2)}\n`,
1881
+ "utf8",
1882
+ );
1883
+ await writeRoleInvocationLedger(admitted, admitted.role, options.model);
1884
+
1885
+ return {
1886
+ role: "notary",
1887
+ runId,
1888
+ bookKey,
1889
+ projectRoot,
1890
+ instruction: "",
1891
+ instructionEmpty: true,
1892
+ attachments: [],
1893
+ runDirectory,
1894
+ sessionDirectory,
1895
+ sessionFile,
1896
+ admittedRequestPath,
1897
+ sourceRunPath: sourceRun.runDirectory,
1898
+ sourceRun,
1899
+ ...(options.correlationId === undefined
1900
+ ? {}
1901
+ : { correlationId: options.correlationId }),
1902
+ };
1903
+ }
1904
+
1905
+ /** Package-owned fixed kickoff only — never caller instruction/attachments. */
1906
+ export function buildNotaryTransportPrompt(
1907
+ _admitted: AdmittedNotaryInvocation,
1908
+ engineMaterial?: EngineSessionMaterial,
1909
+ ): string {
1910
+ return appendEngineSessionMaterial([NOTARY_FIXED_KICKOFF], engineMaterial).join(
1911
+ "\n",
1912
+ );
1913
+ }
1914
+
1745
1915
  /**
1746
1916
  * Parse Reviewer-specific argv after the `reviewer` token.
1747
1917
  * Public flags: --project, required --base, optional repeatable --authority-ref.