@akagilnc/pi-workflow-roles 0.1.5583 → 0.1.5593

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.
@@ -1392,23 +1392,10 @@ var init_cli_errors = __esm({
1392
1392
  }
1393
1393
  });
1394
1394
 
1395
- // src/sha256.ts
1396
- import { createHash } from "node:crypto";
1397
- function sha256Hex(bytes) {
1398
- return createHash("sha256").update(bytes).digest("hex");
1399
- }
1400
- var init_sha256 = __esm({
1401
- "src/sha256.ts"() {
1402
- "use strict";
1403
- }
1404
- });
1405
-
1406
1395
  // src/collector-config.ts
1407
1396
  var init_collector_config = __esm({
1408
1397
  "src/collector-config.ts"() {
1409
1398
  "use strict";
1410
- init_sha256();
1411
- init_unknown_value();
1412
1399
  init_run_ticket_number();
1413
1400
  }
1414
1401
  });
@@ -15608,22 +15595,10 @@ function rewriteSummonsMaterials(value2, rewrites) {
15608
15595
  rewrites
15609
15596
  );
15610
15597
  rewriteSourceRunLocator(value2.sourceRun, rewrites);
15611
- if (Array.isArray(value2.attachmentPaths)) {
15612
- value2.attachmentPaths = value2.attachmentPaths.map(
15613
- (path8) => rewriteRunDirectoryPathValueAgainstRewrites(path8, rewrites)
15614
- );
15615
- }
15616
15598
  }
15617
15599
  function rewriteAdmittedRoleRunPage(page, rewrites) {
15618
15600
  rewriteRunDirectoryPathFieldsAgainstRewrites(page, ADMITTED_PAGE_FIELDS, rewrites);
15619
15601
  rewriteSourceRunLocator(page.sourceRun, rewrites);
15620
- if (Array.isArray(page.attachments)) {
15621
- for (const attachment of page.attachments) {
15622
- if (isRecord(attachment)) {
15623
- rewriteRunDirectoryPathFieldsAgainstRewrites(attachment, ["frozenPath"], rewrites);
15624
- }
15625
- }
15626
- }
15627
15602
  if (isRecord(page.principal)) {
15628
15603
  rewriteRunDirectoryPathFieldsAgainstRewrites(
15629
15604
  page.principal,
@@ -15859,7 +15834,7 @@ var init_role_run_relocation = __esm({
15859
15834
  "taskPath",
15860
15835
  "packetPath",
15861
15836
  "prerequisitesPath",
15862
- "requestManifestPath",
15837
+ // #1165: requestManifestPath is a caller path — never rewrite.
15863
15838
  "mergerInputPath",
15864
15839
  "sourceRunPath"
15865
15840
  ];
@@ -15884,6 +15859,17 @@ var init_role_run_relocation = __esm({
15884
15859
  }
15885
15860
  });
15886
15861
 
15862
+ // src/sha256.ts
15863
+ import { createHash } from "node:crypto";
15864
+ function sha256Hex(bytes) {
15865
+ return createHash("sha256").update(bytes).digest("hex");
15866
+ }
15867
+ var init_sha256 = __esm({
15868
+ "src/sha256.ts"() {
15869
+ "use strict";
15870
+ }
15871
+ });
15872
+
15887
15873
  // src/submission-correctable-error.ts
15888
15874
  var correctableSubmissionErrorBrand, CorrectableSubmissionError;
15889
15875
  var init_submission_correctable_error = __esm({
@@ -16149,8 +16135,8 @@ var init_option_definitions = __esm({
16149
16135
  repeatable: true,
16150
16136
  form: "option",
16151
16137
  description: {
16152
- en: "Attach a regular file; frozen at admission (repeatable).",
16153
- zh: "\u9644\u52A0\u666E\u901A\u6587\u4EF6\uFF1B\u53D7\u7406\u5373\u51BB\u7ED3\uFF08\u53EF\u91CD\u590D\uFF09\u3002"
16138
+ en: "Caller file path passed as-is to the role (repeatable). Package does not read, copy, or validate; use an absolute path when cwd differs from --project.",
16139
+ zh: "\u8C03\u7528\u65B9\u6587\u4EF6\u8DEF\u5F84\u539F\u6837\u9012\u7ED9\u8859\u95E8\uFF08\u53EF\u91CD\u590D\uFF09\u3002\u5305\u4E0D\u8BFB\u3001\u4E0D\u590D\u5236\u3001\u4E0D\u6821\u9A8C\uFF1B\u8C03\u7528\u65B9\u5F53\u524D\u76EE\u5F55\u4E0E --project \u4E0D\u540C\u65F6\u8BF7\u7ED9\u7EDD\u5BF9\u8DEF\u5F84\u3002"
16154
16140
  }
16155
16141
  };
16156
16142
  SHARED_PHASE_SEMANTICS = {
@@ -16325,8 +16311,8 @@ var init_option_definitions = __esm({
16325
16311
  repeatable: false,
16326
16312
  form: "option",
16327
16313
  description: {
16328
- en: "Optional request manifest JSON path ({requests:[{id,body}]}). Parsed for semantics and passed to the Collector as materials \u2014 not a machine collection directive.",
16329
- zh: "\u53EF\u9009 request manifest JSON \u8DEF\u5F84\uFF08{requests:[{id,body}]}\uFF09\u3002\u6309\u8BED\u4E49\u89E3\u6790\u540E\u4F5C\u4E3A\u6750\u6599\u9012\u7ED9\u901A\u8FDB\u53F8\uFF0C\u4E0D\u662F\u673A\u5668\u4EE3\u6536 finding \u7684\u6307\u4EE4\u3002"
16314
+ en: "Caller path to a request manifest JSON file, passed as-is. Package does not read, copy, or validate; use an absolute path when cwd differs from --project.",
16315
+ zh: "\u8C03\u7528\u65B9 request manifest JSON \u8DEF\u5F84\uFF0C\u539F\u6837\u4F20\u9012\u3002\u5305\u4E0D\u8BFB\u3001\u4E0D\u590D\u5236\u3001\u4E0D\u6821\u9A8C\uFF1B\u8C03\u7528\u65B9\u5F53\u524D\u76EE\u5F55\u4E0E --project \u4E0D\u540C\u65F6\u8BF7\u7ED9\u7EDD\u5BF9\u8DEF\u5F84\u3002"
16330
16316
  }
16331
16317
  }
16332
16318
  ];
@@ -27,7 +27,7 @@ import { NotarySourceRunError, resolveNotarySourceRunLocator } from "../notary-s
27
27
  import { packagedAdmitsCountersign, packagedBindsBoardTicket, packagedRebindSourceOnResume, packagedRoleMetadata, } from "../packaged-role-registry.js";
28
28
  import { readAuditorResumeBinding } from "../auditor-soul.js";
29
29
  import { CliUsageError } from "./cli-errors.js";
30
- import { admitPublicRole, bindAdmittedTicketNumber, buildInstructionTransportPrompt, materializeCountersignInvocation, persistAdmittedSourceRunPath, recordAdmittedCorrelation, recordChildDiaristRun, relocateAdmittedRunToTicket, withPreparedAttachments, } from "./invocation.js";
30
+ import { admitPublicRole, bindAdmittedTicketNumber, buildInstructionTransportPrompt, materializeCountersignInvocation, persistAdmittedSourceRunPath, recordAdmittedCorrelation, recordChildDiaristRun, relocateAdmittedRunToTicket, } from "./invocation.js";
31
31
  import { bindDiaristAssertedTicketNumber, presentControlledFailure, packageFaultNoteFor, prepareSummonsResumeMaterials, roleTurnOptions, runPostAdmissionOneShot, runPostAdmissionResumable, runPostAdmissionSeatResume, showResumeErrorPointer, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
32
32
  import { presentTerminal } from "./auto-resume.js";
33
33
  import { buildAutoResumeContinuationPrompt, loadResumablePublicRole, markRunAdmitted, parentRunPathFromGatePointerInstruction, readCurrentCourt, } from "./run-lifecycle.js";
@@ -429,7 +429,7 @@ async function runCountersignBody(parsed, env, io) {
429
429
  summons: {
430
430
  sourceRunPath: gateParentRunPath,
431
431
  instruction: resumeInstruction,
432
- instructionEmpty: resumeInstruction.trim() === "",
432
+ instructionEmpty: resumeInstruction.length === 0,
433
433
  },
434
434
  });
435
435
  if (resumed != null)
@@ -446,12 +446,12 @@ async function runCountersignBody(parsed, env, io) {
446
446
  throw error;
447
447
  }
448
448
  try {
449
- return await withPreparedAttachments(parsed.attachmentPaths ?? [], async (preparedAttachments) => {
449
+ {
450
450
  const materializeAdmission = async (ticketNumber) => {
451
451
  await materializeCountersignInvocation(admitted, {
452
452
  home: env.home,
453
453
  principalAuthority: env.principalAuthority,
454
- preparedAttachments,
454
+ attachmentPaths: parsed.attachmentPaths ?? [],
455
455
  ...(env.model === undefined ? {} : { model: env.model }),
456
456
  ...(ticketNumber === undefined ? {} : { ticketNumber }),
457
457
  });
@@ -486,7 +486,7 @@ async function runCountersignBody(parsed, env, io) {
486
486
  });
487
487
  await relocateAdmittedRunToTicket(admitted, env.principalAuthority);
488
488
  return result;
489
- });
489
+ }
490
490
  }
491
491
  catch (error) {
492
492
  const rejected = usageExit(error, io);
@@ -557,7 +557,7 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
557
557
  const summons = {
558
558
  ...sameParentInstruction(env, {
559
559
  instruction: parsed.instruction ?? "",
560
- instructionEmpty: (parsed.instruction ?? "").trim() === "",
560
+ instructionEmpty: (parsed.instruction ?? "").length === 0,
561
561
  }),
562
562
  attachmentPaths: parsed.attachmentPaths ?? [],
563
563
  };
@@ -583,7 +583,7 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
583
583
  sourceRunPath: parentRunPath,
584
584
  ...sameParentInstruction(env, {
585
585
  instruction: parsed.instruction ?? "",
586
- instructionEmpty: (parsed.instruction ?? "").trim() === "",
586
+ instructionEmpty: (parsed.instruction ?? "").length === 0,
587
587
  }),
588
588
  attachmentPaths: parsed.attachmentPaths ?? [],
589
589
  };
@@ -669,7 +669,7 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
669
669
  instruction: parsed.instruction ?? "",
670
670
  projectRoot: admitted.projectRoot,
671
671
  failureLabel: "secretariat unbound summons",
672
- attachmentPaths: admitted.attachments.map((attachment) => attachment.frozenPath),
672
+ attachmentPaths: admitted.attachments.map((attachment) => attachment.path),
673
673
  correlationId: admitted.runId,
674
674
  }, env, io);
675
675
  }
@@ -1,21 +1,20 @@
1
1
  /**
2
- * Public Invocation request admission: optional opaque instruction, frozen
3
- * Attachments, project default/override (ADR 0052 / #106).
2
+ * Public Invocation request admission: optional opaque instruction, caller
3
+ * attachment paths as-is, project default/override (ADR 0052 / #106 / #1165).
4
4
  */
5
5
  import { execFileSync } from "node:child_process";
6
6
  import { existsSync } from "node:fs";
7
- import { lstat, mkdtemp, readFile, realpath, rename, rm, writeFile, } from "node:fs/promises";
8
- import { tmpdir } from "node:os";
9
- import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
7
+ import { readFile, realpath, rename, writeFile, } from "node:fs/promises";
8
+ import { dirname, isAbsolute, join, resolve, sep } from "node:path";
10
9
  import { activationBookDirectory, ensureRealDirectoryTree, homeFromRunDirectory, pathContainedIn, resolveActivationLedgerHome, } from "../activation-ledger-topology.js";
11
10
  import { resolveBookKeyFromGit } from "../activation-ledger-git.js";
12
11
  import { ensureRoleRunDirectory, ensureRoleRunPlacement, formatRunLeaf, isUnboundRunDirectory, listBookRunDirectories, roleRunPlacement, } from "../role-run-placement.js";
13
12
  import { packagedAdmittedSubject, packagedArgvResult, packagedBareToken, packagedBaseIsRevision, packagedEmitsAuthorityRefs, packagedPublicInstructionSubject, packagedRoleMetadata, packagedStoresSourceRunRaw, packagedSubjectChoices, } from "../packaged-role-registry.js";
14
13
  import { isSafePositiveTicketNumber, parseTicketNumber, readBoardTicketNumber, requireSafePositiveTicketNumber, } from "../run-ticket-number.js";
15
- import { rewriteRunDirectoryPathFields, rewriteRunDirectoryPathValue, } from "../role-run-relocation.js";
14
+ import { rewriteRunDirectoryPathFields, } from "../role-run-relocation.js";
16
15
  import { readPageSync, renderCurrentSync, updateSectionSync, writeSectionSync } from "../run-dossier.js";
17
16
  import { loadDoctorCase, } from "../doctor-evidence.js";
18
- import { emptyCollectorManifest, loadCollectorManifest, parseCollectorPrNumber, parseCollectorRepository, } from "../collector-config.js";
17
+ import { parseCollectorPrNumber, parseCollectorRepository, } from "../collector-config.js";
19
18
  import { ownerRepoFromGitHubRemoteUrl } from "./github-remote.js";
20
19
  import { FixerPacketValidationError, parseFixerPrerequisites, } from "../package-contracts/fixer-packet.js";
21
20
  import { createProductionMergerGitState } from "../merger-git-state.js";
@@ -28,6 +27,20 @@ import { NotarySourceRunError, resolveNotarySourceRunLocator, } from "../notary-
28
27
  import { CliUsageError } from "./cli-errors.js";
29
28
  import { REJECTED_PUBLIC_SPELLINGS, createTypedOptionConsumer, evaluateAnalystModeOptionContract, optionsForOwner, resolveAnalystMode, } from "./option-definitions.js";
30
29
  import { errorText, isRecord } from "../unknown-value.js";
30
+ /** Normalize admitted attachment records (caller path only; #1165). */
31
+ export function normalizeAdmittedAttachment(raw) {
32
+ if (typeof raw !== "object" || raw === null)
33
+ return undefined;
34
+ const record = raw;
35
+ const path = record.path;
36
+ // Opaque caller path — keep "" / whitespace; only a non-string is absent.
37
+ if (typeof path === "string")
38
+ return { path };
39
+ return undefined;
40
+ }
41
+ function admitCallerAttachments(attachmentPaths) {
42
+ return attachmentPaths.map((path) => ({ path }));
43
+ }
31
44
  /** Issue a principal from the single authoritative role-run placement. */
32
45
  export function issueAdmissionPlacement(authority, request) {
33
46
  const ledgerHome = resolveActivationLedgerHome(request.home);
@@ -261,12 +274,9 @@ export async function relocateAdmittedRunToTicket(admitted, authority, heldLease
261
274
  "taskPath",
262
275
  "packetPath",
263
276
  "prerequisitesPath",
264
- "requestManifestPath",
265
277
  "mergerInputPath",
266
278
  ], oldRunDirectory, target.runDirectory);
267
- for (const attachment of admitted.attachments) {
268
- attachment.frozenPath = rewriteRunDirectoryPathValue(attachment.frozenPath, oldRunDirectory, target.runDirectory);
269
- }
279
+ // #1165: caller --attach / --request-manifest paths stay as given; do not rewrite.
270
280
  admitted.principal = principal;
271
281
  // current.json projects host.original from this run directory. Ownership is
272
282
  // already transferred; a refuse still carries the true cause to the caller
@@ -382,7 +392,8 @@ function assignPublicSeatOption(owner, taken, fields) {
382
392
  const value = taken.value;
383
393
  switch (taken.def.id) {
384
394
  case "attach":
385
- fields.attachmentPaths.push(requireOptionPath(taken.def.canonical, value));
395
+ // #1165: file-flag values are opaque; only a missing argument is a usage error.
396
+ fields.attachmentPaths.push(requireProvidedOptionValue(taken.def.canonical, value));
386
397
  return;
387
398
  case "project":
388
399
  fields.project = requireOptionPath(taken.def.canonical, value);
@@ -426,7 +437,8 @@ function assignPublicSeatOption(owner, taken, fields) {
426
437
  fields.repo = parseRepoOption(value);
427
438
  return;
428
439
  case "request-manifest":
429
- fields.requestManifestPath = requireOptionPath(taken.def.canonical, value);
440
+ // #1165: file-flag values are opaque; only a missing argument is a usage error.
441
+ fields.requestManifestPath = requireProvidedOptionValue(taken.def.canonical, value);
430
442
  return;
431
443
  case "issue": {
432
444
  if (value === undefined || value.trim() === "") {
@@ -539,6 +551,16 @@ function requireOptionPath(flag, value) {
539
551
  }
540
552
  return value;
541
553
  }
554
+ /**
555
+ * #1165 file flags (--attach / --request-manifest): keep the provided string
556
+ * opaque (including spaces and ""). Only a missing argument is a usage error.
557
+ */
558
+ function requireProvidedOptionValue(flag, value) {
559
+ if (value === undefined) {
560
+ throw new CliUsageError(`${flag} requires a path`);
561
+ }
562
+ return value;
563
+ }
542
564
  /**
543
565
  * Shared Skill-arg token rule for caller-controlled values projected into the
544
566
  * space-joined Skill invocation line. Rejects blank, whitespace (smuggles the
@@ -624,123 +646,6 @@ export function requireAuthorityRef(value) {
624
646
  optionLike: "--authority-ref requires a durable reference, not inline Spec prose",
625
647
  });
626
648
  }
627
- async function readRegularFileAttachment(sourcePath) {
628
- const absolute = isAbsolute(sourcePath) ? sourcePath : resolve(sourcePath);
629
- let st;
630
- try {
631
- st = await lstat(absolute);
632
- }
633
- catch (error) {
634
- throw new CliUsageError(`attachment is not a readable regular file: ${sourcePath}`, { cause: error });
635
- }
636
- if (!st.isFile() || st.isSymbolicLink()) {
637
- throw new CliUsageError(`attachment must be a regular file (not a directory or symlink): ${sourcePath}`);
638
- }
639
- try {
640
- return { absolute, bytes: await readFile(absolute) };
641
- }
642
- catch (error) {
643
- throw new CliUsageError(`attachment is not a readable regular file: ${sourcePath}`, { cause: error });
644
- }
645
- }
646
- /**
647
- * Snapshot deferred inputs sequentially before identity side effects. The staging
648
- * files keep aggregate attachment bytes off heap until their final run is known.
649
- */
650
- async function removePreparedAttachmentDirectory(stagingDirectory) {
651
- await rm(stagingDirectory, { recursive: true, force: true });
652
- }
653
- /** Own the complete deferred-snapshot lifetime without masking either failure. */
654
- export async function withPreparedAttachments(attachmentPaths, use) {
655
- if (attachmentPaths.length === 0)
656
- return await use([]);
657
- const stagingDirectory = await mkdtemp(join(tmpdir(), "ak-role-attachments-"));
658
- const prepared = [];
659
- let result;
660
- try {
661
- for (let index = 0; index < attachmentPaths.length; index += 1) {
662
- const { absolute, bytes } = await readRegularFileAttachment(attachmentPaths[index]);
663
- const snapshotPath = join(stagingDirectory, String(index).padStart(6, "0"));
664
- await writeFile(snapshotPath, bytes);
665
- prepared.push({ absolute, snapshotPath });
666
- }
667
- result = await use(prepared);
668
- }
669
- catch (primary) {
670
- try {
671
- await removePreparedAttachmentDirectory(stagingDirectory);
672
- }
673
- catch (cleanup) {
674
- throw new AggregateError([primary, cleanup], "attachment snapshot operation failed and cleanup also failed", { cause: primary });
675
- }
676
- throw primary;
677
- }
678
- await removePreparedAttachmentDirectory(stagingDirectory);
679
- return result;
680
- }
681
- async function freezeAttachmentBytes(provenancePath, bytes, destinationDir, index) {
682
- const frozenPath = join(destinationDir, `${String(index).padStart(2, "0")}-${basename(provenancePath)}`);
683
- await writeFile(frozenPath, bytes);
684
- return {
685
- provenancePath,
686
- frozenPath,
687
- byteLength: bytes.byteLength,
688
- sha256: sha256Hex(bytes),
689
- mediaKind: "regular-file",
690
- };
691
- }
692
- async function freezePreparedAttachment(prepared, destinationDir, index) {
693
- return freezeAttachmentBytes(prepared.absolute, await readFile(prepared.snapshotPath), destinationDir, index);
694
- }
695
- async function freezeRegularFileAttachment(sourcePath, destinationDir, index) {
696
- const { absolute, bytes } = await readRegularFileAttachment(sourcePath);
697
- return {
698
- attachment: await freezeAttachmentBytes(absolute, bytes, destinationDir, index),
699
- body: bytes,
700
- };
701
- }
702
- /** Freeze attachments only — ticket binding is the shared LLM seat path (#635). */
703
- async function freezeAttachments(ledgerHome, attachmentPaths, attachmentsDirectory) {
704
- const attachments = [];
705
- // attachments/ appears only when something is frozen; created through the
706
- // ledger's symlink-rejecting tree walk, never a plain mkdir.
707
- if (attachmentPaths.length > 0) {
708
- ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
709
- }
710
- for (let i = 0; i < attachmentPaths.length; i += 1) {
711
- const frozen = await freezeRegularFileAttachment(attachmentPaths[i], attachmentsDirectory, i);
712
- attachments.push(frozen.attachment);
713
- }
714
- return attachments;
715
- }
716
- /**
717
- * Freeze summons attachments into an already-retained run (#637 same-ticket resume).
718
- * Writes under attachments/summons-<key>/ so prior freeze names stay intact.
719
- * Manual resume never calls this — birth attachments keep their original semantics.
720
- */
721
- export async function freezeAttachmentsIntoRun(attachmentPaths, runDirectory, summonsKey = `s-${Date.now().toString(36)}`) {
722
- if (attachmentPaths.length === 0)
723
- return [];
724
- const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(runDirectory));
725
- const attachmentsDirectory = join(runDirectory, "attachments", summonsKey);
726
- ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
727
- return freezeAttachments(ledgerHome, attachmentPaths, attachmentsDirectory);
728
- }
729
- /** Freeze prepared bytes and metadata through one path for birth or retained runs. */
730
- export async function freezePreparedAttachmentsIntoRun(prepared, runDirectory, summonsKey) {
731
- if (prepared.length === 0)
732
- return [];
733
- const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(runDirectory));
734
- const attachmentsDirectory = summonsKey === undefined
735
- ? join(runDirectory, "attachments")
736
- : join(runDirectory, "attachments", summonsKey);
737
- ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
738
- const attachments = [];
739
- for (let index = 0; index < prepared.length; index += 1) {
740
- attachments.push(await freezePreparedAttachment(prepared[index], attachmentsDirectory, index));
741
- }
742
- return attachments;
743
- }
744
649
  function ticketAdmissionFields(ticketNumber) {
745
650
  if (ticketNumber === undefined)
746
651
  return {};
@@ -891,36 +796,21 @@ export async function admitPublicRole(role, parsed, env, override) {
891
796
  else {
892
797
  repository = resolveGitHubRemoteRepository(projectRoot);
893
798
  }
894
- let manifest = emptyCollectorManifest();
895
- let manifestCanonicalJson;
896
- if (parsed.requestManifestPath !== undefined) {
897
- try {
898
- manifest = await loadCollectorManifest(parsed.requestManifestPath);
899
- manifestCanonicalJson = manifest.canonicalJson;
900
- }
901
- catch (error) {
902
- throw new CliUsageError(errorText(error), { cause: error });
903
- }
904
- }
905
- const manifestDigest = manifest.digest;
799
+ // #1165: pass caller path as-is; package does not read, validate, or rewrite.
906
800
  const admittedCollector = await admitStandardMaterialInvocation("collector", {
907
801
  ...shared,
908
802
  instruction,
909
803
  attachmentPaths,
910
804
  ...project,
911
- placedFields: async (placed) => {
805
+ placedFields: async () => {
912
806
  // #1088: explicit --pr only. Unbound PR is located by the LLM via host CLI.
913
- let requestManifestPath;
914
- if (manifestCanonicalJson !== undefined) {
915
- requestManifestPath = join(placed.runDirectory, "request-manifest.json");
916
- await writeFile(requestManifestPath, manifestCanonicalJson, "utf8");
917
- }
918
807
  return {
919
808
  ...(explicitPrNumber === undefined ? {} : { prNumber: explicitPrNumber }),
920
809
  repository: repository.canonical,
921
810
  repositoryDisplay: repository.display,
922
- ...(requestManifestPath === undefined ? {} : { requestManifestPath }),
923
- manifestDigest,
811
+ ...(parsed.requestManifestPath === undefined
812
+ ? {}
813
+ : { requestManifestPath: parsed.requestManifestPath }),
924
814
  };
925
815
  },
926
816
  });
@@ -931,12 +821,10 @@ export async function admitPublicRole(role, parsed, env, override) {
931
821
  throw new CliUsageError(`doctor --issue must be a positive integer, got ${Number.NaN}`);
932
822
  }
933
823
  const issueNumber = parsed.issueNumber;
934
- let frozenAttachments = [];
935
- const admittedDoctor = await admitStandardMaterialInvocation("doctor", {
824
+ return admitStandardMaterialInvocation("doctor", {
936
825
  ...shared,
937
826
  instruction,
938
- attachmentPaths: [],
939
- freezeAttachments: false,
827
+ attachmentPaths,
940
828
  ...project,
941
829
  placedFields: async (placed) => {
942
830
  let caseRunsPath;
@@ -973,16 +861,13 @@ export async function admitPublicRole(role, parsed, env, override) {
973
861
  const detail = errorText(error);
974
862
  throw new CliUsageError(`doctor case could not be constructed from retained evidence: ${detail}`, { cause: error });
975
863
  }
976
- frozenAttachments = await freezeAttachments(placed.ledgerHome, attachmentPaths, placed.attachmentsDirectory);
977
864
  return {
978
865
  issueNumber,
979
866
  caseRunsPath,
980
867
  caseIdentity,
981
- attachments: persistedAttachmentRefs(frozenAttachments),
982
868
  };
983
869
  },
984
870
  });
985
- return { ...admittedDoctor, attachments: frozenAttachments };
986
871
  }
987
872
  case "source-locator": {
988
873
  const projectRoot = resolve(parsed.project ?? shared.cwd);
@@ -1013,7 +898,7 @@ export async function admitPublicRole(role, parsed, env, override) {
1013
898
  instruction: "",
1014
899
  attachmentPaths: [],
1015
900
  ...project,
1016
- freezeAttachments: false,
901
+ recordAttachments: false,
1017
902
  admittedFields: {
1018
903
  sourceRunPath: sourceRun.runDirectory,
1019
904
  sourceRun,
@@ -1030,7 +915,7 @@ export async function admitPublicRole(role, parsed, env, override) {
1030
915
  instruction,
1031
916
  attachmentPaths: [],
1032
917
  ...project,
1033
- freezeAttachments: false,
918
+ recordAttachments: false,
1034
919
  admittedFields: { baseRevision },
1035
920
  });
1036
921
  }
@@ -1128,9 +1013,9 @@ async function placeRoleAdmission(options) {
1128
1013
  home: options.home,
1129
1014
  ...(options.materialize === undefined ? {} : { materialize: options.materialize }),
1130
1015
  });
1131
- const attachments = options.freezeAttachments === false
1016
+ const attachments = options.recordAttachments === false
1132
1017
  ? []
1133
- : await freezeAttachments(ledgerHome, options.attachmentPaths, attachmentsDirectory);
1018
+ : admitCallerAttachments(options.attachmentPaths);
1134
1019
  return {
1135
1020
  runId,
1136
1021
  bookKey,
@@ -1146,13 +1031,7 @@ async function placeRoleAdmission(options) {
1146
1031
  };
1147
1032
  }
1148
1033
  function persistedAttachmentRefs(attachments) {
1149
- return attachments.map((attachment) => ({
1150
- provenancePath: attachment.provenancePath,
1151
- frozenPath: attachment.frozenPath,
1152
- byteLength: attachment.byteLength,
1153
- sha256: attachment.sha256,
1154
- mediaKind: attachment.mediaKind,
1155
- }));
1034
+ return attachments.map((attachment) => ({ path: attachment.path }));
1156
1035
  }
1157
1036
  async function persistPlacedAdmission(admitted, placed, model) {
1158
1037
  writeAdmittedRequestPersistence(placed.runDirectory, admitted, {
@@ -1172,19 +1051,18 @@ async function persistPlacedAdmission(admitted, placed, model) {
1172
1051
  * before materializeCountersignInvocation writes the page.
1173
1052
  * Seats whose extra facts are known before placement pass them as admittedFields.
1174
1053
  * Seats whose extra facts depend on the placed run pass placedFields
1175
- * (coder writes task.md; fixer writes fix-packet.md; collector writes request-manifest.json;
1176
- * doctor resolves the case, then freezes attachments; merger writes merger-input.json).
1054
+ * (coder writes task.md; fixer writes fix-packet.md; merger writes merger-input.json).
1177
1055
  */
1178
1056
  async function admitStandardMaterialInvocation(role, options) {
1179
1057
  const defer = options.deferPersistence === true;
1180
- const skipFreeze = defer || options.freezeAttachments === false;
1058
+ const skipAttachments = defer || options.recordAttachments === false;
1181
1059
  const placed = await placeRoleAdmission({
1182
1060
  role,
1183
1061
  home: options.home,
1184
1062
  principalAuthority: options.principalAuthority,
1185
1063
  cwd: options.cwd,
1186
1064
  attachmentPaths: options.attachmentPaths,
1187
- ...(skipFreeze ? { freezeAttachments: false } : {}),
1065
+ ...(skipAttachments ? { recordAttachments: false } : {}),
1188
1066
  ...(defer ? { materialize: false } : {}),
1189
1067
  ...(options.project === undefined ? {} : { project: options.project }),
1190
1068
  ...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
@@ -1196,7 +1074,8 @@ async function admitStandardMaterialInvocation(role, options) {
1196
1074
  ? {}
1197
1075
  : { correlationId: options.correlationId };
1198
1076
  const instruction = options.instruction;
1199
- const instructionEmpty = instruction.trim() === "";
1077
+ // Admitted emptiness is literal absence, not whitespace-only (#1165 J3).
1078
+ const instructionEmpty = instruction.length === 0;
1200
1079
  const placedExtra = options.placedFields === undefined ? undefined : await options.placedFields(placed);
1201
1080
  const admittedFields = {
1202
1081
  ...(options.admittedFields ?? {}),
@@ -1244,7 +1123,7 @@ function admittedTransportPromptKind(admitted) {
1244
1123
  /**
1245
1124
  * One initial prompt transport. The registry `transportPrompt` leaf selects
1246
1125
  * a fixed kickoff, a bound baseline, or frozen skill args. Absent means the
1247
- * caller instruction plus frozen attachment paths.
1126
+ * caller instruction plus caller attachment paths as-is (#1165).
1248
1127
  * Engine / outsourcing material rides startup readingMaterial (#1167), not here.
1249
1128
  */
1250
1129
  export function buildInstructionTransportPrompt(admitted) {
@@ -1260,7 +1139,9 @@ export function buildInstructionTransportPrompt(admitted) {
1260
1139
  throw new Error("baseline transport prompt is missing the bound revision");
1261
1140
  }
1262
1141
  const lines = [admitted.baseRevision];
1263
- if (!admitted.instructionEmpty) {
1142
+ // Use raw admitted.instruction — do not gate on instructionEmpty metadata
1143
+ // (old pages may set that flag wrong while still holding whitespace text).
1144
+ if (admitted.instruction.length > 0) {
1264
1145
  lines.push("", admitted.instruction);
1265
1146
  }
1266
1147
  return lines.join("\n");
@@ -1276,17 +1157,25 @@ export function buildInstructionTransportPrompt(admitted) {
1276
1157
  lens: admitted.lens,
1277
1158
  authorityRefs: admitted.authorityRefs,
1278
1159
  })];
1279
- if (!admitted.instructionEmpty && admitted.instruction.trim() !== "") {
1160
+ if (admitted.instruction.length > 0) {
1280
1161
  lines.push("", admitted.instruction);
1281
1162
  }
1282
1163
  return lines.join("\n");
1283
1164
  }
1284
- const lines = [admitted.instructionEmpty ? "" : admitted.instruction];
1285
- if (admitted.attachments.length > 0) {
1165
+ // #1165 J3: assemble from the admitted instruction bytes themselves.
1166
+ const lines = [admitted.instruction];
1167
+ // #1164/#1165: each caller path keeps its file-flag provenance.
1168
+ const flaggedPaths = [
1169
+ ...admitted.attachments.map((attachment) => `--attach ${attachment.path}`),
1170
+ ...(admitted.requestManifestPath === undefined
1171
+ ? []
1172
+ : [`--request-manifest ${admitted.requestManifestPath}`]),
1173
+ ];
1174
+ if (flaggedPaths.length > 0) {
1286
1175
  lines.push("");
1287
- lines.push("已受理附件(冻结快照路径):");
1288
- for (const attachment of admitted.attachments) {
1289
- lines.push(`- ${attachment.frozenPath}`);
1176
+ lines.push("已受理附件:");
1177
+ for (const entry of flaggedPaths) {
1178
+ lines.push(`- ${entry}`);
1290
1179
  }
1291
1180
  }
1292
1181
  return lines.join("\n");
@@ -1298,8 +1187,7 @@ export async function materializeCountersignInvocation(admitted, options) {
1298
1187
  home: options.home,
1299
1188
  principalAuthority: options.principalAuthority,
1300
1189
  cwd: admitted.projectRoot,
1301
- attachmentPaths: [],
1302
- freezeAttachments: false,
1190
+ attachmentPaths: options.attachmentPaths,
1303
1191
  runId: admitted.runId,
1304
1192
  ...(options.ticketNumber === undefined ? {} : { assertedTicketNumber: options.ticketNumber }),
1305
1193
  });
@@ -1310,8 +1198,7 @@ export async function materializeCountersignInvocation(admitted, options) {
1310
1198
  if (ticketFields.ticketNumber !== undefined) {
1311
1199
  admitted.ticketNumber = ticketFields.ticketNumber;
1312
1200
  }
1313
- const attachments = await freezePreparedAttachmentsIntoRun(options.preparedAttachments, admitted.runDirectory);
1314
- admitted.attachments = attachments;
1201
+ admitted.attachments = placement.attachments;
1315
1202
  writeAdmittedRequestPersistence(admitted.runDirectory, admitted, {
1316
1203
  sessionDirectory: placement.sessionDirectory,
1317
1204
  sessionFile: placement.sessionFile,
@@ -1332,10 +1219,13 @@ export async function loadAdmittedJudgeRequest(runDirectory) {
1332
1219
  return undefined;
1333
1220
  if (!Array.isArray(record.attachments))
1334
1221
  return undefined;
1222
+ const attachments = record.attachments
1223
+ .map((item) => normalizeAdmittedAttachment(item))
1224
+ .filter((item) => item !== undefined);
1335
1225
  return {
1336
1226
  instruction: record.instruction,
1337
1227
  instructionEmpty: record.instructionEmpty,
1338
- attachments: record.attachments,
1228
+ attachments,
1339
1229
  };
1340
1230
  }
1341
1231
  catch {
@@ -1597,7 +1487,7 @@ export function parseAnalystArgv(args) {
1597
1487
  return;
1598
1488
  }
1599
1489
  if (taken.def.id === "attach") {
1600
- pushValue("attach", requireOptionPath(taken.def.canonical, taken.value));
1490
+ pushValue("attach", requireProvidedOptionValue(taken.def.canonical, taken.value));
1601
1491
  return;
1602
1492
  }
1603
1493
  if (taken.def.id === "group-a-label" || taken.def.id === "group-b-label") {