@akagilnc/pi-workflow-roles 0.1.1893 → 0.1.1941

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.
@@ -211,8 +211,9 @@ async function dispatchAdmittedCollector(input: {
211
211
  PI_CODING_AGENT_DIR: env.agentDir,
212
212
  AK_ROLE_RUN_DIR: admitted.runDirectory,
213
213
  };
214
- if (env.correlationId !== undefined && env.correlationId.trim() !== "") {
215
- childEnv.AK_CORRELATION_ID = env.correlationId;
214
+ const correlationId = admitted.correlationId ?? env.correlationId;
215
+ if (correlationId !== undefined && correlationId.trim() !== "") {
216
+ childEnv.AK_CORRELATION_ID = correlationId;
216
217
  }
217
218
 
218
219
  let result: ExplicitInternalPiResult;
@@ -368,7 +369,10 @@ export async function runPublicCollector(
368
369
 
369
370
  return await dispatchAdmittedCollector({
370
371
  admitted,
371
- env,
372
+ env: {
373
+ ...env,
374
+ ...(admitted.correlationId === undefined ? {} : { correlationId: admitted.correlationId }),
375
+ },
372
376
  io,
373
377
  extraArgs,
374
378
  lease,
@@ -312,8 +312,9 @@ async function dispatchAdmittedFixer(input: {
312
312
  PI_CODING_AGENT_DIR: env.agentDir,
313
313
  AK_ROLE_RUN_DIR: admitted.runDirectory,
314
314
  };
315
- if (env.correlationId !== undefined && env.correlationId.trim() !== "") {
316
- childEnv.AK_CORRELATION_ID = env.correlationId;
315
+ const correlationId = admitted.correlationId ?? env.correlationId;
316
+ if (correlationId !== undefined && correlationId.trim() !== "") {
317
+ childEnv.AK_CORRELATION_ID = correlationId;
317
318
  }
318
319
 
319
320
  let result: ExplicitInternalPiResult;
@@ -507,7 +508,10 @@ export async function runPublicFixer(
507
508
 
508
509
  return await dispatchAdmittedFixer({
509
510
  admitted,
510
- env,
511
+ env: {
512
+ ...env,
513
+ ...(admitted.correlationId === undefined ? {} : { correlationId: admitted.correlationId }),
514
+ },
511
515
  io,
512
516
  extraArgs,
513
517
  lease,
@@ -593,7 +597,10 @@ export async function runPublicFixerResume(
593
597
 
594
598
  return await dispatchAdmittedFixer({
595
599
  admitted,
596
- env,
600
+ env: {
601
+ ...env,
602
+ ...(admitted.correlationId === undefined ? {} : { correlationId: admitted.correlationId }),
603
+ },
597
604
  io,
598
605
  extraArgs,
599
606
  lease,
@@ -20,6 +20,7 @@ import {
20
20
  } from "../activation-ledger-topology.ts";
21
21
  import { resolveBookKeyFromGit } from "../activation-ledger-git.ts";
22
22
  import { roleRunSessionCoordinates } from "../sitian-role-run-coordinates.ts";
23
+ import { resolveTicketNumberFromAttachmentBodies } from "../ticket-frontmatter.ts";
23
24
  import {
24
25
  loadDoctorCase,
25
26
  } from "../doctor-evidence.ts";
@@ -73,6 +74,16 @@ export type AdmittedRoleInvocationBase = {
73
74
  /** Exact Pi session file principal (bound at admission; reopened on resume). */
74
75
  readonly sessionFile: string;
75
76
  readonly admittedRequestPath: string;
77
+ /**
78
+ * Optional opaque invocation correlation restored from a prior admitted page
79
+ * (ADR 0049 host channel). Admission does not mint ticket-binding ids.
80
+ */
81
+ readonly correlationId?: string;
82
+ /**
83
+ * Typed ticket face from frozen attachment frontmatter (`ticketNumber`).
84
+ * Absent when no attachment carries a valid contract field (unbound).
85
+ */
86
+ readonly ticketNumber?: number;
76
87
  };
77
88
 
78
89
  export type AdmittedJudgeInvocation = AdmittedRoleInvocationBase & {
@@ -123,6 +134,11 @@ export type AdmittedReviewerInvocation = AdmittedRoleInvocationBase & {
123
134
  readonly role: "reviewer";
124
135
  /** Required fixed base revision for the pinned review target (ADR 0037). */
125
136
  readonly baseRevision: string;
137
+ /**
138
+ * Optional durable authority references/URLs frozen at admission.
139
+ * Spec evidence-child material only — never Standards, never invocation prose promotion.
140
+ */
141
+ readonly authorityRefs: readonly string[];
126
142
  };
127
143
 
128
144
  /** Mechanical envelope derived from the active ordinary two-parent merge. */
@@ -153,7 +169,7 @@ export type AdmittedRoleInvocation =
153
169
 
154
170
  type RoleInvocationLedgerSource = Pick<
155
171
  AdmittedRoleInvocationBase,
156
- "runId" | "bookKey" | "projectRoot" | "runDirectory" | "sessionDirectory" | "sessionFile"
172
+ "runId" | "bookKey" | "projectRoot" | "runDirectory" | "sessionDirectory" | "sessionFile" | "correlationId" | "ticketNumber"
157
173
  >;
158
174
 
159
175
  /**
@@ -173,6 +189,8 @@ async function writeRoleInvocationLedger(
173
189
  runDirectory: source.runDirectory,
174
190
  sessionDirectory: source.sessionDirectory,
175
191
  sessionFile: source.sessionFile,
192
+ ...(source.correlationId === undefined ? {} : { correlationId: source.correlationId }),
193
+ ...(source.ticketNumber === undefined ? {} : { ticketNumber: source.ticketNumber }),
176
194
  };
177
195
  await writeFile(
178
196
  join(source.runDirectory, "invocation.json"),
@@ -305,6 +323,8 @@ export type ParseReviewerArgvResult = {
305
323
  attachmentPaths: string[];
306
324
  /** Required fixed base revision for the pinned review target. */
307
325
  baseRevision: string;
326
+ /** Repeatable durable authority references/URLs (exact order preserved). */
327
+ authorityRefs: string[];
308
328
  project?: string;
309
329
  };
310
330
 
@@ -340,6 +360,25 @@ function requireOptionPath(
340
360
  return value;
341
361
  }
342
362
 
363
+ /**
364
+ * Public --authority-ref admission grammar (refs-only).
365
+ * Unique owner for fresh argv and durable resume restore — no string-only parallel.
366
+ * Accepts durable reference tokens as-is; rejects blank and inline Spec prose
367
+ * (whitespace-bearing sentences). Does not fetch, normalize, or judge content.
368
+ */
369
+ export function requireAuthorityRef(value: string | undefined): string {
370
+ if (value === undefined || value.trim() === "") {
371
+ throw new CliUsageError("--authority-ref requires a nonempty durable reference");
372
+ }
373
+ // Spec prose sentences contain whitespace; durable public refs are single tokens.
374
+ if (/\s/.test(value)) {
375
+ throw new CliUsageError(
376
+ "--authority-ref requires a durable reference, not inline Spec prose",
377
+ );
378
+ }
379
+ return value;
380
+ }
381
+
343
382
  /**
344
383
  * Parse Judge-specific argv after the `judge` token.
345
384
  * Rejects any public burden selector/hint and unknown flags.
@@ -525,7 +564,7 @@ async function freezeRegularFileAttachment(
525
564
  sourcePath: string,
526
565
  destinationDir: string,
527
566
  index: number,
528
- ): Promise<FrozenAttachment> {
567
+ ): Promise<{ attachment: FrozenAttachment; body: Buffer }> {
529
568
  const absolute = isAbsolute(sourcePath) ? sourcePath : resolve(sourcePath);
530
569
  let st;
531
570
  try {
@@ -546,14 +585,49 @@ async function freezeRegularFileAttachment(
546
585
  const frozenPath = join(destinationDir, name);
547
586
  await writeFile(frozenPath, bytes);
548
587
  return {
549
- provenancePath: absolute,
550
- frozenPath,
551
- byteLength: bytes.byteLength,
552
- sha256: sha256Hex(bytes),
553
- mediaKind: "regular-file",
588
+ attachment: {
589
+ provenancePath: absolute,
590
+ frozenPath,
591
+ byteLength: bytes.byteLength,
592
+ sha256: sha256Hex(bytes),
593
+ mediaKind: "regular-file",
594
+ },
595
+ body: bytes,
554
596
  };
555
597
  }
556
598
 
599
+ /** Freeze attachments and resolve typed ticketNumber from frozen bodies (bind-if-present). */
600
+ async function freezeAttachmentsWithTicketNumber(
601
+ attachmentPaths: readonly string[],
602
+ attachmentsDirectory: string,
603
+ ): Promise<{
604
+ readonly attachments: FrozenAttachment[];
605
+ readonly ticketNumber?: number;
606
+ }> {
607
+ const attachments: FrozenAttachment[] = [];
608
+ const bodies: Buffer[] = [];
609
+ for (let i = 0; i < attachmentPaths.length; i += 1) {
610
+ const frozen = await freezeRegularFileAttachment(
611
+ attachmentPaths[i]!,
612
+ attachmentsDirectory,
613
+ i,
614
+ );
615
+ attachments.push(frozen.attachment);
616
+ bodies.push(frozen.body);
617
+ }
618
+ const ticketNumber = resolveTicketNumberFromAttachmentBodies(bodies);
619
+ return {
620
+ attachments,
621
+ ...(ticketNumber === undefined ? {} : { ticketNumber }),
622
+ };
623
+ }
624
+
625
+ function ticketAdmissionFields(
626
+ ticketNumber: number | undefined,
627
+ ): { ticketNumber?: number } {
628
+ return ticketNumber === undefined ? {} : { ticketNumber };
629
+ }
630
+
557
631
  export type AdmitJudgeInvocationOptions = {
558
632
  home: string;
559
633
  cwd: string;
@@ -583,16 +657,11 @@ export async function admitJudgeInvocation(
583
657
  ensureRealDirectoryTree(ledgerHome, sessionDirectory);
584
658
  ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
585
659
 
586
- const attachments: FrozenAttachment[] = [];
587
- for (let i = 0; i < options.attachmentPaths.length; i += 1) {
588
- attachments.push(
589
- await freezeRegularFileAttachment(
590
- options.attachmentPaths[i]!,
591
- attachmentsDirectory,
592
- i,
593
- ),
594
- );
595
- }
660
+ const { attachments, ticketNumber } = await freezeAttachmentsWithTicketNumber(
661
+ options.attachmentPaths,
662
+ attachmentsDirectory,
663
+ );
664
+ const ticketFields = ticketAdmissionFields(ticketNumber);
596
665
 
597
666
  const instruction = options.instruction;
598
667
  const instructionEmpty = instruction.trim() === "";
@@ -604,6 +673,7 @@ export async function admitJudgeInvocation(
604
673
  runDirectory,
605
674
  sessionDirectory,
606
675
  sessionFile,
676
+ ...ticketFields,
607
677
  instruction,
608
678
  instructionEmpty,
609
679
  attachments: attachments.map((a) => ({
@@ -630,6 +700,7 @@ export async function admitJudgeInvocation(
630
700
  sessionDirectory,
631
701
  sessionFile,
632
702
  admittedRequestPath,
703
+ ...ticketFields,
633
704
  };
634
705
  }
635
706
 
@@ -721,16 +792,11 @@ export async function admitCoderInvocation(
721
792
  ensureRealDirectoryTree(ledgerHome, sessionDirectory);
722
793
  ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
723
794
 
724
- const attachments: FrozenAttachment[] = [];
725
- for (let i = 0; i < options.attachmentPaths.length; i += 1) {
726
- attachments.push(
727
- await freezeRegularFileAttachment(
728
- options.attachmentPaths[i]!,
729
- attachmentsDirectory,
730
- i,
731
- ),
732
- );
733
- }
795
+ const { attachments, ticketNumber } = await freezeAttachmentsWithTicketNumber(
796
+ options.attachmentPaths,
797
+ attachmentsDirectory,
798
+ );
799
+ const ticketFields = ticketAdmissionFields(ticketNumber);
734
800
 
735
801
  const taskPath = join(runDirectory, "task.md");
736
802
  await writeFile(taskPath, instruction, "utf8");
@@ -744,6 +810,7 @@ export async function admitCoderInvocation(
744
810
  runDirectory,
745
811
  sessionDirectory,
746
812
  sessionFile,
813
+ ...ticketFields,
747
814
  instruction,
748
815
  instructionEmpty: false,
749
816
  taskPath,
@@ -773,6 +840,7 @@ export async function admitCoderInvocation(
773
840
  sessionFile,
774
841
  admittedRequestPath,
775
842
  taskPath,
843
+ ...ticketFields,
776
844
  };
777
845
  }
778
846
 
@@ -828,34 +896,15 @@ export async function admitFixerInvocation(
828
896
  throw new CliUsageError("fixer phase must be plan or apply");
829
897
  }
830
898
 
831
- const projectRoot = resolve(options.project ?? options.cwd);
832
- const runId = (options.createRunId ?? uuidv7)();
833
- const { ledgerHome, bookKey, runDirectory, sessionDirectory, sessionFile } =
834
- roleRunSessionCoordinates({ cwd: projectRoot, runId, role: "fixer", home: options.home });
835
- const attachmentsDirectory = join(runDirectory, "attachments");
836
- ensureRealDirectoryTree(ledgerHome, sessionDirectory);
837
- ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
838
-
839
- const attachments: FrozenAttachment[] = [];
840
- for (let i = 0; i < options.attachmentPaths.length; i += 1) {
841
- attachments.push(
842
- await freezeRegularFileAttachment(
843
- options.attachmentPaths[i]!,
844
- attachmentsDirectory,
845
- i,
846
- ),
847
- );
848
- }
849
-
899
+ // Validate/read prerequisites before freezing request materials.
850
900
  let prerequisites: readonly FixerPrerequisite[] = Object.freeze([]);
851
- let prerequisitesPath: string | undefined;
901
+ let prerequisitesSource: string | undefined;
852
902
  if (options.prerequisitesPath !== undefined) {
853
903
  const absolutePrereq = isAbsolute(options.prerequisitesPath)
854
904
  ? options.prerequisitesPath
855
905
  : resolve(options.prerequisitesPath);
856
- let source: string;
857
906
  try {
858
- source = await readFile(absolutePrereq, "utf8");
907
+ prerequisitesSource = await readFile(absolutePrereq, "utf8");
859
908
  } catch (error) {
860
909
  throw new CliUsageError(
861
910
  `fixer prerequisites path is unreadable: ${options.prerequisitesPath}`,
@@ -863,13 +912,31 @@ export async function admitFixerInvocation(
863
912
  );
864
913
  }
865
914
  try {
866
- prerequisites = parseFixerPrerequisites(source);
915
+ prerequisites = parseFixerPrerequisites(prerequisitesSource);
867
916
  } catch (error) {
868
917
  if (error instanceof FixerPacketValidationError) {
869
918
  throw new CliUsageError(error.message, { cause: error });
870
919
  }
871
920
  throw error;
872
921
  }
922
+ }
923
+
924
+ const projectRoot = resolve(options.project ?? options.cwd);
925
+ const runId = (options.createRunId ?? uuidv7)();
926
+ const { ledgerHome, bookKey, runDirectory, sessionDirectory, sessionFile } =
927
+ roleRunSessionCoordinates({ cwd: projectRoot, runId, role: "fixer", home: options.home });
928
+ const attachmentsDirectory = join(runDirectory, "attachments");
929
+ ensureRealDirectoryTree(ledgerHome, sessionDirectory);
930
+ ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
931
+
932
+ const { attachments, ticketNumber } = await freezeAttachmentsWithTicketNumber(
933
+ options.attachmentPaths,
934
+ attachmentsDirectory,
935
+ );
936
+ const ticketFields = ticketAdmissionFields(ticketNumber);
937
+
938
+ let prerequisitesPath: string | undefined;
939
+ if (prerequisitesSource !== undefined) {
873
940
  prerequisitesPath = join(runDirectory, "prerequisites.json");
874
941
  await writeFile(
875
942
  prerequisitesPath,
@@ -890,6 +957,7 @@ export async function admitFixerInvocation(
890
957
  runDirectory,
891
958
  sessionDirectory,
892
959
  sessionFile,
960
+ ...ticketFields,
893
961
  instruction,
894
962
  instructionEmpty: false,
895
963
  packetPath,
@@ -926,6 +994,7 @@ export async function admitFixerInvocation(
926
994
  packetPath,
927
995
  ...(prerequisitesPath === undefined ? {} : { prerequisitesPath }),
928
996
  prerequisites,
997
+ ...ticketFields,
929
998
  };
930
999
  }
931
1000
 
@@ -1103,6 +1172,19 @@ export async function admitCollectorInvocation(
1103
1172
  repository = resolveGitHubRemoteRepository(projectRoot);
1104
1173
  }
1105
1174
 
1175
+ // Validate optional request-manifest before freezing request materials.
1176
+ let manifest = emptyCollectorManifest();
1177
+ let manifestCanonicalJson: string | undefined;
1178
+ if (options.requestManifestPath !== undefined) {
1179
+ try {
1180
+ manifest = await loadCollectorManifest(options.requestManifestPath);
1181
+ manifestCanonicalJson = manifest.canonicalJson;
1182
+ } catch (error) {
1183
+ throw new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error });
1184
+ }
1185
+ }
1186
+ const manifestDigest = manifest.digest;
1187
+
1106
1188
  const runId = (options.createRunId ?? uuidv7)();
1107
1189
  const { ledgerHome, bookKey, runDirectory, sessionDirectory, sessionFile } =
1108
1190
  roleRunSessionCoordinates({ cwd: projectRoot, runId, role: "collector", home: options.home });
@@ -1110,30 +1192,17 @@ export async function admitCollectorInvocation(
1110
1192
  ensureRealDirectoryTree(ledgerHome, sessionDirectory);
1111
1193
  ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
1112
1194
 
1113
- const attachments: FrozenAttachment[] = [];
1114
- const attachmentPaths = options.attachmentPaths ?? [];
1115
- for (let i = 0; i < attachmentPaths.length; i += 1) {
1116
- attachments.push(
1117
- await freezeRegularFileAttachment(
1118
- attachmentPaths[i]!,
1119
- attachmentsDirectory,
1120
- i,
1121
- ),
1122
- );
1123
- }
1195
+ const { attachments, ticketNumber } = await freezeAttachmentsWithTicketNumber(
1196
+ options.attachmentPaths ?? [],
1197
+ attachmentsDirectory,
1198
+ );
1199
+ const ticketFields = ticketAdmissionFields(ticketNumber);
1124
1200
 
1125
- let manifest = emptyCollectorManifest();
1126
1201
  let requestManifestPath: string | undefined;
1127
- if (options.requestManifestPath !== undefined) {
1128
- try {
1129
- manifest = await loadCollectorManifest(options.requestManifestPath);
1130
- } catch (error) {
1131
- throw new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error });
1132
- }
1202
+ if (manifestCanonicalJson !== undefined) {
1133
1203
  requestManifestPath = join(runDirectory, "request-manifest.json");
1134
- await writeFile(requestManifestPath, manifest.canonicalJson, "utf8");
1204
+ await writeFile(requestManifestPath, manifestCanonicalJson, "utf8");
1135
1205
  }
1136
- const manifestDigest = manifest.digest;
1137
1206
 
1138
1207
  const instruction = options.instruction ?? "";
1139
1208
  const instructionEmpty = instruction.trim() === "";
@@ -1145,6 +1214,7 @@ export async function admitCollectorInvocation(
1145
1214
  runDirectory,
1146
1215
  sessionDirectory,
1147
1216
  sessionFile,
1217
+ ...ticketFields,
1148
1218
  instruction,
1149
1219
  instructionEmpty,
1150
1220
  prNumber,
@@ -1184,6 +1254,7 @@ export async function admitCollectorInvocation(
1184
1254
  repository,
1185
1255
  ...(requestManifestPath === undefined ? {} : { requestManifestPath }),
1186
1256
  manifestDigest,
1257
+ ...ticketFields,
1187
1258
  };
1188
1259
  }
1189
1260
 
@@ -1462,17 +1533,11 @@ export async function admitDoctorInvocation(
1462
1533
  ensureRealDirectoryTree(ledgerHome, sessionDirectory);
1463
1534
  ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
1464
1535
 
1465
- const attachments: FrozenAttachment[] = [];
1466
- const attachmentPaths = options.attachmentPaths ?? [];
1467
- for (let i = 0; i < attachmentPaths.length; i += 1) {
1468
- attachments.push(
1469
- await freezeRegularFileAttachment(
1470
- attachmentPaths[i]!,
1471
- attachmentsDirectory,
1472
- i,
1473
- ),
1474
- );
1475
- }
1536
+ const { attachments, ticketNumber } = await freezeAttachmentsWithTicketNumber(
1537
+ options.attachmentPaths ?? [],
1538
+ attachmentsDirectory,
1539
+ );
1540
+ const ticketFields = ticketAdmissionFields(ticketNumber);
1476
1541
 
1477
1542
  const instruction = options.instruction ?? "";
1478
1543
  const instructionEmpty = instruction.trim() === "";
@@ -1484,6 +1549,7 @@ export async function admitDoctorInvocation(
1484
1549
  runDirectory,
1485
1550
  sessionDirectory,
1486
1551
  sessionFile,
1552
+ ...ticketFields,
1487
1553
  instruction,
1488
1554
  instructionEmpty,
1489
1555
  issueNumber: options.issueNumber,
@@ -1520,6 +1586,7 @@ export async function admitDoctorInvocation(
1520
1586
  issueNumber: options.issueNumber,
1521
1587
  caseRunsPath,
1522
1588
  caseIdentity,
1589
+ ...ticketFields,
1523
1590
  };
1524
1591
  }
1525
1592
 
@@ -1540,13 +1607,15 @@ export function buildDoctorTransportPrompt(
1540
1607
 
1541
1608
  /**
1542
1609
  * Parse Reviewer-specific argv after the `reviewer` token.
1543
- * Public flags: --project and required --base (the non-interactive CLI cannot answer the canonical fixed-point question).
1610
+ * Public flags: --project, required --base, optional repeatable --authority-ref.
1544
1611
  * Reviewer gathers its own evidence; users submit neither attachments nor capability packets.
1612
+ * Caller instruction remains scope/procedure provenance — not Spec authority.
1545
1613
  */
1546
1614
  export function parseReviewerArgv(
1547
1615
  args: readonly string[],
1548
1616
  ): ParseReviewerArgvResult {
1549
1617
  const attachmentPaths: string[] = [];
1618
+ const authorityRefs: string[] = [];
1550
1619
  let project: string | undefined;
1551
1620
  let baseRevision: string | undefined;
1552
1621
  const positional: string[] = [];
@@ -1574,6 +1643,14 @@ export function parseReviewerArgv(
1574
1643
  baseRevision = requireOptionPath("--base", token.slice("--base=".length));
1575
1644
  continue;
1576
1645
  }
1646
+ if (token === "--authority-ref") {
1647
+ authorityRefs.push(requireAuthorityRef(tokens.shift()));
1648
+ continue;
1649
+ }
1650
+ if (token.startsWith("--authority-ref=")) {
1651
+ authorityRefs.push(requireAuthorityRef(token.slice("--authority-ref=".length)));
1652
+ continue;
1653
+ }
1577
1654
  if (token.startsWith("-") && token !== "-") {
1578
1655
  throw new CliUsageError(`unknown reviewer option: ${token}`);
1579
1656
  }
@@ -1587,6 +1664,7 @@ export function parseReviewerArgv(
1587
1664
  instruction: positional.join(" "),
1588
1665
  attachmentPaths,
1589
1666
  baseRevision,
1667
+ authorityRefs,
1590
1668
  ...(project === undefined ? {} : { project }),
1591
1669
  };
1592
1670
  }
@@ -1598,6 +1676,8 @@ export type AdmitReviewerInvocationOptions = {
1598
1676
  instruction: string;
1599
1677
  attachmentPaths: readonly string[];
1600
1678
  baseRevision: string;
1679
+ /** Optional durable authority references/URLs; frozen unchanged at admission. */
1680
+ authorityRefs?: readonly string[];
1601
1681
  project?: string;
1602
1682
  createRunId?: () => string;
1603
1683
  };
@@ -1605,6 +1685,7 @@ export type AdmitReviewerInvocationOptions = {
1605
1685
  /**
1606
1686
  * Admit a Reviewer Role run on the fixed base only.
1607
1687
  * Caller instruction is optional provenance; Reviewer acquires issue/authority independently.
1688
+ * Optional authorityRefs are frozen as durable references only — not Spec prose.
1608
1689
  */
1609
1690
  export async function admitReviewerInvocation(
1610
1691
  options: AdmitReviewerInvocationOptions,
@@ -1615,6 +1696,9 @@ export async function admitReviewerInvocation(
1615
1696
  if (options.baseRevision.trim() === "") {
1616
1697
  throw new CliUsageError("--base requires a nonempty revision");
1617
1698
  }
1699
+ const authorityRefs = Object.freeze(
1700
+ (options.authorityRefs ?? []).map((ref) => requireAuthorityRef(ref)),
1701
+ );
1618
1702
 
1619
1703
  const projectRoot = resolve(options.project ?? options.cwd);
1620
1704
  const runId = (options.createRunId ?? uuidv7)();
@@ -1625,16 +1709,11 @@ export async function admitReviewerInvocation(
1625
1709
  ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
1626
1710
 
1627
1711
  // Public parse already rejects attachments; keep freeze loop for structural symmetry.
1628
- const attachments: FrozenAttachment[] = [];
1629
- for (let i = 0; i < options.attachmentPaths.length; i += 1) {
1630
- attachments.push(
1631
- await freezeRegularFileAttachment(
1632
- options.attachmentPaths[i]!,
1633
- attachmentsDirectory,
1634
- i,
1635
- ),
1636
- );
1637
- }
1712
+ const { attachments, ticketNumber } = await freezeAttachmentsWithTicketNumber(
1713
+ options.attachmentPaths,
1714
+ attachmentsDirectory,
1715
+ );
1716
+ const ticketFields = ticketAdmissionFields(ticketNumber);
1638
1717
 
1639
1718
  const instruction = options.instruction;
1640
1719
  const instructionEmpty = instruction.trim() === "";
@@ -1646,9 +1725,11 @@ export async function admitReviewerInvocation(
1646
1725
  runDirectory,
1647
1726
  sessionDirectory,
1648
1727
  sessionFile,
1728
+ ...ticketFields,
1649
1729
  instruction,
1650
1730
  instructionEmpty,
1651
1731
  baseRevision: options.baseRevision,
1732
+ authorityRefs: [...authorityRefs],
1652
1733
  attachments: attachments.map((a) => ({
1653
1734
  provenancePath: a.provenancePath,
1654
1735
  frozenPath: a.frozenPath,
@@ -1678,6 +1759,8 @@ export async function admitReviewerInvocation(
1678
1759
  sessionFile,
1679
1760
  admittedRequestPath,
1680
1761
  baseRevision: options.baseRevision,
1762
+ authorityRefs,
1763
+ ...ticketFields,
1681
1764
  };
1682
1765
  }
1683
1766
 
@@ -1845,16 +1928,11 @@ export async function admitMergerInvocation(
1845
1928
  ensureRealDirectoryTree(ledgerHome, sessionDirectory);
1846
1929
  ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
1847
1930
 
1848
- const attachments: FrozenAttachment[] = [];
1849
- for (let i = 0; i < options.attachmentPaths.length; i += 1) {
1850
- attachments.push(
1851
- await freezeRegularFileAttachment(
1852
- options.attachmentPaths[i]!,
1853
- attachmentsDirectory,
1854
- i,
1855
- ),
1856
- );
1857
- }
1931
+ const { attachments, ticketNumber } = await freezeAttachmentsWithTicketNumber(
1932
+ options.attachmentPaths,
1933
+ attachmentsDirectory,
1934
+ );
1935
+ const ticketFields = ticketAdmissionFields(ticketNumber);
1858
1936
 
1859
1937
  // Intent materials seed primary-source investigation; the method owns the work.
1860
1938
  const targetIntent = mergerMaterialFromUtf8(
@@ -1866,6 +1944,7 @@ export async function admitMergerInvocation(
1866
1944
  const taskMaterial = mergerMaterialFromUtf8(instruction);
1867
1945
  const authorityMaterial = mergerMaterialFromUtf8(instruction);
1868
1946
 
1947
+ // Validate merger envelope before placing admitted identity.
1869
1948
  const mergerInput = validateMergerInput({
1870
1949
  version: 1,
1871
1950
  attemptId: runId,
@@ -1898,6 +1977,7 @@ export async function admitMergerInvocation(
1898
1977
  runDirectory,
1899
1978
  sessionDirectory,
1900
1979
  sessionFile,
1980
+ ...ticketFields,
1901
1981
  instruction,
1902
1982
  instructionEmpty: false,
1903
1983
  mergerInputPath,
@@ -1938,6 +2018,7 @@ export async function admitMergerInvocation(
1938
2018
  admittedRequestPath,
1939
2019
  mergerInputPath,
1940
2020
  derived: admitted.derived,
2021
+ ...ticketFields,
1941
2022
  };
1942
2023
  }
1943
2024
 
@@ -289,8 +289,9 @@ async function dispatchAdmittedJudge(input: {
289
289
  // and role-runtime can record typed provider HTTP observations.
290
290
  AK_ROLE_RUN_DIR: admitted.runDirectory,
291
291
  };
292
- if (env.correlationId !== undefined && env.correlationId.trim() !== "") {
293
- childEnv.AK_CORRELATION_ID = env.correlationId;
292
+ const correlationId = admitted.correlationId ?? env.correlationId;
293
+ if (correlationId !== undefined && correlationId.trim() !== "") {
294
+ childEnv.AK_CORRELATION_ID = correlationId;
294
295
  }
295
296
 
296
297
  let result: ExplicitInternalPiResult;
@@ -450,7 +451,10 @@ export async function runPublicJudge(
450
451
 
451
452
  return await dispatchAdmittedJudge({
452
453
  admitted,
453
- env,
454
+ env: {
455
+ ...env,
456
+ ...(admitted.correlationId === undefined ? {} : { correlationId: admitted.correlationId }),
457
+ },
454
458
  io,
455
459
  extraArgs,
456
460
  lease,
@@ -520,7 +524,10 @@ export async function runPublicResume(
520
524
 
521
525
  return await dispatchAdmittedJudge({
522
526
  admitted,
523
- env,
527
+ env: {
528
+ ...env,
529
+ ...(admitted.correlationId === undefined ? {} : { correlationId: admitted.correlationId }),
530
+ },
524
531
  io,
525
532
  extraArgs,
526
533
  lease,