@akagilnc/pi-workflow-roles 0.1.1883 → 0.1.1893

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.
@@ -12,6 +12,15 @@ import {
12
12
  resolveActivationLedgerHome,
13
13
  } from "../activation-ledger-topology.ts";
14
14
  import { CliUsageError } from "./cli-errors.ts";
15
+ import {
16
+ readLatestTypedProviderHttpObservation,
17
+ } from "../typed-provider-http.ts";
18
+ export {
19
+ clearTypedProviderHttpObservation,
20
+ recordTypedProviderHttpStatus,
21
+ readLatestTypedProviderHttpObservation,
22
+ type TypedProviderHttpObservation,
23
+ } from "../typed-provider-http.ts";
15
24
  import type { FixerPhase } from "../package-contracts/fixer-output.ts";
16
25
  import type { FixerPrerequisite } from "../package-contracts/fixer-packet.ts";
17
26
  import {
@@ -65,7 +74,6 @@ export type RoleRunRecord = {
65
74
  export const RESUME_TRANSPORT_ENVELOPE = "[ak-role:resume-continue]" as const;
66
75
 
67
76
  const RUN_STATE_FILE = "run-state.json";
68
- const TYPED_HTTP_FILE = "typed-provider-http.json";
69
77
  const WRITER_LOCK_FILE = "writer.lock";
70
78
 
71
79
  export function isV1ResumableProvider(
@@ -74,84 +82,14 @@ export function isV1ResumableProvider(
74
82
  return (V1_RESUMABLE_PROVIDERS as readonly string[]).includes(provider);
75
83
  }
76
84
 
77
- function typedProviderHttpPath(runDirectory: string): string {
78
- return join(runDirectory, TYPED_HTTP_FILE);
79
- }
80
-
81
- /**
82
- * Clear any prior attempt's typed provider HTTP observation.
83
- * Each initial/resume dispatch must start without inherited 429 evidence so
84
- * only the current attempt can qualify v1 resume.
85
- */
86
- export async function clearTypedProviderHttpObservation(
87
- runDirectory: string,
88
- ): Promise<void> {
89
- try {
90
- await unlink(typedProviderHttpPath(runDirectory));
91
- } catch (error) {
92
- if (
93
- error instanceof Error &&
94
- "code" in error &&
95
- (error as { code?: unknown }).code === "ENOENT"
96
- ) {
97
- return;
98
- }
99
- throw error;
100
- }
101
- }
102
-
103
- /**
104
- * Record a typed provider HTTP status observation for the admitted run.
105
- * The latest observation is authoritative: only a current HTTP 429 from
106
- * Codex/xAI is retained for v1 resume; any other status or provider clears
107
- * prior within-attempt 429 evidence. Never inspects diagnostic prose.
108
- */
109
- export async function recordTypedProviderHttpStatus(
110
- runDirectory: string,
111
- observation: { readonly httpStatus: number; readonly provider: string },
112
- ): Promise<void> {
113
- if (
114
- observation.httpStatus === 429 &&
115
- isV1ResumableProvider(observation.provider)
116
- ) {
117
- const body: TypedHttp429Observation = {
118
- httpStatus: 429,
119
- provider: observation.provider,
120
- };
121
- await writeFile(
122
- typedProviderHttpPath(runDirectory),
123
- `${JSON.stringify(body)}\n`,
124
- "utf8",
125
- );
126
- return;
127
- }
128
- // Non-qualifying latest response supersedes any earlier 429 in this attempt.
129
- await clearTypedProviderHttpObservation(runDirectory);
130
- }
131
-
132
- /**
133
- * Read a durable typed HTTP 429 observation. Returns undefined unless both
134
- * httpStatus === 429 and provider is a v1-resumable provider are present as
135
- * typed fields (never inferred from prose).
136
- */
137
85
  export async function readTypedHttp429Observation(
138
86
  runDirectory: string,
139
87
  ): Promise<TypedHttp429Observation | undefined> {
140
- try {
141
- const raw: unknown = JSON.parse(
142
- await readFile(typedProviderHttpPath(runDirectory), "utf8"),
143
- );
144
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
145
- return undefined;
146
- }
147
- const record = raw as Record<string, unknown>;
148
- if (record.httpStatus !== 429) return undefined;
149
- if (typeof record.provider !== "string") return undefined;
150
- if (!isV1ResumableProvider(record.provider)) return undefined;
151
- return { httpStatus: 429, provider: record.provider };
152
- } catch {
153
- return undefined;
154
- }
88
+ const observation = await readLatestTypedProviderHttpObservation(runDirectory);
89
+ if (observation === undefined) return undefined;
90
+ if (observation.httpStatus !== 429) return undefined;
91
+ if (!isV1ResumableProvider(observation.provider)) return undefined;
92
+ return { httpStatus: 429, provider: observation.provider };
155
93
  }
156
94
 
157
95
  /**
@@ -13,6 +13,13 @@ import { DOCTOR_AUDIT_TOOL_NAME } from "../doctor-auditor.ts";
13
13
  import { JUDGE_AUDIT_TOOL_NAME } from "../judge-auditor.ts";
14
14
  import { REVIEWER_AUDIT_TOOL_NAME } from "../reviewer-auditor.ts";
15
15
  import { knownFailureFromProviderStop, type ExplicitInternalKnownFailure, readReviewerDispatchRejection } from "./explicit-internal.ts";
16
+ import {
17
+ isV1ResumableProvider,
18
+ readLatestTypedProviderHttpObservation,
19
+ readTypedHttp429Observation,
20
+ type TypedHttp429Observation,
21
+ type TypedProviderHttpObservation,
22
+ } from "./run-lifecycle.ts";
16
23
  import {
17
24
  AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE,
18
25
  AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE,
@@ -367,14 +374,16 @@ export function classifyPostAdmissionFailure(input: {
367
374
  input.knownDiagnostic !== undefined && input.knownDiagnostic.trim() !== ""
368
375
  ? input.knownDiagnostic
369
376
  : conciseChildDiagnostic(input.stderr, fallback);
370
- const { code: _knownCode, timedOut: _knownTimedOut, ...knownDetails } =
377
+ const { timedOut: _knownTimedOut, ...knownDetails } =
371
378
  input.knownDetails ?? {};
379
+ const remoteCode = knownDetails.code;
372
380
  return {
373
381
  cause: input.knownCause,
374
382
  diagnostic,
375
383
  details: {
376
384
  ...knownDetails,
377
- code: input.code,
385
+ ...(remoteCode === undefined ? {} : { code: remoteCode }),
386
+ exitCode: input.code,
378
387
  ...(input.timedOut ? { timedOut: true as const } : {}),
379
388
  },
380
389
  ...(input.knownIdentity === undefined
@@ -386,7 +395,7 @@ export function classifyPostAdmissionFailure(input: {
386
395
  return {
387
396
  cause: "timeout",
388
397
  diagnostic: "role run timed out",
389
- details: { timedOut: true, code: input.code },
398
+ details: { timedOut: true, exitCode: input.code },
390
399
  };
391
400
  }
392
401
  if (input.code !== 0) {
@@ -394,27 +403,27 @@ export function classifyPostAdmissionFailure(input: {
394
403
  return {
395
404
  cause: "activation",
396
405
  diagnostic: conciseChildDiagnostic(input.stderr, fallback),
397
- details: { code: input.code },
406
+ details: { exitCode: input.code },
398
407
  };
399
408
  }
400
409
  if (input.session?.state === "missing") {
401
410
  return {
402
411
  cause: "session",
403
412
  diagnostic: "role run left no readable session transcript",
404
- details: { code: input.code, session: "missing" },
413
+ details: { exitCode: input.code, session: "missing" },
405
414
  };
406
415
  }
407
416
  if (input.session?.state === "unreadable") {
408
417
  return {
409
418
  cause: "session",
410
419
  diagnostic: input.session.diagnostic,
411
- details: { code: input.code, session: "unreadable" },
420
+ details: { exitCode: input.code, session: "unreadable" },
412
421
  };
413
422
  }
414
423
  return {
415
424
  cause: "output",
416
425
  diagnostic: "role run completed without a lawful typed terminal result",
417
- details: { code: input.code },
426
+ details: { exitCode: input.code },
418
427
  };
419
428
  }
420
429
 
@@ -448,6 +457,15 @@ type SessionMessage = {
448
457
  provider?: string;
449
458
  model?: string;
450
459
  api?: string;
460
+ rawStopReason?: string;
461
+ diagnostics?: unknown;
462
+ /** Typed HTTP / SDK structured fields when held on the call surface. */
463
+ statusCode?: number;
464
+ status?: number;
465
+ httpStatus?: number;
466
+ body?: unknown;
467
+ code?: unknown;
468
+ errno?: unknown;
451
469
  };
452
470
 
453
471
  type SessionEntry = {
@@ -546,19 +564,65 @@ async function readBoundSessionEntries(
546
564
  }
547
565
 
548
566
  /**
549
- * Latest native assistant provider-stop in a session (stopReason === "error").
567
+ * Latest native assistant provider-stop in a session (stopReason error|aborted).
550
568
  * Only the final assistant turn decides terminality — an older error followed by a
551
569
  * later non-error stop is not a provider failure (would wash a no-lawful-output path).
552
570
  * Typed production source for provider cause — not child stderr prose.
553
571
  */
554
- export function extractSessionProviderStop(
555
- entries: readonly SessionEntry[],
556
- ): {
557
- stopReason: "error";
572
+ type SessionProviderStop = {
573
+ stopReason: "error" | "aborted";
558
574
  errorMessage?: string;
559
575
  provider?: string;
560
576
  model?: string;
561
- } | undefined {
577
+ api?: string;
578
+ rawStopReason?: string;
579
+ diagnostics?: unknown;
580
+ httpStatus?: number;
581
+ body?: unknown;
582
+ code?: unknown;
583
+ errno?: unknown;
584
+ };
585
+
586
+ function typedHttpStatusFromMessage(message: SessionMessage): number | undefined {
587
+ for (const candidate of [message.httpStatus, message.statusCode, message.status]) {
588
+ if (typeof candidate === "number" && (candidate < 200 || candidate >= 300)) return candidate;
589
+ }
590
+ return undefined;
591
+ }
592
+
593
+ function sessionProviderStopFromAssistant(message: SessionMessage | undefined): SessionProviderStop | undefined {
594
+ if (message?.role !== "assistant") return undefined;
595
+ if (message.stopReason !== "error" && message.stopReason !== "aborted") return undefined;
596
+ const httpStatus = typedHttpStatusFromMessage(message);
597
+ return {
598
+ stopReason: message.stopReason,
599
+ // Preserve held errorMessage bytes — emptiness check must not rewrite.
600
+ ...(typeof message.errorMessage === "string" && message.errorMessage.trim() !== ""
601
+ ? { errorMessage: message.errorMessage }
602
+ : {}),
603
+ ...(typeof message.provider === "string" && message.provider.trim() !== ""
604
+ ? { provider: message.provider }
605
+ : {}),
606
+ ...(typeof message.model === "string" && message.model.trim() !== ""
607
+ ? { model: message.model }
608
+ : {}),
609
+ ...(typeof message.api === "string" && message.api.trim() !== ""
610
+ ? { api: message.api }
611
+ : {}),
612
+ ...(typeof message.rawStopReason === "string" && message.rawStopReason.trim() !== ""
613
+ ? { rawStopReason: message.rawStopReason }
614
+ : {}),
615
+ ...(message.diagnostics === undefined ? {} : { diagnostics: message.diagnostics }),
616
+ ...(httpStatus === undefined ? {} : { httpStatus }),
617
+ ...(message.body === undefined ? {} : { body: message.body }),
618
+ ...(message.code === undefined ? {} : { code: message.code }),
619
+ ...(message.errno === undefined ? {} : { errno: message.errno }),
620
+ };
621
+ }
622
+
623
+ export function extractSessionProviderStop(
624
+ entries: readonly SessionEntry[],
625
+ ): SessionProviderStop | undefined {
562
626
  // A resumed dispatch appends a typed top-level user turn to the same session.
563
627
  // Retained audit state from an older attempt must not replace the newer attempt's
564
628
  // native provider stop. Sessions without a user turn are the initial attempt.
@@ -578,14 +642,8 @@ export function extractSessionProviderStop(
578
642
  const entry = entries[i];
579
643
  if (entry?.type !== "custom" || entry.customType !== COMPLIANCE_RESPONSE_ENTRY_TYPE) continue;
580
644
  const response = isRecord(entry.data) && isRecord(entry.data.response) ? entry.data.response : undefined;
581
- if (response?.role === "assistant" && response.stopReason === "error") {
582
- return {
583
- stopReason: "error",
584
- ...(typeof response.errorMessage === "string" && response.errorMessage.trim() !== "" ? { errorMessage: response.errorMessage } : {}),
585
- ...(typeof response.provider === "string" && response.provider.trim() !== "" ? { provider: response.provider } : {}),
586
- ...(typeof response.model === "string" && response.model.trim() !== "" ? { model: response.model } : {}),
587
- };
588
- }
645
+ const stop = sessionProviderStopFromAssistant(response);
646
+ if (stop !== undefined) return stop;
589
647
  break;
590
648
  }
591
649
  for (let i = entries.length - 1; i >= attemptStart; i -= 1) {
@@ -594,19 +652,7 @@ export function extractSessionProviderStop(
594
652
  const message = entry.message;
595
653
  if (message?.role !== "assistant") continue;
596
654
  // Latest assistant in the current attempt only (reviewer-child-executor lastAssistant pattern).
597
- if (message.stopReason !== "error") return undefined;
598
- return {
599
- stopReason: "error",
600
- ...(typeof message.errorMessage === "string" && message.errorMessage.trim() !== ""
601
- ? { errorMessage: message.errorMessage }
602
- : {}),
603
- ...(typeof message.provider === "string" && message.provider.trim() !== ""
604
- ? { provider: message.provider }
605
- : {}),
606
- ...(typeof message.model === "string" && message.model.trim() !== ""
607
- ? { model: message.model }
608
- : {}),
609
- };
655
+ return sessionProviderStopFromAssistant(message);
610
656
  }
611
657
  return undefined;
612
658
  }
@@ -614,15 +660,7 @@ export function extractSessionProviderStop(
614
660
  /** Read the bound session principal and extract a typed provider-stop, if any. */
615
661
  export async function readSessionProviderStop(
616
662
  sessionFile: string,
617
- ): Promise<
618
- | {
619
- stopReason: "error";
620
- errorMessage?: string;
621
- provider?: string;
622
- model?: string;
623
- }
624
- | undefined
625
- > {
663
+ ): Promise<SessionProviderStop | undefined> {
626
664
  try {
627
665
  const entries = await readBoundSessionEntries(sessionFile);
628
666
  return extractSessionProviderStop(entries);
@@ -662,8 +700,7 @@ export async function readBoundEvidenceChildKnownFailure(
662
700
  return {
663
701
  ...primary,
664
702
  details: {
665
- ...(stop.provider === undefined ? {} : { provider: stop.provider }),
666
- ...(stop.model === undefined ? {} : { model: stop.model }),
703
+ ...(primary.details ?? {}),
667
704
  secondaryEvidence: "evidence-child",
668
705
  },
669
706
  };
@@ -721,10 +758,10 @@ export async function readBoundAuditorKnownFailure(
721
758
  if (entry?.type !== "custom" || entry.customType !== AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE || !isRecord(entry.data)) continue;
722
759
  const parent = isRecord(entry.data.parent) ? entry.data.parent : undefined;
723
760
  const failure = isRecord(entry.data.failure) ? entry.data.failure : undefined;
724
- if (parent?.sessionId !== parentId || parent.sessionFile !== sessionFile || parent.attemptEntryId !== attemptEntryId || failure?.cause !== "provider") continue;
761
+ if (parent?.sessionId !== parentId || parent.sessionFile !== sessionFile || parent.attemptEntryId !== attemptEntryId || (failure?.cause !== "provider" && failure?.cause !== "unrecognized")) continue;
725
762
  const identity = isRecord(failure.identity) ? failure.identity : undefined;
726
763
  return {
727
- cause: "provider",
764
+ cause: failure.cause === "provider" ? "provider" : "unrecognized",
728
765
  ...(identity === undefined ? {} : { identity: {
729
766
  ...(typeof identity.name === "string" ? { name: identity.name } : {}),
730
767
  ...(typeof identity.code === "string" || typeof identity.code === "number" ? { code: identity.code } : {}),
@@ -737,8 +774,7 @@ export async function readBoundAuditorKnownFailure(
737
774
  return {
738
775
  ...primary,
739
776
  details: {
740
- ...(stop.provider === undefined ? {} : { provider: stop.provider }),
741
- ...(stop.model === undefined ? {} : { model: stop.model }),
777
+ ...(primary.details ?? {}),
742
778
  secondaryEvidence: "unavailable",
743
779
  },
744
780
  };
@@ -778,26 +814,57 @@ function typedFailedTerminatingToolKnownFailure(
778
814
  return undefined;
779
815
  }
780
816
 
817
+ /**
818
+ * One audited-runner resolution: knownFailure plus the typed-HTTP sidecar outcome
819
+ * from the same read. Callers that also decide v1 resume must consume this once —
820
+ * never re-read the sidecar in presentControlledFailure.
821
+ */
822
+ export type AuditedRunnerFailureResolution = {
823
+ readonly knownFailure?: ExplicitInternalKnownFailure;
824
+ /** Successful sidecar read (not absence). */
825
+ readonly typedHttpObservation?: TypedProviderHttpObservation;
826
+ /**
827
+ * True when this resolution already performed the typed-HTTP sidecar read
828
+ * (success, absence, or non-absence failure folded into knownFailure).
829
+ * False when an earlier evidence tier short-circuited before the sidecar.
830
+ */
831
+ readonly typedHttpObservationSettled: boolean;
832
+ };
833
+
834
+ function resolutionOf(
835
+ knownFailure: ExplicitInternalKnownFailure | undefined,
836
+ typedHttp: {
837
+ readonly settled: boolean;
838
+ readonly observation?: TypedProviderHttpObservation;
839
+ } = { settled: false },
840
+ ): AuditedRunnerFailureResolution {
841
+ return {
842
+ ...(knownFailure === undefined ? {} : { knownFailure }),
843
+ ...(typedHttp.observation === undefined ? {} : { typedHttpObservation: typedHttp.observation }),
844
+ typedHttpObservationSettled: typedHttp.settled,
845
+ };
846
+ }
847
+
781
848
  /** Sole evidence-priority owner for public runners with Soul auditors. */
782
- export async function resolveAuditedRunnerKnownFailure(input: {
849
+ export async function resolveAuditedRunnerFailureResolution(input: {
783
850
  runner: ExplicitInternalKnownFailure | undefined;
784
851
  sessionFile: string;
785
852
  credential: ExplicitInternalKnownFailure | undefined;
786
853
  /** Reviewer only: recover child-written rejection page into knownFailure.details. */
787
854
  runDirectory?: string;
788
- }): Promise<ExplicitInternalKnownFailure | undefined> {
789
- if (input.runner !== undefined) return input.runner;
855
+ }): Promise<AuditedRunnerFailureResolution> {
856
+ if (input.runner !== undefined) return resolutionOf(input.runner);
790
857
  if (input.runDirectory !== undefined) {
791
858
  try {
792
859
  const rejection = await readReviewerDispatchRejection(input.runDirectory);
793
- if (rejection !== undefined) return rejection;
860
+ if (rejection !== undefined) return resolutionOf(rejection);
794
861
  } catch (error) {
795
862
  const failure = error instanceof Error ? error : new Error(String(error));
796
- return {
863
+ return resolutionOf({
797
864
  cause: "activation",
798
865
  identity: thrownIdentity(failure),
799
866
  diagnostic: failure.message || failure.name,
800
- };
867
+ });
801
868
  }
802
869
  }
803
870
  // Bound auditor evidence outranks a parent failure that the auditor path itself
@@ -805,24 +872,28 @@ export async function resolveAuditedRunnerKnownFailure(input: {
805
872
  // it outranks provider/credential and nonzero fallbacks, but not its recorded cause.
806
873
  try {
807
874
  const auditorFailure = await readBoundAuditorKnownFailure(input.sessionFile);
808
- if (auditorFailure !== undefined) return auditorFailure;
875
+ if (auditorFailure !== undefined) return resolutionOf(auditorFailure);
809
876
  } catch (error) {
810
877
  const failure = sessionReadFailure(error, "failed to recover bound auditor failure");
811
- return {
878
+ return resolutionOf({
812
879
  cause: "session",
813
880
  identity: thrownIdentity(failure),
814
881
  diagnostic: failure.message || failure.name,
815
- };
882
+ });
816
883
  }
817
884
  try {
818
885
  const terminatingFailure = typedFailedTerminatingToolKnownFailure(
819
886
  await readBoundSessionEntries(input.sessionFile),
820
887
  );
821
- if (terminatingFailure !== undefined) return terminatingFailure;
888
+ if (terminatingFailure !== undefined) return resolutionOf(terminatingFailure);
822
889
  } catch (error) {
823
890
  if (!isMissingPathError(error)) {
824
891
  const failure = sessionReadFailure(error, "failed to recover typed terminating-tool failure");
825
- return { cause: "session", identity: thrownIdentity(failure), diagnostic: failure.message || failure.name };
892
+ return resolutionOf({
893
+ cause: "session",
894
+ identity: thrownIdentity(failure),
895
+ diagnostic: failure.message || failure.name,
896
+ });
826
897
  }
827
898
  }
828
899
  // Reviewer axis evidence-children are next: fixed two-axis dispatch fails
@@ -830,19 +901,138 @@ export async function resolveAuditedRunnerKnownFailure(input: {
830
901
  // fallback; credential is last.
831
902
  try {
832
903
  const evidenceChildFailure = await readBoundEvidenceChildKnownFailure(input.sessionFile);
833
- if (evidenceChildFailure !== undefined) return evidenceChildFailure;
904
+ if (evidenceChildFailure !== undefined) return resolutionOf(evidenceChildFailure);
834
905
  } catch (error) {
835
906
  const failure = sessionReadFailure(error, "failed to recover bound evidence-child failure");
836
- return {
907
+ return resolutionOf({
837
908
  cause: "session",
838
909
  identity: thrownIdentity(failure),
839
910
  diagnostic: failure.message || failure.name,
840
- };
911
+ });
841
912
  }
842
913
  const parentStop = await readSessionProviderStop(input.sessionFile);
843
- return parentStop === undefined
844
- ? input.credential
845
- : knownFailureFromProviderStop(parentStop);
914
+ // Typed HTTP observation: ENOENT=absence; other read/parse/shape failures keep real cause.
915
+ // This is the single sidecar read for both knownFailure projection and v1 resume.
916
+ let httpObservation: TypedProviderHttpObservation | undefined;
917
+ if (input.runDirectory !== undefined) {
918
+ try {
919
+ httpObservation = await readLatestTypedProviderHttpObservation(input.runDirectory);
920
+ } catch (error) {
921
+ const failure = error instanceof Error ? error : new Error(String(error));
922
+ return resolutionOf(
923
+ {
924
+ cause: "session",
925
+ identity: thrownIdentity(failure),
926
+ diagnostic: failure.message || failure.name,
927
+ },
928
+ { settled: true },
929
+ );
930
+ }
931
+ }
932
+ const typedHttp = {
933
+ settled: input.runDirectory !== undefined,
934
+ ...(httpObservation === undefined ? {} : { observation: httpObservation }),
935
+ };
936
+ if (parentStop === undefined) {
937
+ if (input.credential !== undefined) return resolutionOf(input.credential, typedHttp);
938
+ if (httpObservation === undefined) return resolutionOf(undefined, typedHttp);
939
+ // Project the HTTP observation's status + provider/source association.
940
+ return resolutionOf(
941
+ knownFailureFromProviderStop({
942
+ stopReason: "error",
943
+ httpStatus: httpObservation.httpStatus,
944
+ provider: httpObservation.provider,
945
+ }),
946
+ typedHttp,
947
+ );
948
+ }
949
+ return resolutionOf(
950
+ knownFailureFromProviderStop({
951
+ ...parentStop,
952
+ ...(httpObservation === undefined
953
+ ? {}
954
+ : {
955
+ httpStatus: httpObservation.httpStatus,
956
+ // Observation association outranks session-configured provider name alone.
957
+ provider: httpObservation.provider,
958
+ }),
959
+ }),
960
+ typedHttp,
961
+ );
962
+ }
963
+
964
+ /** Sole evidence-priority owner for public runners with Soul auditors. */
965
+ export async function resolveAuditedRunnerKnownFailure(input: {
966
+ runner: ExplicitInternalKnownFailure | undefined;
967
+ sessionFile: string;
968
+ credential: ExplicitInternalKnownFailure | undefined;
969
+ /** Reviewer only: recover child-written rejection page into knownFailure.details. */
970
+ runDirectory?: string;
971
+ }): Promise<ExplicitInternalKnownFailure | undefined> {
972
+ return (await resolveAuditedRunnerFailureResolution(input)).knownFailure;
973
+ }
974
+
975
+ /**
976
+ * v1 resume observation for controlled-failure settlement — at most one sidecar read.
977
+ * Prefer the pre-resolved outcome from resolveAuditedRunnerFailureResolution.
978
+ * Non-absence failures never throw: they return observationReadFailure for the
979
+ * existing controlled-failure → error.json chain.
980
+ */
981
+ export async function resolveControlledFailureResumeObservation(input: {
982
+ readonly runDirectory: string;
983
+ readonly typedHttpObservationSettled?: boolean;
984
+ readonly typedHttpObservation?: TypedProviderHttpObservation;
985
+ }): Promise<{
986
+ readonly typedHttp429?: TypedHttp429Observation;
987
+ readonly observationReadFailure?: ExplicitInternalKnownFailure;
988
+ }> {
989
+ if (input.typedHttpObservationSettled === true) {
990
+ const observation = input.typedHttpObservation;
991
+ if (
992
+ observation !== undefined &&
993
+ observation.httpStatus === 429 &&
994
+ isV1ResumableProvider(observation.provider)
995
+ ) {
996
+ return {
997
+ typedHttp429: { httpStatus: 429, provider: observation.provider },
998
+ };
999
+ }
1000
+ return {};
1001
+ }
1002
+ try {
1003
+ const typedHttp429 = await readTypedHttp429Observation(input.runDirectory);
1004
+ return typedHttp429 === undefined ? {} : { typedHttp429 };
1005
+ } catch (error) {
1006
+ const failure = error instanceof Error ? error : new Error(String(error));
1007
+ return {
1008
+ observationReadFailure: {
1009
+ cause: "session",
1010
+ identity: thrownIdentity(failure),
1011
+ diagnostic: failure.message || failure.name,
1012
+ },
1013
+ };
1014
+ }
1015
+ }
1016
+
1017
+ /** Spread into presentControlledFailure failureInput from one audited resolution. */
1018
+ export function controlledFailureInputFromResolution(
1019
+ resolution: AuditedRunnerFailureResolution,
1020
+ ): {
1021
+ knownFailure?: ExplicitInternalKnownFailure;
1022
+ typedHttpObservationSettled?: true;
1023
+ typedHttpObservation?: TypedProviderHttpObservation;
1024
+ } {
1025
+ return {
1026
+ ...(resolution.knownFailure === undefined ? {} : { knownFailure: resolution.knownFailure }),
1027
+ ...(resolution.typedHttpObservationSettled
1028
+ ? {
1029
+ typedHttpObservationSettled: true as const,
1030
+ ...(resolution.typedHttpObservation === undefined
1031
+ ? {}
1032
+ : { typedHttpObservation: resolution.typedHttpObservation }),
1033
+ }
1034
+ : {}),
1035
+ };
846
1036
  }
847
1037
 
848
1038
  function isRecord(value: unknown): value is Record<string, unknown> {
@@ -14,7 +14,7 @@ export type ReviewerChildExecuteOptions = Readonly<{
14
14
  export function projectSharedChildFailure(error: unknown): unknown {
15
15
  if (typeof error === "object" && error !== null && "evidenceChildFailure" in error) {
16
16
  const classification = (error as { evidenceChildFailure?: unknown }).evidenceChildFailure;
17
- if (classification === "provider" || classification === "child") {
17
+ if (classification === "provider" || classification === "child" || classification === "unknown") {
18
18
  Object.assign(error, { reviewerFailure: classification });
19
19
  }
20
20
  }
@@ -44,7 +44,7 @@ import {
44
44
  NAVIGATOR_INVOCATION_ENTRY,
45
45
  resolveLifecycleInvocationPrincipal,
46
46
  } from "./navigator-invocation-identity.ts";
47
- import { recordTypedProviderHttpStatus } from "./public-cli/run-lifecycle.ts";
47
+ import { recordTypedProviderHttpStatus } from "./typed-provider-http.ts";
48
48
  import {
49
49
  ExplicitInternalActivationError,
50
50
  recordReviewerDispatchRejectionSync,
@@ -807,22 +807,32 @@ export function createRoleRuntimeExtension(
807
807
  await observe(() => observationFace.onEnd(event), ctx);
808
808
  });
809
809
 
810
- // Public Role run: record typed provider HTTP status for v1 resume (#108).
811
- // Latest typed response is authoritative; only a current Codex/xAI 429 is
812
- // retained — never prose classification or an earlier within-attempt 429.
810
+ // Public Role run: record typed non-success HTTP for error evidence + v1 resume.
811
+ // 2xx clears prior observation (see recordTypedProviderHttpStatus). Non-success
812
+ // write failure must surface — never silently drop authorized error evidence.
813
813
  pi.on("after_provider_response", async (event, ctx) => {
814
814
  const runDir = process.env.AK_ROLE_RUN_DIR;
815
815
  if (typeof runDir !== "string" || runDir.trim() === "") return;
816
816
  const provider = ctx.model?.provider;
817
817
  if (typeof provider !== "string" || provider.trim() === "") return;
818
+ const status = event.status;
819
+ if (typeof status !== "number") return;
818
820
  try {
819
821
  await recordTypedProviderHttpStatus(runDir, {
820
- httpStatus: event.status,
822
+ httpStatus: status,
821
823
  provider,
822
824
  });
823
- } catch {
824
- // Observation is best-effort relative to the live provider stream;
825
- // settlement still owns failure presentation.
825
+ } catch (error) {
826
+ if (
827
+ status >= 200 &&
828
+ status < 300 &&
829
+ error instanceof Error &&
830
+ "code" in error &&
831
+ (error as { code?: unknown }).code === "ENOENT"
832
+ ) {
833
+ return;
834
+ }
835
+ failInfrastructure(error, ctx);
826
836
  }
827
837
  });
828
838