@akagilnc/pi-workflow-roles 0.1.5406 → 0.1.5475

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 (93) hide show
  1. package/CLAUDE.md +1 -1
  2. package/README.md +42 -55
  3. package/README.zh-CN.md +59 -73
  4. package/THIRD_PARTY_NOTICES.md +4 -7
  5. package/dist/acp-host/production-host.js +8261 -9073
  6. package/dist/collector-tool-schemas.js +1 -1
  7. package/dist/compliance-transport.js +2 -5
  8. package/dist/engine-detour-usage.js +3 -30
  9. package/dist/gatekeeper-role.js +4 -18
  10. package/dist/headless-host/description.js +0 -209
  11. package/dist/headless-host/production-host.js +6973 -8035
  12. package/dist/host-contracts.js +8 -0
  13. package/dist/migrate-book-topology.js +30 -27
  14. package/dist/packaged-role-registry.js +1 -9
  15. package/dist/pi/role-turn-host.js +37 -19
  16. package/dist/public-cli/auto-resume.js +29 -66
  17. package/dist/public-cli/countersign-run.js +4 -18
  18. package/dist/public-cli/instruction-seat-run.js +59 -76
  19. package/dist/public-cli/main.js +9844 -10720
  20. package/dist/public-cli/option-definitions.js +1 -1
  21. package/dist/public-cli/post-admission.js +204 -285
  22. package/dist/public-cli/process-cancel.js +8 -0
  23. package/dist/public-cli/run-lifecycle.js +24 -94
  24. package/dist/public-cli/settlement.js +614 -1524
  25. package/dist/public-cli/terminal.js +23 -53
  26. package/dist/public-role-summons.js +3 -8
  27. package/dist/receipt-delivery-policy.js +11 -12
  28. package/dist/run-terminal-artifacts.js +19 -14
  29. package/dist/serialize-thrown-value.js +7 -1
  30. package/dist/submission-ledger.js +21 -89
  31. package/package.json +1 -1
  32. package/packets/fixer-repair.md +3 -1
  33. package/packets/judge-apply.md +7 -16
  34. package/packets/judge-authority.md +7 -17
  35. package/packets/judge-plan.md +7 -18
  36. package/packets/judge-review.md +8 -22
  37. package/packets/judge-submission.md +4 -7
  38. package/resources/collector-bot-handbook.md +2 -2
  39. package/resources/diarist-collect.md +2 -2
  40. package/resources/engine-dispatch.md +2 -2
  41. package/resources/navigator-route-playbook.md +1 -1
  42. package/resources/secretariat-ticket-identity.md +1 -1
  43. package/scripts/build-package.mjs +0 -2
  44. package/souls/coder-output-guide.md +3 -3
  45. package/souls/coder.md +2 -3
  46. package/souls/doctor-auditor.md +0 -3
  47. package/souls/fixer-auditor.md +0 -2
  48. package/souls/fixer-output-guide.md +3 -3
  49. package/souls/fixer.md +2 -2
  50. package/souls/gate-output-guide.md +1 -1
  51. package/souls/quality-law.md +2 -0
  52. package/src/acp-host/role-turn-host.ts +168 -75
  53. package/src/collector-tool-schemas.ts +1 -1
  54. package/src/compliance-transport.ts +3 -6
  55. package/src/engine-detour-usage.ts +3 -33
  56. package/src/external-host-turn-loop.ts +81 -36
  57. package/src/gatekeeper-role.ts +5 -20
  58. package/src/headless-host/description.ts +4 -214
  59. package/src/headless-host/production-host.ts +2 -1
  60. package/src/headless-host/role-turn-host.ts +153 -186
  61. package/src/host-contracts.ts +13 -10
  62. package/src/navigator-public-session.ts +1 -1
  63. package/src/navigator-session-contracts.ts +1 -1
  64. package/src/package-contracts/gatekeeper-output.ts +1 -1
  65. package/src/package-contracts/navigator-output.ts +1 -1
  66. package/src/package-contracts/terminating-infrastructure.ts +1 -1
  67. package/src/packaged-role-registry.ts +2 -10
  68. package/src/pi/adapter.ts +0 -3
  69. package/src/pi/role-turn-host.ts +42 -28
  70. package/src/prepared-role-turn.ts +2 -0
  71. package/src/public-cli/auto-resume.ts +27 -66
  72. package/src/public-cli/countersign-run.ts +4 -22
  73. package/src/public-cli/instruction-seat-run.ts +79 -80
  74. package/src/public-cli/option-definitions.ts +1 -1
  75. package/src/public-cli/post-admission.ts +264 -340
  76. package/src/public-cli/process-cancel.ts +9 -0
  77. package/src/public-cli/run-lifecycle.ts +25 -126
  78. package/src/public-cli/settlement.ts +792 -1912
  79. package/src/public-cli/terminal.ts +29 -69
  80. package/src/public-role-summons.ts +3 -8
  81. package/src/receipt-delivery-policy.ts +11 -12
  82. package/src/role-envelope.ts +16 -9
  83. package/src/role-runtime.ts +14 -81
  84. package/src/run-terminal-artifacts.ts +21 -17
  85. package/src/serialize-thrown-value.ts +7 -1
  86. package/src/submission-ledger.ts +30 -116
  87. package/dist/audit-escalation.js +0 -7
  88. package/dist/pi/known-failure.js +0 -51
  89. package/dist/typed-provider-http.js +0 -57
  90. package/dist/upstream-error-testimony.js +0 -19
  91. package/src/pi/known-failure.ts +0 -76
  92. package/src/typed-provider-http.ts +0 -89
  93. package/src/upstream-error-testimony.ts +0 -45
@@ -194,15 +194,6 @@ function resolveReadScope(
194
194
  return homeOrScope;
195
195
  }
196
196
 
197
- function recordsForAttempt<T extends { subject?: unknown; payload?: unknown }>(
198
- owned: readonly T[],
199
- attemptId: string | undefined,
200
- ): readonly T[] {
201
- // #836: courtAttempt is a recording tag for new courts, not a visibility gate.
202
- void attemptId;
203
- return owned;
204
- }
205
-
206
197
  /** Recording tag a row was written under — subject first, historical payload fallback. */
207
198
  function recordAttemptId(record: { subject?: unknown; payload?: unknown }): string | undefined {
208
199
  if (typeof record.subject === "object" && record.subject !== null) {
@@ -216,34 +207,6 @@ function recordAttemptId(record: { subject?: unknown; payload?: unknown }): stri
216
207
  return undefined;
217
208
  }
218
209
 
219
- /**
220
- * True when the given attemptId itself already produced a sealed or
221
- * audit-escalation submission (#836 r12 class 2). This is a separate
222
- * freshness signal, not a presentation filter — `recordsForAttempt` above
223
- * stays a pass-through so every original payload keeps presenting honestly
224
- * (#836: attemptId is a recording tag, not a visibility gate). Settlement
225
- * consumes this only to stop a prior attempt's stale acceptance from
226
- * outranking the current attempt's own real host-turn failure signal
227
- * (#637 original intent, restored narrowly).
228
- */
229
- export async function hasFreshAttemptSubmission(
230
- cwd: string,
231
- runId: string,
232
- attemptId: string,
233
- homeOrScope?: string | SubmissionLedgerReadScope,
234
- ): Promise<boolean> {
235
- const { owned } = await readOwnedSubmissionRecords(cwd, runId, resolveReadScope(homeOrScope));
236
- return owned.some((record) => {
237
- if (recordAttemptId(record) !== attemptId) return false;
238
- if (record.kind === "sealed") return true;
239
- if (record.kind === "outcome") {
240
- const payload = record.payload as { type?: string; outcome?: string } | undefined;
241
- return payload?.type === "outcome" && payload.outcome === "audit-escalation";
242
- }
243
- return false;
244
- });
245
- }
246
-
247
210
  function isTerminalRoleName(value: unknown): value is TerminalRoleName {
248
211
  return typeof value === "string" && value.length > 0;
249
212
  }
@@ -359,55 +322,38 @@ function mapOwnedToSubmissionRows(
359
322
  };
360
323
 
361
324
  for (const record of scoped) {
362
- const attemptId = recordAttemptId(record);
363
- if (record.kind === "candidate") {
364
- const payload = record.payload as Partial<Extract<SubmissionLedgerEvent, { type: "candidate" }>> & {
365
- projection?: { role?: unknown };
366
- } | undefined;
367
- if (payload?.type !== "candidate" || payload.params === undefined) continue;
368
- const callKey =
369
- typeof payload.toolCallId === "string"
370
- ? submissionCallKey(attemptId, payload.toolCallId)
371
- : undefined;
372
- const fallback = callKey !== undefined ? roleByCall.get(callKey) : undefined;
373
- take(rowFromPayload("candidate", payload, payload.params, fallback), callKey);
374
- continue;
375
- }
376
- if (record.kind === "sealed") {
377
- const payload = record.payload as Partial<Extract<SubmissionLedgerEvent, { type: "sealed" }>> & {
378
- projection?: { role?: unknown };
379
- } | undefined;
380
- if (payload?.type !== "sealed" || payload.accepted === undefined) continue;
381
- const callKey =
382
- typeof payload.toolCallId === "string"
383
- ? submissionCallKey(attemptId, payload.toolCallId)
384
- : undefined;
385
- const fallback = callKey !== undefined ? roleByCall.get(callKey) : undefined;
386
- take(rowFromPayload("accepted", payload, payload.accepted, fallback), callKey);
387
- continue;
388
- }
389
- if (record.kind !== "outcome") continue;
390
- const payload = record.payload as Partial<Extract<SubmissionLedgerEvent, { type: "outcome" }>> & {
325
+ const payload = record.payload as {
326
+ type?: unknown;
327
+ role?: unknown;
391
328
  projection?: { role?: unknown };
329
+ toolCallId?: unknown;
330
+ params?: unknown;
331
+ accepted?: unknown;
332
+ outcome?: unknown;
333
+ auditReceipt?: unknown;
334
+ auditOfficer?: unknown;
392
335
  } | undefined;
393
- if (payload?.type !== "outcome" || payload.accepted === undefined) continue;
394
- const outcome = payload.outcome;
395
- const kind: RecordedSubmissionRow["kind"] |
396
- undefined =
397
- outcome === "audit-escalation"
398
- ? "audit-escalation"
399
- : outcome === "correctable-rejection"
400
- ? "correctable-rejection"
401
- : outcome === "infrastructure"
402
- ? "infrastructure"
403
- : undefined;
404
- if (kind === undefined) continue;
405
- const callKey =
406
- typeof payload.toolCallId === "string"
407
- ? submissionCallKey(attemptId, payload.toolCallId)
336
+ let kind: RecordedSubmissionRow["kind"] | undefined;
337
+ let original: unknown;
338
+ if (record.kind === "candidate" && payload?.type === "candidate") {
339
+ kind = "candidate";
340
+ original = payload.params;
341
+ } else if (record.kind === "sealed" && payload?.type === "sealed") {
342
+ kind = "accepted";
343
+ original = payload.accepted;
344
+ } else if (record.kind === "outcome" && payload?.type === "outcome") {
345
+ kind = payload.outcome === "audit-escalation" ? "audit-escalation"
346
+ : payload.outcome === "correctable-rejection" ? "correctable-rejection"
347
+ : payload.outcome === "infrastructure" ? "infrastructure"
408
348
  : undefined;
409
- const fallback = callKey !== undefined ? roleByCall.get(callKey) : undefined;
410
- take(rowFromPayload(kind, payload, payload.accepted, fallback), callKey);
349
+ original = payload.accepted;
350
+ }
351
+ if (kind === undefined || original === undefined || payload === undefined) continue;
352
+ const callKey = typeof payload.toolCallId === "string"
353
+ ? submissionCallKey(recordAttemptId(record), payload.toolCallId)
354
+ : undefined;
355
+ const fallback = callKey === undefined ? undefined : roleByCall.get(callKey);
356
+ take(rowFromPayload(kind, payload, original, fallback), callKey);
411
357
  }
412
358
  return out;
413
359
  }
@@ -425,8 +371,7 @@ export async function readRecordedSubmissionRows(
425
371
  ): Promise<readonly RecordedSubmissionRow[]> {
426
372
  const scope = resolveReadScope(homeOrScope);
427
373
  const { owned } = await readOwnedSubmissionRecords(cwd, runId, scope);
428
- const scoped = recordsForAttempt(owned, scope.attemptId);
429
- return mapOwnedToSubmissionRows(scoped);
374
+ return mapOwnedToSubmissionRows(owned);
430
375
  }
431
376
 
432
377
  /**
@@ -461,37 +406,6 @@ export async function readRecordedSubmissions(
461
406
  return (await readRecordedSubmissionRows(cwd, runId, homeOrScope)).map((row) => row.accepted);
462
407
  }
463
408
 
464
- /** True when the run has at least one recorded original payload (any outcome class). */
465
- export async function hasRecordedSubmission(
466
- cwd: string,
467
- runId: string,
468
- homeOrScope?: string | SubmissionLedgerReadScope,
469
- ): Promise<boolean> {
470
- return (await readRecordedSubmissionRows(cwd, runId, homeOrScope)).length > 0;
471
- }
472
-
473
- export type LatestSubmissionOutcome = Extract<SubmissionLedgerEvent, { type: "outcome" }>;
474
-
475
- /** Latest non-final outcome on the run ledger (settlement residual precedence). */
476
- export async function readLatestSubmissionOutcome(
477
- cwd: string,
478
- runId: string,
479
- homeOrScope?: string | SubmissionLedgerReadScope,
480
- ): Promise<LatestSubmissionOutcome | undefined> {
481
- const scope = resolveReadScope(homeOrScope);
482
- const { owned } = await readOwnedSubmissionRecords(cwd, runId, scope);
483
- const scoped = recordsForAttempt(owned, scope.attemptId);
484
- for (let index = scoped.length - 1; index >= 0; index -= 1) {
485
- const record = scoped[index];
486
- if (record?.kind !== "outcome") continue;
487
- const payload = record.payload as Partial<LatestSubmissionOutcome> | undefined;
488
- if (payload?.type === "outcome" && typeof payload.outcome === "string") {
489
- return payload as LatestSubmissionOutcome;
490
- }
491
- }
492
- return undefined;
493
- }
494
-
495
409
  type LedgerState = { sequence: number };
496
410
 
497
411
  async function restoreState(cwd: string, runId: string, scope: SubmissionLedgerReadScope): Promise<LedgerState> {
@@ -1,7 +0,0 @@
1
- import { isRecord } from "./unknown-value.js";
2
- /** Historical audit-escalation discriminator retained for reading pre-#1057 run artifacts. */
3
- export const AUDIT_ESCALATION_KIND = "audit_escalation";
4
- export function isAuditEscalationResult(value) {
5
- return isRecord(value)
6
- && value.kind === AUDIT_ESCALATION_KIND;
7
- }
@@ -1,51 +0,0 @@
1
- import { hasUpstreamErrorTestimony, projectConfirmedRemotePayload, } from "../upstream-error-testimony.js";
2
- export { hasUpstreamErrorTestimony } from "../upstream-error-testimony.js";
3
- function nonEmptyString(value) {
4
- return typeof value === "string" && value.trim() !== "" ? value : undefined;
5
- }
6
- function sessionStopDetails(input) {
7
- const details = {};
8
- const errorMessage = nonEmptyString(input.errorMessage);
9
- if (errorMessage !== undefined)
10
- details.errorMessage = errorMessage;
11
- const api = nonEmptyString(input.api);
12
- if (api !== undefined)
13
- details.api = api;
14
- const rawStopReason = nonEmptyString(input.rawStopReason);
15
- if (rawStopReason !== undefined)
16
- details.rawStopReason = rawStopReason;
17
- const testimony = hasUpstreamErrorTestimony(input);
18
- if (testimony) {
19
- const provider = nonEmptyString(input.provider);
20
- if (provider !== undefined)
21
- details.provider = provider;
22
- const model = nonEmptyString(input.model);
23
- if (model !== undefined)
24
- details.model = model;
25
- }
26
- if (typeof input.httpStatus === "number")
27
- details.httpStatus = input.httpStatus;
28
- if (input.diagnostics !== undefined)
29
- details.diagnostics = input.diagnostics;
30
- // SDK structured payload fields: project only when held on a confirmed-remote node.
31
- if (testimony)
32
- Object.assign(details, projectConfirmedRemotePayload(input));
33
- return details;
34
- }
35
- /**
36
- * Project a native session assistant stop onto the existing knownFailure chain.
37
- * Classification follows two-way testimony: typed HTTP status or SDK structure
38
- * keeps provider; stopReason / errorMessage prose alone never invents a class (#881).
39
- * Present upstream payload is preserved in details without rewriting; missing fields are omitted.
40
- */
41
- export function knownFailureFromProviderStop(input) {
42
- if (input.stopReason !== "error" && input.stopReason !== "aborted")
43
- return undefined;
44
- const diagnostic = nonEmptyString(input.errorMessage);
45
- const details = sessionStopDetails(input);
46
- return {
47
- ...(hasUpstreamErrorTestimony(input) ? { cause: "provider" } : {}),
48
- ...(diagnostic === undefined ? {} : { diagnostic }),
49
- ...(Object.keys(details).length === 0 ? {} : { details }),
50
- };
51
- }
@@ -1,57 +0,0 @@
1
- import { readFile, unlink, writeFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { isEnoent, isRecord } from "./unknown-value.js";
4
- const TYPED_HTTP_FILE = "typed-provider-http.json";
5
- function typedProviderHttpPath(runDirectory) {
6
- return join(runDirectory, TYPED_HTTP_FILE);
7
- }
8
- async function clearTypedProviderHttpObservation(runDirectory) {
9
- try {
10
- await unlink(typedProviderHttpPath(runDirectory));
11
- } catch (error) {
12
- if (isEnoent(error)) return;
13
- throw error;
14
- }
15
- }
16
- async function recordTypedProviderHttpStatus(runDirectory, observation) {
17
- if (observation.httpStatus >= 200 && observation.httpStatus < 300) {
18
- await clearTypedProviderHttpObservation(runDirectory);
19
- return;
20
- }
21
- const body = {
22
- httpStatus: observation.httpStatus,
23
- provider: observation.provider
24
- };
25
- await writeFile(
26
- typedProviderHttpPath(runDirectory),
27
- `${JSON.stringify(body)}
28
- `,
29
- "utf8"
30
- );
31
- }
32
- async function readLatestTypedProviderHttpObservation(runDirectory) {
33
- let text;
34
- try {
35
- text = await readFile(typedProviderHttpPath(runDirectory), "utf8");
36
- } catch (error) {
37
- if (isEnoent(error)) return void 0;
38
- throw error;
39
- }
40
- const raw = JSON.parse(text);
41
- if (!isRecord(raw)) {
42
- throw new Error("typed provider HTTP observation must be a JSON object");
43
- }
44
- const record = raw;
45
- if (typeof record.httpStatus !== "number") {
46
- throw new Error("typed provider HTTP observation missing numeric httpStatus");
47
- }
48
- if (typeof record.provider !== "string" || record.provider.trim() === "") {
49
- throw new Error("typed provider HTTP observation missing non-empty provider");
50
- }
51
- return { httpStatus: record.httpStatus, provider: record.provider };
52
- }
53
- export {
54
- clearTypedProviderHttpObservation,
55
- readLatestTypedProviderHttpObservation,
56
- recordTypedProviderHttpStatus
57
- };
@@ -1,19 +0,0 @@
1
- function isNonSuccessHttpStatus(status) {
2
- return typeof status === "number" && (status < 200 || status >= 300);
3
- }
4
- function hasUpstreamErrorTestimony(input) {
5
- if (isNonSuccessHttpStatus(input.httpStatus)) return true;
6
- return Array.isArray(input.diagnostics) && input.diagnostics.length > 0;
7
- }
8
- function projectConfirmedRemotePayload(input) {
9
- return {
10
- ...input.body === void 0 ? {} : { body: input.body },
11
- ...input.code === void 0 ? {} : { code: input.code },
12
- ...input.errno === void 0 ? {} : { errno: input.errno }
13
- };
14
- }
15
- export {
16
- hasUpstreamErrorTestimony,
17
- isNonSuccessHttpStatus,
18
- projectConfirmedRemotePayload
19
- };
@@ -1,76 +0,0 @@
1
- /**
2
- * Project native session assistant stops onto the host-contract knownFailure shape.
3
- */
4
- import type { RoleTurnKnownFailure } from "../host-contracts.ts";
5
- import {
6
- hasUpstreamErrorTestimony,
7
- projectConfirmedRemotePayload,
8
- } from "../upstream-error-testimony.ts";
9
-
10
- export { hasUpstreamErrorTestimony } from "../upstream-error-testimony.ts";
11
-
12
- function nonEmptyString(value: unknown): string | undefined {
13
- return typeof value === "string" && value.trim() !== "" ? value : undefined;
14
- }
15
-
16
- function sessionStopDetails(input: {
17
- readonly errorMessage?: string | null;
18
- readonly provider?: string;
19
- readonly model?: string;
20
- readonly api?: string;
21
- readonly rawStopReason?: string;
22
- readonly httpStatus?: number;
23
- readonly diagnostics?: unknown;
24
- readonly body?: unknown;
25
- readonly code?: unknown;
26
- readonly errno?: unknown;
27
- }): Record<string, unknown> {
28
- const details: Record<string, unknown> = {};
29
- const errorMessage = nonEmptyString(input.errorMessage);
30
- if (errorMessage !== undefined) details.errorMessage = errorMessage;
31
- const api = nonEmptyString(input.api);
32
- if (api !== undefined) details.api = api;
33
- const rawStopReason = nonEmptyString(input.rawStopReason);
34
- if (rawStopReason !== undefined) details.rawStopReason = rawStopReason;
35
- const testimony = hasUpstreamErrorTestimony(input);
36
- if (testimony) {
37
- const provider = nonEmptyString(input.provider);
38
- if (provider !== undefined) details.provider = provider;
39
- const model = nonEmptyString(input.model);
40
- if (model !== undefined) details.model = model;
41
- }
42
- if (typeof input.httpStatus === "number") details.httpStatus = input.httpStatus;
43
- if (input.diagnostics !== undefined) details.diagnostics = input.diagnostics;
44
- // SDK structured payload fields: project only when held on a confirmed-remote node.
45
- if (testimony) Object.assign(details, projectConfirmedRemotePayload(input));
46
- return details;
47
- }
48
-
49
- /**
50
- * Project a native session assistant stop onto the existing knownFailure chain.
51
- * Classification follows two-way testimony: typed HTTP status or SDK structure
52
- * keeps provider; stopReason / errorMessage prose alone never invents a class (#881).
53
- * Present upstream payload is preserved in details without rewriting; missing fields are omitted.
54
- */
55
- export function knownFailureFromProviderStop(input: {
56
- readonly stopReason?: string;
57
- readonly errorMessage?: string | null;
58
- readonly provider?: string;
59
- readonly model?: string;
60
- readonly api?: string;
61
- readonly rawStopReason?: string;
62
- readonly httpStatus?: number;
63
- readonly diagnostics?: unknown;
64
- readonly body?: unknown;
65
- readonly code?: unknown;
66
- readonly errno?: unknown;
67
- }): RoleTurnKnownFailure | undefined {
68
- if (input.stopReason !== "error" && input.stopReason !== "aborted") return undefined;
69
- const diagnostic = nonEmptyString(input.errorMessage);
70
- const details = sessionStopDetails(input);
71
- return {
72
- ...(hasUpstreamErrorTestimony(input) ? { cause: "provider" as const } : {}),
73
- ...(diagnostic === undefined ? {} : { diagnostic }),
74
- ...(Object.keys(details).length === 0 ? {} : { details }),
75
- };
76
- }
@@ -1,89 +0,0 @@
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
- import { isEnoent, isRecord } from "./unknown-value.ts";
9
-
10
- const TYPED_HTTP_FILE = "typed-provider-http.json";
11
-
12
- export type TypedProviderHttpObservation = {
13
- readonly httpStatus: number;
14
- readonly provider: string;
15
- };
16
-
17
- function typedProviderHttpPath(runDirectory: string): string {
18
- return join(runDirectory, TYPED_HTTP_FILE);
19
- }
20
-
21
- /**
22
- * Clear any prior attempt's typed provider HTTP observation.
23
- * Each initial/resume dispatch must start without inherited 429 evidence so
24
- * only the current attempt can qualify v1 resume.
25
- */
26
- export async function clearTypedProviderHttpObservation(
27
- runDirectory: string,
28
- ): Promise<void> {
29
- try {
30
- await unlink(typedProviderHttpPath(runDirectory));
31
- } catch (error) {
32
- if (isEnoent(error)) return;
33
- throw error;
34
- }
35
- }
36
-
37
- /**
38
- * Record a typed provider HTTP status observation for the admitted run.
39
- * Only non-success HTTP statuses are persisted (authorized error evidence).
40
- * A 2xx response clears any prior observation so success cannot leave stale
41
- * error evidence. Never inspects diagnostic prose.
42
- */
43
- export async function recordTypedProviderHttpStatus(
44
- runDirectory: string,
45
- observation: { readonly httpStatus: number; readonly provider: string },
46
- ): Promise<void> {
47
- if (observation.httpStatus >= 200 && observation.httpStatus < 300) {
48
- await clearTypedProviderHttpObservation(runDirectory);
49
- return;
50
- }
51
- const body: TypedProviderHttpObservation = {
52
- httpStatus: observation.httpStatus,
53
- provider: observation.provider,
54
- };
55
- await writeFile(
56
- typedProviderHttpPath(runDirectory),
57
- `${JSON.stringify(body)}\n`,
58
- "utf8",
59
- );
60
- }
61
-
62
- /**
63
- * Read the latest durable typed provider HTTP observation, if any.
64
- * Only ENOENT means absence (undefined). Read/parse/shape failures preserve the
65
- * real cause for the existing controlled-failure chain — never laundered as absence.
66
- */
67
- export async function readLatestTypedProviderHttpObservation(
68
- runDirectory: string,
69
- ): Promise<TypedProviderHttpObservation | undefined> {
70
- let text: string;
71
- try {
72
- text = await readFile(typedProviderHttpPath(runDirectory), "utf8");
73
- } catch (error) {
74
- if (isEnoent(error)) return undefined;
75
- throw error;
76
- }
77
- const raw: unknown = JSON.parse(text);
78
- if (!isRecord(raw)) {
79
- throw new Error("typed provider HTTP observation must be a JSON object");
80
- }
81
- const record = raw as Record<string, unknown>;
82
- if (typeof record.httpStatus !== "number") {
83
- throw new Error("typed provider HTTP observation missing numeric httpStatus");
84
- }
85
- if (typeof record.provider !== "string" || record.provider.trim() === "") {
86
- throw new Error("typed provider HTTP observation missing non-empty provider");
87
- }
88
- return { httpStatus: record.httpStatus, provider: record.provider };
89
- }
@@ -1,45 +0,0 @@
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
- }