@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.
- package/dist/archivist-record-entry.js +21 -12
- package/dist/archivist-record-topology.js +20 -0
- package/dist/auditor-dossier-tool.js +67 -76
- package/dist/canonical-json.js +66 -69
- package/dist/compliance-transport.js +99 -86
- package/dist/countersign-contracts.js +23 -25
- package/dist/doctor-contracts.js +169 -145
- package/dist/engine-detour-tool.js +83 -87
- package/dist/engine-detour.js +148 -183
- package/dist/evidence-child-executor.js +10 -1
- package/dist/exact-utf8.js +11 -10
- package/dist/git-object-id.js +10 -5
- package/dist/gleaner-left-contracts.js +33 -35
- package/dist/grok/production-host.js +229 -118
- package/dist/inspector-contracts.js +28 -31
- package/dist/institutional-resolution.js +101 -104
- package/dist/ledger-session-read.js +241 -0
- package/dist/merger-contracts.js +66 -56
- package/dist/notary-contracts.js +91 -100
- package/dist/open-tool-schema.js +32 -31
- package/dist/package-contracts/collector-output.js +56 -43
- package/dist/package-contracts/fixer-output.js +52 -50
- package/dist/package-contracts/fixer-packet.js +67 -64
- package/dist/package-contracts/gatekeeper-output.js +48 -56
- package/dist/package-contracts/judge-output.js +23 -20
- package/dist/package-contracts/navigator-output.js +35 -40
- package/dist/package-contracts/reviewer-output.js +94 -110
- package/dist/package-contracts/terminating-infrastructure.js +62 -90
- package/dist/package-contracts/worker-output.js +30 -11
- package/dist/package-resources/engine-material.js +58 -94
- package/dist/public-cli/config.js +420 -484
- package/dist/public-cli/main.js +1219 -524
- package/dist/public-cli/registry.js +107 -114
- package/dist/receipt-delivery-policy.js +74 -76
- package/dist/session-opening-materials.js +44 -47
- package/dist/sha256.js +5 -3
- package/dist/sitian-appender.js +204 -210
- package/dist/sitian-contracts.js +15 -19
- package/dist/sitian-facade.js +5 -10
- package/dist/sitian-reader.js +37 -46
- package/dist/stream-idle-guard.js +63 -71
- package/dist/ticket-seat-memory.js +244 -0
- package/package.json +1 -1
- package/scripts/build-package.mjs +41 -0
- package/souls/audit-law.md +4 -0
- package/souls/countersign.md +1 -1
- package/souls/gleaner-left.md +1 -1
- package/souls/inspector.md +1 -1
- package/souls/notary.md +1 -1
- package/souls/reviewer.md +1 -1
- package/src/analyst-gate-cycles-read.ts +152 -83
- package/src/analyst-ledger.ts +170 -43
- package/src/archivist-record-entry.ts +32 -11
- package/src/archivist-record-topology.ts +39 -0
- package/src/evidence-child-executor.ts +14 -1
- package/src/grok/production-host.ts +44 -9
- package/src/host-contracts.ts +18 -0
- package/src/host-transition-prior-native.ts +8 -1
- package/src/ledger-session-read.ts +56 -0
- package/src/pi/durable-principal.ts +6 -0
- package/src/public-cli/inspector-run.ts +21 -10
- package/src/public-cli/invocation.ts +4 -20
- package/src/public-cli/notary-run.ts +21 -4
- package/src/public-cli/post-admission.ts +333 -42
- package/src/public-cli/settlement.ts +266 -81
- package/src/session-opening-materials.ts +4 -2
- package/src/ticket-seat-memory.ts +454 -0
|
@@ -26,6 +26,11 @@ import type {
|
|
|
26
26
|
SessionCustomEntryAppender,
|
|
27
27
|
} from "../host-contracts.ts";
|
|
28
28
|
import { projectHostTransitionPriorNative } from "../host-transition-prior-native.ts";
|
|
29
|
+
import {
|
|
30
|
+
isTicketSeatMemoryBound,
|
|
31
|
+
readTicketSeatMemoryLastHost,
|
|
32
|
+
writeTicketSeatMemoryLastHost,
|
|
33
|
+
} from "../ticket-seat-memory.ts";
|
|
29
34
|
import type { CredentialProviders, SeatModelConfig } from "./config.ts";
|
|
30
35
|
import {
|
|
31
36
|
missingCredentialPreDispatchFailure,
|
|
@@ -54,10 +59,12 @@ import {
|
|
|
54
59
|
isLawfulTypedTerminalOutcome,
|
|
55
60
|
presentFailureTerminal,
|
|
56
61
|
presentStructuralRejection,
|
|
62
|
+
projectThrownFailureLeaf,
|
|
57
63
|
resolveAuditedRunnerFailureResolution,
|
|
58
64
|
resolveControlledFailureResumeObservation,
|
|
59
65
|
settleFailureTerminalResult,
|
|
60
66
|
sealedAcceptanceRedispatchDisposition,
|
|
67
|
+
type ControlledFailure,
|
|
61
68
|
} from "./settlement.ts";
|
|
62
69
|
import type { CliIo } from "./cli-io.ts";
|
|
63
70
|
import type { AdmittedRoleInvocation } from "./invocation.ts";
|
|
@@ -109,7 +116,6 @@ export type PostAdmissionAdapters<
|
|
|
109
116
|
trySettle: (admitted: A, authority: DurablePrincipalAuthority) => Promise<T | undefined>;
|
|
110
117
|
/** Default: isLawfulTypedTerminalOutcome(terminal.roleOutcome). */
|
|
111
118
|
shouldPresentSettled?: (terminal: T) => boolean;
|
|
112
|
-
trySettleSecondary?: (admitted: A, authority: DurablePrincipalAuthority) => Promise<TerminalResult | undefined>;
|
|
113
119
|
resolveRunnerKnownFailure?: (input: {
|
|
114
120
|
result: RoleTurnResult;
|
|
115
121
|
sessionFile: string;
|
|
@@ -129,6 +135,8 @@ export type ControlledFailureInput = {
|
|
|
129
135
|
readonly code?: string | number;
|
|
130
136
|
};
|
|
131
137
|
knownDiagnostic?: string;
|
|
138
|
+
/** Secondary evidence already owned by the typed production failure channel. */
|
|
139
|
+
knownDetails?: Readonly<Record<string, unknown>>;
|
|
132
140
|
typedHttpObservationSettled?: true;
|
|
133
141
|
typedHttpObservation?: TypedProviderHttpObservation;
|
|
134
142
|
};
|
|
@@ -169,12 +177,19 @@ export async function presentControlledFailure<
|
|
|
169
177
|
admitted.principal !== undefined
|
|
170
178
|
? await inspectJudgeSession(authority.decode(admitted.principal).sessionFile)
|
|
171
179
|
: undefined;
|
|
180
|
+
// knownFailure channel owns details when present; otherwise caller knownDetails
|
|
181
|
+
// (e.g. last-host write concurrent leaf on timeout/activation dual failure).
|
|
182
|
+
const fromKnownFailure =
|
|
183
|
+
explicitInternalKnownFailureClassificationInput(knownFailure);
|
|
172
184
|
const failure = classifyPostAdmissionFailure({
|
|
173
185
|
timedOut: failureInput.timedOut,
|
|
174
186
|
code: failureInput.code,
|
|
175
187
|
stderr: failureInput.stderr,
|
|
176
188
|
...(hasThrown ? { thrown: failureInput.thrown } : {}),
|
|
177
|
-
...
|
|
189
|
+
...(failureInput.knownDetails === undefined
|
|
190
|
+
? {}
|
|
191
|
+
: { knownDetails: failureInput.knownDetails }),
|
|
192
|
+
...fromKnownFailure,
|
|
178
193
|
...(failureInput.knownCause === undefined
|
|
179
194
|
? {}
|
|
180
195
|
: { knownCause: failureInput.knownCause }),
|
|
@@ -218,12 +233,125 @@ export async function presentControlledFailure<
|
|
|
218
233
|
};
|
|
219
234
|
}
|
|
220
235
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
236
|
+
/** Serialize one failure leaf for details.concurrentFailures (sole leaf owner = settlement). */
|
|
237
|
+
function concurrentFailureLeafRecord(leaf: ControlledFailure): {
|
|
238
|
+
readonly cause: ControlledFailureCause;
|
|
239
|
+
readonly diagnostic: string;
|
|
240
|
+
readonly identity?: { readonly name?: string; readonly code?: string | number };
|
|
241
|
+
readonly details?: Readonly<Record<string, unknown>>;
|
|
242
|
+
} {
|
|
243
|
+
return {
|
|
244
|
+
cause: leaf.cause,
|
|
245
|
+
diagnostic: leaf.diagnostic,
|
|
246
|
+
...(leaf.identity === undefined ? {} : { identity: leaf.identity }),
|
|
247
|
+
...(leaf.details === undefined ? {} : { details: leaf.details }),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function existingConcurrentFailureLeaves(
|
|
252
|
+
details: Readonly<Record<string, unknown>> | undefined,
|
|
253
|
+
): unknown[] {
|
|
254
|
+
const value = details?.concurrentFailures;
|
|
255
|
+
return Array.isArray(value) ? [...value] : [];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Attach last-host write failure as concurrent secondary evidence on an existing
|
|
260
|
+
* returned-path ControlledFailureInput — append, never replace prior leaves.
|
|
261
|
+
*/
|
|
262
|
+
function withLastHostWriteConcurrentFailure(
|
|
263
|
+
input: ControlledFailureInput,
|
|
264
|
+
writeError: unknown,
|
|
265
|
+
): ControlledFailureInput {
|
|
266
|
+
const writeLeaf = concurrentFailureLeafRecord(projectThrownFailureLeaf(writeError));
|
|
267
|
+
if (input.knownFailure !== undefined) {
|
|
268
|
+
const failure = input.knownFailure;
|
|
269
|
+
return {
|
|
270
|
+
...input,
|
|
271
|
+
knownFailure: {
|
|
272
|
+
cause: failure.cause,
|
|
273
|
+
...(failure.identity === undefined ? {} : { identity: failure.identity }),
|
|
274
|
+
...(failure.diagnostic === undefined
|
|
275
|
+
? {}
|
|
276
|
+
: { diagnostic: failure.diagnostic }),
|
|
277
|
+
details: {
|
|
278
|
+
...(failure.details ?? {}),
|
|
279
|
+
concurrentFailures: [
|
|
280
|
+
...existingConcurrentFailureLeaves(failure.details),
|
|
281
|
+
writeLeaf,
|
|
282
|
+
],
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
...input,
|
|
289
|
+
knownDetails: {
|
|
290
|
+
...(input.knownDetails ?? {}),
|
|
291
|
+
concurrentFailures: [
|
|
292
|
+
...existingConcurrentFailureLeaves(input.knownDetails),
|
|
293
|
+
writeLeaf,
|
|
294
|
+
],
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Settled failure roleOutcome already narrowed by kind at the sole call site. */
|
|
300
|
+
type SettledFailureOutcome = Extract<
|
|
301
|
+
TerminalResult["roleOutcome"],
|
|
302
|
+
{ kind: "failure" }
|
|
303
|
+
>;
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Rebuild the settled failure as knownFailure so concurrent last-host write can
|
|
307
|
+
* append without replacing cause/identity/diagnostic/details.
|
|
308
|
+
* Caller narrows by kind; no second runtime re-check.
|
|
309
|
+
*/
|
|
310
|
+
function knownFailureFromSettledFailureOutcome(
|
|
311
|
+
outcome: SettledFailureOutcome,
|
|
312
|
+
): RoleTurnKnownFailure {
|
|
313
|
+
const facts = outcome.decisiveFacts;
|
|
314
|
+
const secondary = facts.secondaryEvidence;
|
|
315
|
+
const details =
|
|
316
|
+
secondary !== undefined &&
|
|
317
|
+
typeof secondary === "object" &&
|
|
318
|
+
secondary !== null &&
|
|
319
|
+
!Array.isArray(secondary)
|
|
320
|
+
? (secondary as Readonly<Record<string, unknown>>)
|
|
321
|
+
: undefined;
|
|
322
|
+
const identity: { name?: string; code?: string | number } = {};
|
|
323
|
+
if (typeof facts.errorName === "string") identity.name = facts.errorName;
|
|
324
|
+
if (
|
|
325
|
+
typeof facts.errorCode === "string" ||
|
|
326
|
+
typeof facts.errorCode === "number"
|
|
327
|
+
) {
|
|
328
|
+
identity.code = facts.errorCode;
|
|
226
329
|
}
|
|
330
|
+
return {
|
|
331
|
+
cause: outcome.cause,
|
|
332
|
+
diagnostic: outcome.diagnostic,
|
|
333
|
+
...(Object.keys(identity).length > 0 ? { identity } : {}),
|
|
334
|
+
...(details === undefined ? {} : { details }),
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Presence envelope for a caught last-host write failure.
|
|
340
|
+
* Own-key presence is distinct from the caught value: `throw undefined` must stay a
|
|
341
|
+
* real failure fact (same rule as settlement thrown own-key), never a missing-write sentinel.
|
|
342
|
+
*/
|
|
343
|
+
type LastHostWriteFailure = { readonly error: unknown };
|
|
344
|
+
|
|
345
|
+
/** Keep primary throw identity; attach last-host write as a real AggregateError leaf. */
|
|
346
|
+
function withOptionalLastHostWriteThrow(
|
|
347
|
+
primary: unknown,
|
|
348
|
+
writeFailure: LastHostWriteFailure | undefined,
|
|
349
|
+
message: string,
|
|
350
|
+
): unknown {
|
|
351
|
+
if (writeFailure === undefined) return primary;
|
|
352
|
+
return new AggregateError([primary, writeFailure.error], message, {
|
|
353
|
+
cause: primary,
|
|
354
|
+
});
|
|
227
355
|
}
|
|
228
356
|
|
|
229
357
|
export async function dispatchPostAdmissionTurn<
|
|
@@ -261,19 +389,95 @@ export async function dispatchPostAdmissionTurn<
|
|
|
261
389
|
}
|
|
262
390
|
// #617 DK-4: capture previous invocation host before markRunRunning overwrites it.
|
|
263
391
|
// Single authority projectHostTransitionPriorNative owns known-host prior native paths.
|
|
264
|
-
|
|
392
|
+
// #636 ticket-seat memory: one last-host page owns both last host and Grok native-home run.
|
|
393
|
+
// Open is not turn return/throw: production Grok notes the open fact at bind success;
|
|
394
|
+
// last-host records when the host returned a result OR when native home actually opened.
|
|
395
|
+
// Pre-open throws claim nothing; post-open throws retain the opened run.
|
|
396
|
+
// Side effects are gated by explicit ticket+seat binding — never by directory-outside-run guessing.
|
|
397
|
+
let previousHost: string | undefined;
|
|
398
|
+
let previousRunDirectory: string | undefined;
|
|
399
|
+
let nativeHomeRunDirectory: string | undefined;
|
|
400
|
+
let openedNativeHomeRunDirectory: string | undefined;
|
|
265
401
|
const liveHost = env.host;
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
?
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
402
|
+
const principalCoordinates =
|
|
403
|
+
admitted.principal === undefined
|
|
404
|
+
? undefined
|
|
405
|
+
: env.principalAuthority.decode(admitted.principal);
|
|
406
|
+
const ticketSeatMemoryBound = isTicketSeatMemoryBound({
|
|
407
|
+
role: admitted.role,
|
|
408
|
+
...(admitted.ticketNumber === undefined ? {} : { ticketNumber: admitted.ticketNumber }),
|
|
409
|
+
});
|
|
410
|
+
let turnRequest: RoleTurnRequest;
|
|
411
|
+
try {
|
|
412
|
+
// Ticket-seat host authority lives on the nest last-host page only.
|
|
413
|
+
// Per-run invocation.host must not override: old-run retry after a cross-host
|
|
414
|
+
// intermediate would otherwise keep the stale run host and drop the transition.
|
|
415
|
+
// Non-ticket-seat paths still read the per-run invocation mark (#617).
|
|
416
|
+
if (ticketSeatMemoryBound && principalCoordinates !== undefined) {
|
|
417
|
+
const lastHost = await readTicketSeatMemoryLastHost(
|
|
418
|
+
principalCoordinates.sessionDirectory,
|
|
419
|
+
);
|
|
420
|
+
if (lastHost !== undefined) {
|
|
421
|
+
previousHost = lastHost.host;
|
|
422
|
+
// runDirectory on last-host is the established Grok native isolation run when present
|
|
423
|
+
// (preserved across non-Grok hosts so return-to-Grok can reopen it).
|
|
424
|
+
if (
|
|
425
|
+
typeof lastHost.runDirectory === "string" &&
|
|
426
|
+
lastHost.runDirectory.length > 0
|
|
427
|
+
) {
|
|
428
|
+
previousRunDirectory = lastHost.runDirectory;
|
|
429
|
+
if (
|
|
430
|
+
liveHost === "grok-build" &&
|
|
431
|
+
request.continuation.kind === "resume"
|
|
432
|
+
) {
|
|
433
|
+
nativeHomeRunDirectory = lastHost.runDirectory;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
} else {
|
|
438
|
+
previousHost = await readInvocationHost(admitted.runDirectory);
|
|
439
|
+
}
|
|
440
|
+
const hostTransition =
|
|
441
|
+
previousHost !== undefined && liveHost !== undefined && principalCoordinates !== undefined
|
|
442
|
+
? await projectHostTransitionPriorNative({
|
|
443
|
+
previousHost,
|
|
444
|
+
liveHost,
|
|
445
|
+
runDirectory: admitted.runDirectory,
|
|
446
|
+
piSessionFile: principalCoordinates.sessionFile,
|
|
447
|
+
...(previousRunDirectory === undefined ? {} : { previousRunDirectory }),
|
|
448
|
+
})
|
|
449
|
+
: undefined;
|
|
450
|
+
turnRequest = request;
|
|
451
|
+
if (hostTransition !== undefined) {
|
|
452
|
+
turnRequest = { ...turnRequest, hostTransition };
|
|
453
|
+
}
|
|
454
|
+
if (nativeHomeRunDirectory !== undefined) {
|
|
455
|
+
turnRequest = { ...turnRequest, nativeHomeRunDirectory };
|
|
456
|
+
}
|
|
457
|
+
// Capture the actual Grok open event from production isolation (bind success).
|
|
458
|
+
if (ticketSeatMemoryBound) {
|
|
459
|
+
turnRequest = {
|
|
460
|
+
...turnRequest,
|
|
461
|
+
noteNativeHomeOpened: (runDirectory) => {
|
|
462
|
+
openedNativeHomeRunDirectory = runDirectory;
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
} catch (error) {
|
|
467
|
+
// last-host / prior-native IO is on the public one-shot path — controlled failure, not bare throw.
|
|
468
|
+
return (await presentControlledFailure(
|
|
469
|
+
admitted,
|
|
470
|
+
{
|
|
471
|
+
timedOut: false,
|
|
472
|
+
code: null,
|
|
473
|
+
stderr: "",
|
|
474
|
+
thrown: error,
|
|
475
|
+
},
|
|
476
|
+
adapters,
|
|
477
|
+
env.principalAuthority,
|
|
478
|
+
io,
|
|
479
|
+
)) as { exitCode: number; admitted: A; terminal: T };
|
|
480
|
+
}
|
|
277
481
|
|
|
278
482
|
await markRunRunning(admitted.runDirectory, env.model, effectiveEngine, env.host);
|
|
279
483
|
await clearTypedProviderHttpObservation(admitted.runDirectory);
|
|
@@ -298,17 +502,66 @@ export async function dispatchPostAdmissionTurn<
|
|
|
298
502
|
}
|
|
299
503
|
}
|
|
300
504
|
|
|
301
|
-
|
|
505
|
+
// Turn outcome and open fact are separate events. Discriminate throw value undefined.
|
|
506
|
+
let turnOutcome:
|
|
507
|
+
| { readonly kind: "returned"; readonly result: RoleTurnResult }
|
|
508
|
+
| { readonly kind: "thrown"; readonly error: unknown };
|
|
302
509
|
try {
|
|
303
|
-
|
|
510
|
+
turnOutcome = {
|
|
511
|
+
kind: "returned",
|
|
512
|
+
result: await env.roleTurnHost.executeTurn(turnRequest),
|
|
513
|
+
};
|
|
304
514
|
} catch (error) {
|
|
515
|
+
turnOutcome = { kind: "thrown", error };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// last-host authority: host returned a turn result OR native home actually opened.
|
|
519
|
+
// Grok open fact comes from production bind (noteNativeHomeOpened), not from return/throw.
|
|
520
|
+
// Mock hosts that return without opening still record via the returned path.
|
|
521
|
+
// Pre-open throws: neither → no claim. Post-open throws: open fact → retain.
|
|
522
|
+
// Spans returned failure / same-run retry / return-to-Grok / post-open body+cleanup throws.
|
|
523
|
+
// last-host write failure is pending secondary evidence only — never forks settlement.
|
|
524
|
+
// Thrown dual: AggregateError leaves. Returned dual: same shared settle/resolve chain,
|
|
525
|
+
// write leaf attached at the single present boundary (ADR 0080 single-settlement-disposition).
|
|
526
|
+
// Presence is the envelope, not the caught value — `throw undefined` stays a failure fact.
|
|
527
|
+
let lastHostWriteFailure: LastHostWriteFailure | undefined;
|
|
528
|
+
const hostEngaged =
|
|
529
|
+
turnOutcome.kind === "returned" || openedNativeHomeRunDirectory !== undefined;
|
|
530
|
+
if (
|
|
531
|
+
hostEngaged &&
|
|
532
|
+
liveHost !== undefined &&
|
|
533
|
+
principalCoordinates !== undefined &&
|
|
534
|
+
ticketSeatMemoryBound
|
|
535
|
+
) {
|
|
536
|
+
try {
|
|
537
|
+
await writeTicketSeatMemoryLastHost(
|
|
538
|
+
principalCoordinates.sessionDirectory,
|
|
539
|
+
liveHost,
|
|
540
|
+
liveHost === "grok-build"
|
|
541
|
+
? (openedNativeHomeRunDirectory ??
|
|
542
|
+
nativeHomeRunDirectory ??
|
|
543
|
+
admitted.runDirectory)
|
|
544
|
+
: previousRunDirectory,
|
|
545
|
+
);
|
|
546
|
+
} catch (error) {
|
|
547
|
+
lastHostWriteFailure = { error };
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (turnOutcome.kind === "thrown") {
|
|
552
|
+
// Preserve original throw/cause (pre-open or post-open). last-host already
|
|
553
|
+
// retained open fact above when bind had succeeded; write fail stays concurrent.
|
|
305
554
|
return (await presentControlledFailure(
|
|
306
555
|
admitted,
|
|
307
556
|
{
|
|
308
557
|
timedOut: false,
|
|
309
558
|
code: null,
|
|
310
559
|
stderr: "",
|
|
311
|
-
thrown:
|
|
560
|
+
thrown: withOptionalLastHostWriteThrow(
|
|
561
|
+
turnOutcome.error,
|
|
562
|
+
lastHostWriteFailure,
|
|
563
|
+
"host turn and ticket-seat last-host write failed",
|
|
564
|
+
),
|
|
312
565
|
},
|
|
313
566
|
adapters,
|
|
314
567
|
env.principalAuthority,
|
|
@@ -316,6 +569,8 @@ export async function dispatchPostAdmissionTurn<
|
|
|
316
569
|
)) as { exitCode: number; admitted: A; terminal: T };
|
|
317
570
|
}
|
|
318
571
|
|
|
572
|
+
const result = turnOutcome.result;
|
|
573
|
+
|
|
319
574
|
try {
|
|
320
575
|
await writeFile(
|
|
321
576
|
join(admitted.runDirectory, "stderr.log"),
|
|
@@ -330,13 +585,18 @@ export async function dispatchPostAdmissionTurn<
|
|
|
330
585
|
try {
|
|
331
586
|
settled = await adapters.trySettle(admitted, env.principalAuthority);
|
|
332
587
|
} catch (error) {
|
|
588
|
+
// Settle throw is a real failure fact — never swallow into undefined.
|
|
333
589
|
return (await presentControlledFailure(
|
|
334
590
|
admitted,
|
|
335
591
|
{
|
|
336
592
|
timedOut: false,
|
|
337
593
|
code: result.code,
|
|
338
594
|
stderr: result.stderr,
|
|
339
|
-
thrown:
|
|
595
|
+
thrown: withOptionalLastHostWriteThrow(
|
|
596
|
+
error,
|
|
597
|
+
lastHostWriteFailure,
|
|
598
|
+
"settlement and ticket-seat last-host write failed",
|
|
599
|
+
),
|
|
340
600
|
},
|
|
341
601
|
adapters,
|
|
342
602
|
env.principalAuthority,
|
|
@@ -344,6 +604,44 @@ export async function dispatchPostAdmissionTurn<
|
|
|
344
604
|
)) as { exitCode: number; admitted: A; terminal: T };
|
|
345
605
|
}
|
|
346
606
|
if (settled !== undefined && shouldPresent(settled)) {
|
|
607
|
+
if (lastHostWriteFailure !== undefined) {
|
|
608
|
+
if (settled.roleOutcome.kind === "failure") {
|
|
609
|
+
// Existing failure terminal stays primary; last-host write is concurrent only.
|
|
610
|
+
// shouldPresentSettled must not be read as "no existing failure".
|
|
611
|
+
return (await presentControlledFailure(
|
|
612
|
+
admitted,
|
|
613
|
+
withLastHostWriteConcurrentFailure(
|
|
614
|
+
{
|
|
615
|
+
timedOut: result.timedOut,
|
|
616
|
+
code: result.code,
|
|
617
|
+
stderr: result.stderr,
|
|
618
|
+
knownFailure: knownFailureFromSettledFailureOutcome(
|
|
619
|
+
settled.roleOutcome,
|
|
620
|
+
),
|
|
621
|
+
},
|
|
622
|
+
lastHostWriteFailure.error,
|
|
623
|
+
),
|
|
624
|
+
adapters,
|
|
625
|
+
env.principalAuthority,
|
|
626
|
+
io,
|
|
627
|
+
)) as { exitCode: number; admitted: A; terminal: T };
|
|
628
|
+
}
|
|
629
|
+
// No existing failure terminal — last-host write is the sole public failure.
|
|
630
|
+
// thrown key is always set so `throw undefined` stays own-key present.
|
|
631
|
+
return (await presentControlledFailure(
|
|
632
|
+
admitted,
|
|
633
|
+
{
|
|
634
|
+
timedOut: result.timedOut,
|
|
635
|
+
code: result.code,
|
|
636
|
+
stderr: result.stderr,
|
|
637
|
+
thrown: lastHostWriteFailure.error,
|
|
638
|
+
},
|
|
639
|
+
adapters,
|
|
640
|
+
env.principalAuthority,
|
|
641
|
+
io,
|
|
642
|
+
)) as { exitCode: number; admitted: A; terminal: T };
|
|
643
|
+
}
|
|
644
|
+
// last-host recorded after host returned a turn result (shared failure/retry/return fact).
|
|
347
645
|
await markRunTerminal(admitted.runDirectory);
|
|
348
646
|
io.stdout(formatTerminalResult(settled));
|
|
349
647
|
return {
|
|
@@ -353,19 +651,6 @@ export async function dispatchPostAdmissionTurn<
|
|
|
353
651
|
};
|
|
354
652
|
}
|
|
355
653
|
|
|
356
|
-
if (adapters.trySettleSecondary !== undefined) {
|
|
357
|
-
const secondary = await adapters.trySettleSecondary(admitted, env.principalAuthority);
|
|
358
|
-
if (secondary !== undefined) {
|
|
359
|
-
await markRunTerminal(admitted.runDirectory);
|
|
360
|
-
presentSecondaryTerminal(secondary, io);
|
|
361
|
-
return {
|
|
362
|
-
exitCode: exitCodeForTerminalOutcome(secondary.roleOutcome),
|
|
363
|
-
admitted,
|
|
364
|
-
terminal: secondary as unknown as T,
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
654
|
const sessionFile =
|
|
370
655
|
admitted.principal !== undefined
|
|
371
656
|
? env.principalAuthority.decode(admitted.principal).sessionFile
|
|
@@ -385,14 +670,20 @@ export async function dispatchPostAdmissionTurn<
|
|
|
385
670
|
credential: credentialFailure,
|
|
386
671
|
runDirectory: admitted.runDirectory,
|
|
387
672
|
});
|
|
673
|
+
const hostFailureInput: ControlledFailureInput = {
|
|
674
|
+
timedOut: result.timedOut,
|
|
675
|
+
code: result.code,
|
|
676
|
+
stderr: result.stderr,
|
|
677
|
+
...controlledFailureInputFromResolution(resolution),
|
|
678
|
+
};
|
|
388
679
|
return (await presentControlledFailure(
|
|
389
680
|
admitted,
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
681
|
+
lastHostWriteFailure === undefined
|
|
682
|
+
? hostFailureInput
|
|
683
|
+
: withLastHostWriteConcurrentFailure(
|
|
684
|
+
hostFailureInput,
|
|
685
|
+
lastHostWriteFailure.error,
|
|
686
|
+
),
|
|
396
687
|
adapters,
|
|
397
688
|
env.principalAuthority,
|
|
398
689
|
io,
|