@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.
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Shared durable typed-provider HTTP observation owner.
3
+ * Published with both public-cli and navigator-attendance entries.
4
+ */
5
+ import { readFile, unlink, writeFile } from "node:fs/promises";
6
+ import { join } from "node:path";
7
+
8
+ const TYPED_HTTP_FILE = "typed-provider-http.json";
9
+
10
+ export type TypedProviderHttpObservation = {
11
+ readonly httpStatus: number;
12
+ readonly provider: string;
13
+ };
14
+
15
+ function typedProviderHttpPath(runDirectory: string): string {
16
+ return join(runDirectory, TYPED_HTTP_FILE);
17
+ }
18
+
19
+ /**
20
+ * Clear any prior attempt's typed provider HTTP observation.
21
+ * Each initial/resume dispatch must start without inherited 429 evidence so
22
+ * only the current attempt can qualify v1 resume.
23
+ */
24
+ export async function clearTypedProviderHttpObservation(
25
+ runDirectory: string,
26
+ ): Promise<void> {
27
+ try {
28
+ await unlink(typedProviderHttpPath(runDirectory));
29
+ } catch (error) {
30
+ if (
31
+ error instanceof Error &&
32
+ "code" in error &&
33
+ (error as { code?: unknown }).code === "ENOENT"
34
+ ) {
35
+ return;
36
+ }
37
+ throw error;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Record a typed provider HTTP status observation for the admitted run.
43
+ * Only non-success HTTP statuses are persisted (authorized error evidence).
44
+ * A 2xx response clears any prior observation so success cannot leave stale
45
+ * error evidence. Never inspects diagnostic prose.
46
+ */
47
+ export async function recordTypedProviderHttpStatus(
48
+ runDirectory: string,
49
+ observation: { readonly httpStatus: number; readonly provider: string },
50
+ ): Promise<void> {
51
+ if (observation.httpStatus >= 200 && observation.httpStatus < 300) {
52
+ await clearTypedProviderHttpObservation(runDirectory);
53
+ return;
54
+ }
55
+ const body: TypedProviderHttpObservation = {
56
+ httpStatus: observation.httpStatus,
57
+ provider: observation.provider,
58
+ };
59
+ await writeFile(
60
+ typedProviderHttpPath(runDirectory),
61
+ `${JSON.stringify(body)}\n`,
62
+ "utf8",
63
+ );
64
+ }
65
+
66
+ /**
67
+ * Read the latest durable typed provider HTTP observation, if any.
68
+ * Only ENOENT means absence (undefined). Read/parse/shape failures preserve the
69
+ * real cause for the existing controlled-failure chain — never laundered as absence.
70
+ */
71
+ export async function readLatestTypedProviderHttpObservation(
72
+ runDirectory: string,
73
+ ): Promise<TypedProviderHttpObservation | undefined> {
74
+ let text: string;
75
+ try {
76
+ text = await readFile(typedProviderHttpPath(runDirectory), "utf8");
77
+ } catch (error) {
78
+ if (
79
+ error instanceof Error &&
80
+ "code" in error &&
81
+ (error as { code?: unknown }).code === "ENOENT"
82
+ ) {
83
+ return undefined;
84
+ }
85
+ throw error;
86
+ }
87
+ const raw: unknown = JSON.parse(text);
88
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
89
+ throw new Error("typed provider HTTP observation must be a JSON object");
90
+ }
91
+ const record = raw as Record<string, unknown>;
92
+ if (typeof record.httpStatus !== "number") {
93
+ throw new Error("typed provider HTTP observation missing numeric httpStatus");
94
+ }
95
+ if (typeof record.provider !== "string" || record.provider.trim() === "") {
96
+ throw new Error("typed provider HTTP observation missing non-empty provider");
97
+ }
98
+ return { httpStatus: record.httpStatus, provider: record.provider };
99
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Single authority for upstream-error testimony (#307).
3
+ * Non-2xx HTTP or non-empty SDK diagnostics = upstream testimony.
4
+ * body/code/errno project only from a confirmed-remote node.
5
+ * Seams keep their own shape/cause-chain reading and wrap via these primitives.
6
+ */
7
+
8
+ /** True when value is a numeric HTTP status outside 2xx. */
9
+ export function isNonSuccessHttpStatus(status: unknown): status is number {
10
+ return typeof status === "number" && (status < 200 || status >= 300);
11
+ }
12
+
13
+ /**
14
+ * Upstream testimony is a directly observed HTTP response or SDK-structured
15
+ * remote error. Pi stopReason, selected provider/model names, silent stream
16
+ * death, errorMessage prose (e.g. "500: …"), and locally synthesized errors
17
+ * are not testimony.
18
+ */
19
+ export function hasUpstreamErrorTestimony(input: {
20
+ readonly httpStatus?: number;
21
+ readonly diagnostics?: unknown;
22
+ }): boolean {
23
+ if (isNonSuccessHttpStatus(input.httpStatus)) return true;
24
+ return Array.isArray(input.diagnostics) && input.diagnostics.length > 0;
25
+ }
26
+
27
+ /**
28
+ * Project body/code/errno only when the caller already confirmed remote testimony
29
+ * on the same node. Plain local Error.code is never provider testimony.
30
+ */
31
+ export function projectConfirmedRemotePayload(input: {
32
+ readonly body?: unknown;
33
+ readonly code?: unknown;
34
+ readonly errno?: unknown;
35
+ }): {
36
+ readonly body?: unknown;
37
+ readonly code?: unknown;
38
+ readonly errno?: unknown;
39
+ } {
40
+ return {
41
+ ...(input.body === undefined ? {} : { body: input.body }),
42
+ ...(input.code === undefined ? {} : { code: input.code }),
43
+ ...(input.errno === undefined ? {} : { errno: input.errno }),
44
+ };
45
+ }