@akagilnc/pi-workflow-roles 0.1.3412 → 0.1.3439

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 (67) hide show
  1. package/dist/archivist-record-entry.js +21 -12
  2. package/dist/archivist-record-topology.js +20 -0
  3. package/dist/auditor-dossier-tool.js +67 -76
  4. package/dist/canonical-json.js +66 -69
  5. package/dist/compliance-transport.js +99 -86
  6. package/dist/countersign-contracts.js +23 -25
  7. package/dist/doctor-contracts.js +169 -145
  8. package/dist/engine-detour-tool.js +83 -87
  9. package/dist/engine-detour.js +148 -183
  10. package/dist/evidence-child-executor.js +10 -1
  11. package/dist/exact-utf8.js +11 -10
  12. package/dist/git-object-id.js +10 -5
  13. package/dist/gleaner-left-contracts.js +33 -35
  14. package/dist/grok/production-host.js +229 -118
  15. package/dist/inspector-contracts.js +28 -31
  16. package/dist/institutional-resolution.js +101 -104
  17. package/dist/ledger-session-read.js +241 -0
  18. package/dist/merger-contracts.js +66 -56
  19. package/dist/notary-contracts.js +91 -100
  20. package/dist/open-tool-schema.js +32 -31
  21. package/dist/package-contracts/collector-output.js +56 -43
  22. package/dist/package-contracts/fixer-output.js +52 -50
  23. package/dist/package-contracts/fixer-packet.js +67 -64
  24. package/dist/package-contracts/gatekeeper-output.js +48 -56
  25. package/dist/package-contracts/judge-output.js +23 -20
  26. package/dist/package-contracts/navigator-output.js +35 -40
  27. package/dist/package-contracts/reviewer-output.js +94 -110
  28. package/dist/package-contracts/terminating-infrastructure.js +62 -90
  29. package/dist/package-contracts/worker-output.js +30 -11
  30. package/dist/package-resources/engine-material.js +58 -94
  31. package/dist/public-cli/config.js +420 -484
  32. package/dist/public-cli/main.js +1219 -524
  33. package/dist/public-cli/registry.js +107 -114
  34. package/dist/receipt-delivery-policy.js +74 -76
  35. package/dist/session-opening-materials.js +44 -47
  36. package/dist/sha256.js +5 -3
  37. package/dist/sitian-appender.js +204 -210
  38. package/dist/sitian-contracts.js +15 -19
  39. package/dist/sitian-facade.js +5 -10
  40. package/dist/sitian-reader.js +37 -46
  41. package/dist/stream-idle-guard.js +63 -71
  42. package/dist/ticket-seat-memory.js +244 -0
  43. package/package.json +1 -1
  44. package/scripts/build-package.mjs +41 -0
  45. package/souls/audit-law.md +4 -0
  46. package/souls/countersign.md +1 -1
  47. package/souls/gleaner-left.md +1 -1
  48. package/souls/inspector.md +1 -1
  49. package/souls/notary.md +1 -1
  50. package/souls/reviewer.md +1 -1
  51. package/src/analyst-gate-cycles-read.ts +152 -83
  52. package/src/analyst-ledger.ts +170 -43
  53. package/src/archivist-record-entry.ts +32 -11
  54. package/src/archivist-record-topology.ts +39 -0
  55. package/src/evidence-child-executor.ts +14 -1
  56. package/src/grok/production-host.ts +44 -9
  57. package/src/host-contracts.ts +18 -0
  58. package/src/host-transition-prior-native.ts +8 -1
  59. package/src/ledger-session-read.ts +56 -0
  60. package/src/pi/durable-principal.ts +6 -0
  61. package/src/public-cli/inspector-run.ts +21 -10
  62. package/src/public-cli/invocation.ts +4 -20
  63. package/src/public-cli/notary-run.ts +21 -4
  64. package/src/public-cli/post-admission.ts +333 -42
  65. package/src/public-cli/settlement.ts +266 -81
  66. package/src/session-opening-materials.ts +4 -2
  67. package/src/ticket-seat-memory.ts +454 -0
@@ -12,6 +12,9 @@ import {
12
12
  type AnalystGateCycleRound,
13
13
  } from "../analyst-gate-cycles-read.ts";
14
14
  import { readSitianRecords, resolveSitianRecordPath, sitianReport } from "../sitian-facade.ts";
15
+ import {
16
+ resolveTicketSeatMemoryNestDirectories,
17
+ } from "../ticket-seat-memory.ts";
15
18
  import {
16
19
  readAuditEscalationSubmission,
17
20
  readLatestSubmissionOutcome,
@@ -432,6 +435,111 @@ function isTypedActivationError(
432
435
  );
433
436
  }
434
437
 
438
+ /** Flatten nested AggregateError leaves; non-aggregate values stay as one fact. */
439
+ function flattenThrownFailureLeaves(error: unknown): unknown[] {
440
+ if (!(error instanceof AggregateError)) {
441
+ return [error];
442
+ }
443
+ const leaves: unknown[] = [];
444
+ for (const item of error.errors) {
445
+ leaves.push(...flattenThrownFailureLeaves(item));
446
+ }
447
+ return leaves;
448
+ }
449
+
450
+ /**
451
+ * Project one thrown value into a ControlledFailure leaf.
452
+ * Sole owner for thrown-leaf identity/diagnostic mapping (last-host write concurrent
453
+ * facts reuse this — do not fork a second leaf mapper).
454
+ * AggregateError nesting is handled by classifyThrownFailure.
455
+ */
456
+ export function projectThrownFailureLeaf(error: unknown): ControlledFailure {
457
+ if (isTypedActivationError(error)) {
458
+ const identity = thrownIdentity(error);
459
+ if (error.failureCode !== undefined && identity.code === undefined) {
460
+ identity.code = error.failureCode;
461
+ }
462
+ return {
463
+ cause: error.knownCause,
464
+ diagnostic: error.message || error.name || "unrecognized exception",
465
+ identity,
466
+ ...(error.details === undefined ? {} : { details: error.details }),
467
+ };
468
+ }
469
+ if (error instanceof Error) {
470
+ const identity = thrownIdentity(error);
471
+ return {
472
+ cause: "unrecognized",
473
+ diagnostic: error.message || error.name || "unrecognized exception",
474
+ identity,
475
+ };
476
+ }
477
+ return {
478
+ cause: "unrecognized",
479
+ diagnostic: String(error),
480
+ };
481
+ }
482
+
483
+ /**
484
+ * Concurrent thrown failures (host + cleanup / last-host write, etc.):
485
+ * primary leaf owns cause/diagnostic/identity; remaining leaves stay as
486
+ * details.concurrentFailures so neither fact covers the other.
487
+ */
488
+ function classifyThrownFailure(error: unknown): ControlledFailure {
489
+ if (!(error instanceof AggregateError)) {
490
+ return projectThrownFailureLeaf(error);
491
+ }
492
+ const leaves = flattenThrownFailureLeaves(error);
493
+ if (leaves.length === 0) {
494
+ // Empty aggregate — retain the aggregate shell rather than invent a cause.
495
+ return projectThrownFailureLeaf(error);
496
+ }
497
+ const primary = projectThrownFailureLeaf(leaves[0]);
498
+ if (leaves.length === 1) {
499
+ return primary;
500
+ }
501
+ const priorConcurrent = Array.isArray(primary.details?.concurrentFailures)
502
+ ? primary.details.concurrentFailures
503
+ : [];
504
+ const concurrentFailures = [
505
+ ...priorConcurrent,
506
+ ...leaves.slice(1).map((leaf) => {
507
+ const secondary = projectThrownFailureLeaf(leaf);
508
+ return {
509
+ cause: secondary.cause,
510
+ diagnostic: secondary.diagnostic,
511
+ ...(secondary.identity === undefined ? {} : { identity: secondary.identity }),
512
+ ...(secondary.details === undefined ? {} : { details: secondary.details }),
513
+ };
514
+ }),
515
+ ];
516
+ return {
517
+ cause: primary.cause,
518
+ diagnostic: primary.diagnostic,
519
+ ...(primary.identity === undefined ? {} : { identity: primary.identity }),
520
+ details: {
521
+ ...(primary.details ?? {}),
522
+ concurrentFailures,
523
+ },
524
+ };
525
+ }
526
+
527
+ /** Merge caller-owned secondary evidence into a classified failure without washing path facts. */
528
+ function withKnownDetails(
529
+ failure: ControlledFailure,
530
+ knownDetails: Readonly<Record<string, unknown>> | undefined,
531
+ ): ControlledFailure {
532
+ if (knownDetails === undefined) return failure;
533
+ const { timedOut: _knownTimedOut, ...rest } = knownDetails;
534
+ return {
535
+ ...failure,
536
+ details: {
537
+ ...rest,
538
+ ...(failure.details ?? {}),
539
+ },
540
+ };
541
+ }
542
+
435
543
  /**
436
544
  * Classify a controlled post-admission failure without washing unrecognized identities.
437
545
  * Cause classes are closed; diagnostic text retains the original identity when known.
@@ -439,6 +547,7 @@ function isTypedActivationError(
439
547
  * Order: thrown → knownCause → timeout → activation (nonzero) → session → output.
440
548
  * knownCause precedes timeout so a co-present typed provider/session identity is not
441
549
  * washed when the child also timed out. Cause is never inferred from stderr wording.
550
+ * AggregateError concurrent leaves keep primary identity and secondary facts in details.
442
551
  */
443
552
  export function classifyPostAdmissionFailure(input: {
444
553
  timedOut: boolean;
@@ -468,30 +577,7 @@ export function classifyPostAdmissionFailure(input: {
468
577
  }): ControlledFailure {
469
578
  // Own-key presence, not value: `throw undefined` is a real caught exception.
470
579
  if (Object.hasOwn(input, "thrown")) {
471
- const error = input.thrown;
472
- if (isTypedActivationError(error)) {
473
- const identity = thrownIdentity(error);
474
- if (error.failureCode !== undefined && identity.code === undefined) {
475
- identity.code = error.failureCode;
476
- }
477
- return {
478
- cause: error.knownCause,
479
- diagnostic: error.message || error.name || "unrecognized exception",
480
- identity,
481
- };
482
- }
483
- if (error instanceof Error) {
484
- const identity = thrownIdentity(error);
485
- return {
486
- cause: "unrecognized",
487
- diagnostic: error.message || error.name || "unrecognized exception",
488
- identity,
489
- };
490
- }
491
- return {
492
- cause: "unrecognized",
493
- diagnostic: String(error),
494
- };
580
+ return classifyThrownFailure(input.thrown);
495
581
  }
496
582
  if (input.knownCause !== undefined) {
497
583
  const fallback =
@@ -524,39 +610,54 @@ export function classifyPostAdmissionFailure(input: {
524
610
  };
525
611
  }
526
612
  if (input.timedOut) {
527
- return {
528
- cause: "timeout",
529
- diagnostic: "role run timed out",
530
- details: { timedOut: true, exitCode: input.code },
531
- };
613
+ return withKnownDetails(
614
+ {
615
+ cause: "timeout",
616
+ diagnostic: "role run timed out",
617
+ details: { timedOut: true, exitCode: input.code },
618
+ },
619
+ input.knownDetails,
620
+ );
532
621
  }
533
622
  if (input.code !== 0) {
534
623
  const fallback = `role run failed with exit ${input.code ?? "null"}`;
535
- return {
536
- cause: "activation",
537
- diagnostic: conciseChildDiagnostic(input.stderr, fallback),
538
- details: { exitCode: input.code },
539
- };
624
+ return withKnownDetails(
625
+ {
626
+ cause: "activation",
627
+ diagnostic: conciseChildDiagnostic(input.stderr, fallback),
628
+ details: { exitCode: input.code },
629
+ },
630
+ input.knownDetails,
631
+ );
540
632
  }
541
633
  if (input.session?.state === "missing") {
542
- return {
543
- cause: "session",
544
- diagnostic: "role run left no readable session transcript",
545
- details: { exitCode: input.code, session: "missing" },
546
- };
634
+ return withKnownDetails(
635
+ {
636
+ cause: "session",
637
+ diagnostic: "role run left no readable session transcript",
638
+ details: { exitCode: input.code, session: "missing" },
639
+ },
640
+ input.knownDetails,
641
+ );
547
642
  }
548
643
  if (input.session?.state === "unreadable") {
549
- return {
550
- cause: "session",
551
- diagnostic: input.session.diagnostic,
552
- details: { exitCode: input.code, session: "unreadable" },
553
- };
644
+ return withKnownDetails(
645
+ {
646
+ cause: "session",
647
+ diagnostic: input.session.diagnostic,
648
+ details: { exitCode: input.code, session: "unreadable" },
649
+ },
650
+ input.knownDetails,
651
+ );
554
652
  }
555
- return {
556
- cause: "output",
557
- diagnostic: "role run completed without a lawful typed terminal result",
558
- details: { exitCode: input.code },
559
- };
653
+ return withKnownDetails(
654
+ {
655
+ cause: "output",
656
+ diagnostic: "role run completed without a lawful typed terminal result",
657
+ details: { exitCode: input.code },
658
+ },
659
+ input.knownDetails,
660
+ );
560
661
  }
561
662
 
562
663
  /** One projection owner for the four audited public runners. */
@@ -907,41 +1008,100 @@ async function loadBoundAuditorVolumes(
907
1008
  latestParentUserIndex = i;
908
1009
  break;
909
1010
  }
910
- const childDirectory = join(dirname(sessionFile), "auditor-roles");
911
- let names: string[];
912
- try {
913
- names = await readdir(childDirectory);
914
- } catch (error) {
915
- if (isMissingPathError(error)) return undefined;
916
- throw sessionReadFailure(error, "failed to read bound auditor session directory");
917
- }
1011
+ const runDirectory = join(dirname(sessionFile), "..");
1012
+ // #636: ticket-seat auditor memory nest via sole discovery helper.
1013
+ const memoryNests = await resolveTicketSeatMemoryNestDirectories({
1014
+ runDirectory,
1015
+ seats: ["auditor"],
1016
+ });
1017
+ const childDirectories = [
1018
+ join(dirname(sessionFile), "auditor-roles"),
1019
+ ...memoryNests,
1020
+ ];
918
1021
  // Auto-resume seam (owner A): stale check must ignore resume envelope and
919
1022
  // prioritize retention. Previous `attemptEntryIndex < latest` discarded the
920
1023
  // first attempt's child after resume advanced latest, losing retentionFailure
921
1024
  // when retry had no compliance entry. Fix: ignore envelope for staleness and
922
1025
  // prefer any valid compliance failure before falling back to primary.
923
1026
  const valid: BoundAuditorVolume[] = [];
924
- for (const file of names.filter((name) => name.endsWith(".jsonl")).sort().reverse()) {
925
- let entries: SessionEntry[];
1027
+ let sawAnyDirectory = false;
1028
+ for (const childDirectory of childDirectories) {
1029
+ let names: string[];
926
1030
  try {
927
- entries = await readBoundSessionEntries(join(childDirectory, file));
1031
+ names = await readdir(childDirectory);
1032
+ sawAnyDirectory = true;
928
1033
  } catch (error) {
929
- throw sessionReadFailure(error, "failed to read discovered auditor session");
1034
+ if (isMissingPathError(error)) continue;
1035
+ throw sessionReadFailure(error, "failed to read bound auditor session directory");
1036
+ }
1037
+ for (const file of names.filter((name) => name.endsWith(".jsonl")).sort().reverse()) {
1038
+ let entries: SessionEntry[];
1039
+ try {
1040
+ entries = await readBoundSessionEntries(join(childDirectory, file));
1041
+ } catch (error) {
1042
+ throw sessionReadFailure(error, "failed to read discovered auditor session");
1043
+ }
1044
+ const header = entries.find((entry) => entry.type === "session");
1045
+ if (!isRecord(header)) continue;
1046
+ // Ticket-seat continuous memory keeps the first parent's header.parentSession;
1047
+ // binding.parent.sessionFile is the per-summons authority (#636).
1048
+ // Each binding owns only its interval — never whole-volume provider/compliance.
1049
+ const bindingIndexes: number[] = [];
1050
+ for (let i = 0; i < entries.length; i += 1) {
1051
+ const entry = entries[i];
1052
+ if (entry?.type === "custom" && entry.customType === AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE) {
1053
+ bindingIndexes.push(i);
1054
+ }
1055
+ }
1056
+ const bindingPasses: Array<{ entry: SessionEntry | undefined; start: number; end: number }> =
1057
+ bindingIndexes.length > 0
1058
+ ? bindingIndexes.map((start, idx) => ({
1059
+ entry: entries[start],
1060
+ start,
1061
+ end: idx + 1 < bindingIndexes.length ? bindingIndexes[idx + 1]! : entries.length,
1062
+ }))
1063
+ : [{ entry: undefined, start: 0, end: entries.length }];
1064
+ for (const { entry: bindingEntry, start, end } of bindingPasses) {
1065
+ const bindingParent =
1066
+ bindingEntry !== undefined &&
1067
+ isRecord(bindingEntry.data) &&
1068
+ isRecord(bindingEntry.data.parent)
1069
+ ? bindingEntry.data.parent
1070
+ : undefined;
1071
+ const attemptEntryId =
1072
+ typeof bindingParent?.attemptEntryId === "string"
1073
+ ? bindingParent.attemptEntryId
1074
+ : undefined;
1075
+ const attemptEntryIndex =
1076
+ attemptEntryId === undefined
1077
+ ? -1
1078
+ : parentEntries.findIndex((entry) => entry.id === attemptEntryId);
1079
+ const boundSessionFile =
1080
+ typeof bindingParent?.sessionFile === "string"
1081
+ ? bindingParent.sessionFile
1082
+ : typeof header.parentSession === "string"
1083
+ ? header.parentSession
1084
+ : undefined;
1085
+ if (boundSessionFile !== sessionFile) continue;
1086
+ if (
1087
+ bindingParent !== undefined &&
1088
+ (bindingParent.sessionId !== parentId || attemptEntryIndex < latestParentUserIndex)
1089
+ ) {
1090
+ continue;
1091
+ }
1092
+ if (bindingParent === undefined && header.parentSession !== sessionFile) continue;
1093
+ valid.push({
1094
+ entries: entries.slice(start, end),
1095
+ parentId,
1096
+ sessionFile,
1097
+ ...(attemptEntryId === undefined ? {} : { attemptEntryId }),
1098
+ });
1099
+ // Keep every qualifying interval in the current parent-user range.
1100
+ // A single first-match break drops later same-user summons failures (#636).
1101
+ }
930
1102
  }
931
- const header = entries.find((entry) => entry.type === "session");
932
- if (!isRecord(header) || header.parentSession !== sessionFile) continue;
933
- const bindingEntry = entries.find((entry) => entry.type === "custom" && entry.customType === AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE);
934
- const bindingParent = isRecord(bindingEntry?.data) && isRecord(bindingEntry.data.parent) ? bindingEntry.data.parent : undefined;
935
- const attemptEntryId = typeof bindingParent?.attemptEntryId === "string" ? bindingParent.attemptEntryId : undefined;
936
- const attemptEntryIndex = attemptEntryId === undefined ? -1 : parentEntries.findIndex((entry) => entry.id === attemptEntryId);
937
- if (bindingParent?.sessionId !== parentId || bindingParent.sessionFile !== sessionFile || attemptEntryIndex < latestParentUserIndex) continue;
938
- valid.push({
939
- entries,
940
- parentId,
941
- sessionFile,
942
- ...(attemptEntryId === undefined ? {} : { attemptEntryId }),
943
- });
944
1103
  }
1104
+ if (!sawAnyDirectory && valid.length === 0) return undefined;
945
1105
  return valid;
946
1106
  }
947
1107
 
@@ -2206,16 +2366,32 @@ export function projectTerminalGateFact(
2206
2366
  }
2207
2367
 
2208
2368
  /**
2209
- * Read gate facts from the run's session/auditor-roles nest via the sole
2210
- * nested-volume reader (#446/#478). Missing directory → undefined (no-gate
2211
- * zero change). Damaged discovered volumes propagate — never wash to "no gate".
2369
+ * Read gate facts from the run's session/auditor-roles nest and, when the run
2370
+ * carries a ticket, from #636 ticket-seat memory nests via the sole nested-volume
2371
+ * reader (#446/#478). Missing directories → undefined (no-gate zero change).
2372
+ * Damaged discovered volumes propagate — never wash to "no gate".
2212
2373
  */
2213
2374
  export async function extractGateFactFromSessionDirectory(
2214
2375
  sessionDirectory: string,
2376
+ options: {
2377
+ readonly runDirectory?: string;
2378
+ readonly parentSessionFile?: string;
2379
+ } = {},
2215
2380
  ): Promise<TerminalGateFact | undefined> {
2216
- const rounds = await readAnalystGateCyclesFromAuditorRoles(
2381
+ const runDirectory = options.runDirectory ?? join(sessionDirectory, "..");
2382
+ const memoryNests = await resolveTicketSeatMemoryNestDirectories({
2383
+ runDirectory,
2384
+ seats: ["inspector", "notary"],
2385
+ });
2386
+ const directories = [
2217
2387
  join(sessionDirectory, "auditor-roles"),
2218
- );
2388
+ ...memoryNests,
2389
+ ];
2390
+ const parentSessionFile =
2391
+ options.parentSessionFile ?? join(sessionDirectory, "session.jsonl");
2392
+ const rounds = await readAnalystGateCyclesFromAuditorRoles(directories, {
2393
+ parentSessionFile,
2394
+ });
2219
2395
  return projectTerminalGateFact(rounds);
2220
2396
  }
2221
2397
 
@@ -2233,7 +2409,14 @@ async function withOptionalGateProjection<
2233
2409
  navigator: TerminalNavigatorFact;
2234
2410
  artifacts: readonly TerminalArtifactRef[];
2235
2411
  },
2236
- >(base: T, sessionDirectory: string): Promise<T & { gate?: TerminalGateFact }> {
2412
+ >(
2413
+ base: T,
2414
+ sessionDirectory: string,
2415
+ gateContext: {
2416
+ readonly runDirectory?: string;
2417
+ readonly parentSessionFile?: string;
2418
+ } = {},
2419
+ ): Promise<T & { gate?: TerminalGateFact }> {
2237
2420
  // A gate transport failure is already represented by typed evidence and has no
2238
2421
  // accepted gate cycle to project. Re-reading that rejected receipt as an
2239
2422
  // accepted cycle would replace the original failure with a projection error.
@@ -2249,7 +2432,9 @@ async function withOptionalGateProjection<
2249
2432
  || secondaryEvidence.stage === "notary"
2250
2433
  )
2251
2434
  ) return base;
2252
- const gate = await extractGateFactFromSessionDirectory(sessionDirectory);
2435
+
2436
+ // Defaults live solely in extractGateFactFromSessionDirectory — do not re-derive.
2437
+ const gate = await extractGateFactFromSessionDirectory(sessionDirectory, gateContext);
2253
2438
  return gate === undefined ? base : { ...base, gate };
2254
2439
  }
2255
2440
 
@@ -18,11 +18,13 @@ import {
18
18
  } from "./packaged-role-registry.ts";
19
19
 
20
20
  /**
21
- * Resolve the install package root from this module's location.
21
+ * Resolve the install package root from a module URL.
22
22
  * Works for src/ layout and for bundled artifacts under dist/ (walk up until
23
23
  * package.json + souls/ — the shipped material tree).
24
+ * Sole package-root walk for deferred loaders that lack an injected packageRoot
25
+ * (#443 materials; #580/#636 runtime-constructed imports).
24
26
  */
25
- function resolvePackageRootDir(moduleUrl: string = import.meta.url): string {
27
+ export function resolvePackageRootDir(moduleUrl: string = import.meta.url): string {
26
28
  let dir = dirname(fileURLToPath(moduleUrl));
27
29
  for (let i = 0; i < 8; i += 1) {
28
30
  if (existsSync(join(dir, "package.json")) && existsSync(join(dir, "souls"))) {