@akagilnc/pi-workflow-roles 0.1.4620 → 0.1.4631
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/README.zh-CN.md +1 -1
- package/dist/acp-host/production-host.js +231 -218
- package/dist/activation-ledger-topology.js +0 -4
- package/dist/headless-host/production-host.js +215 -202
- package/dist/pi/role-turn-host.js +0 -3
- package/dist/public-cli/auto-resume.js +26 -0
- package/dist/public-cli/main.js +1054 -780
- package/dist/public-cli/post-admission.js +101 -7
- package/dist/public-cli/process-cancel.js +57 -0
- package/package.json +1 -1
- package/scripts/build-package.mjs +0 -1
- package/src/activation-ledger-topology.ts +1 -6
- package/src/activation-ledger.ts +5 -236
- package/src/analyst-entry.ts +19 -2
- package/src/analyst-ledger.ts +111 -6
- package/src/archivist-record-entry.ts +1 -1
- package/src/pi/role-turn-host.ts +0 -3
- package/src/public-cli/auto-resume.ts +34 -0
- package/src/public-cli/cli.ts +30 -0
- package/src/public-cli/main.ts +25 -3
- package/src/public-cli/post-admission.ts +173 -6
- package/src/public-cli/process-cancel.ts +75 -0
- package/src/role-runtime.ts +4 -51
- package/dist/activation-reconciliation.js +0 -61
- package/src/activation-reconciliation.ts +0 -163
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #855: catchable process signals for ak-role public entry.
|
|
3
|
+
* SIGTERM / SIGINT / SIGHUP abort the shared controller so hosts can gracefully
|
|
4
|
+
* terminate children (SIGTERM only, never SIGKILL). Uncatchable death (SIGKILL,
|
|
5
|
+
* crash, power loss) is reported via run-state + writer lease on analyst.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const CATCHABLE_PROCESS_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"] as const;
|
|
9
|
+
|
|
10
|
+
export type CatchableProcessSignal = (typeof CATCHABLE_PROCESS_SIGNALS)[number];
|
|
11
|
+
|
|
12
|
+
export type ProcessCancelHandle = {
|
|
13
|
+
readonly signal: AbortSignal;
|
|
14
|
+
/** Signal name once a catchable signal has been received; else undefined. */
|
|
15
|
+
readonly receivedSignal: () => CatchableProcessSignal | undefined;
|
|
16
|
+
dispose(): void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Install once per process entry. First catchable signal wins; later signals
|
|
21
|
+
* are ignored so settlement can finish without re-entry storms.
|
|
22
|
+
*/
|
|
23
|
+
export function installProcessCancelHandlers(
|
|
24
|
+
processRef: NodeJS.Process = process,
|
|
25
|
+
): ProcessCancelHandle {
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
let received: CatchableProcessSignal | undefined;
|
|
28
|
+
const listeners = new Map<CatchableProcessSignal, () => void>();
|
|
29
|
+
|
|
30
|
+
for (const sig of CATCHABLE_PROCESS_SIGNALS) {
|
|
31
|
+
const onSignal = (): void => {
|
|
32
|
+
if (received !== undefined) return;
|
|
33
|
+
received = sig;
|
|
34
|
+
try {
|
|
35
|
+
controller.abort(sig);
|
|
36
|
+
} catch {
|
|
37
|
+
// already aborted
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
listeners.set(sig, onSignal);
|
|
41
|
+
processRef.on(sig, onSignal);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
signal: controller.signal,
|
|
46
|
+
receivedSignal: () => received,
|
|
47
|
+
dispose(): void {
|
|
48
|
+
for (const [sig, onSignal] of listeners) {
|
|
49
|
+
processRef.off(sig, onSignal);
|
|
50
|
+
}
|
|
51
|
+
listeners.clear();
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Signal name when this AbortSignal was aborted by installProcessCancelHandlers. */
|
|
57
|
+
export function processCancelSignalName(
|
|
58
|
+
signal: AbortSignal | undefined,
|
|
59
|
+
): CatchableProcessSignal | undefined {
|
|
60
|
+
if (signal === undefined || signal.aborted !== true) return undefined;
|
|
61
|
+
const reason = signal.reason;
|
|
62
|
+
if (
|
|
63
|
+
reason === "SIGTERM" ||
|
|
64
|
+
reason === "SIGINT" ||
|
|
65
|
+
reason === "SIGHUP"
|
|
66
|
+
) {
|
|
67
|
+
return reason;
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Diagnostic line for a catchable process-signal termination (failure honesty). */
|
|
73
|
+
export function processCancelDiagnostic(signalName: CatchableProcessSignal): string {
|
|
74
|
+
return `ak-role terminated by ${signalName}`;
|
|
75
|
+
}
|
package/src/role-runtime.ts
CHANGED
|
@@ -17,20 +17,10 @@ import { createSubmissionLedgerHost, sealAcceptedSubmission } from "./submission
|
|
|
17
17
|
import { createCollectorLedger } from "./collector-ledger.ts";
|
|
18
18
|
|
|
19
19
|
import { activationTraceRecordSchema, namedActivationCause, type ActivationTraceRecord, type ActivationTraceWriter } from "./activation-trace.ts";
|
|
20
|
+
import { homeFromRunDirectory } from "./activation-ledger-topology.ts";
|
|
20
21
|
import {
|
|
21
|
-
homeFromRunDirectory,
|
|
22
|
-
resolveActivationLedgerHomeForPath,
|
|
23
|
-
} from "./activation-ledger-topology.ts";
|
|
24
|
-
import {
|
|
25
|
-
appendAcceptedActivationToBook,
|
|
26
|
-
buildAcceptedActivationFact,
|
|
27
|
-
correlationIdentityFromEnv,
|
|
28
22
|
durableSessionPointer,
|
|
29
|
-
resolveActivationLedgerHome,
|
|
30
23
|
resolveBookKeyFromGit,
|
|
31
|
-
type AcceptedActivationFact,
|
|
32
|
-
type ActivationCorrelationIdentity,
|
|
33
|
-
type ActivationSessionPointer,
|
|
34
24
|
} from "./activation-ledger.ts";
|
|
35
25
|
import { writeStderrJsonlRecord } from "./stderr-jsonl.ts";
|
|
36
26
|
import {
|
|
@@ -301,43 +291,18 @@ export {
|
|
|
301
291
|
export { activationTraceRecordSchema, namedActivationCause } from "./activation-trace.ts";
|
|
302
292
|
export type { ActivationTraceRecord, ActivationTraceWriter } from "./activation-trace.ts";
|
|
303
293
|
export {
|
|
304
|
-
ACCEPTED_ACTIVATION_EVENT,
|
|
305
|
-
ACCEPTED_ACTIVATION_FACT_KEYS,
|
|
306
294
|
ActivationGitRepositoryRequiredError,
|
|
307
295
|
ActivationLedgerError,
|
|
308
296
|
ActivationSessionFileMissingError,
|
|
309
297
|
activationBookDirectory,
|
|
310
|
-
activationWaitingLedgerPath,
|
|
311
|
-
appendAcceptedActivationFact,
|
|
312
|
-
appendAcceptedActivationToBook,
|
|
313
|
-
buildAcceptedActivationFact,
|
|
314
|
-
correlationIdentityFromEnv,
|
|
315
298
|
durableSessionPointer,
|
|
316
299
|
resolveActivationLedgerHome,
|
|
317
300
|
resolveBookKeyFromGit,
|
|
318
|
-
serializeAcceptedActivationFact,
|
|
319
301
|
} from "./activation-ledger.ts";
|
|
320
302
|
export type {
|
|
321
|
-
AcceptedActivationFact,
|
|
322
|
-
AcceptedActivationFactInput,
|
|
323
|
-
AcceptedActivationFactKey,
|
|
324
|
-
ActivationCorrelationIdentity,
|
|
325
303
|
ActivationSessionManager,
|
|
326
304
|
ActivationSessionPointer,
|
|
327
305
|
} from "./activation-ledger.ts";
|
|
328
|
-
export {
|
|
329
|
-
DISPATCH_STUB_EVENT,
|
|
330
|
-
buildDispatchStubFact,
|
|
331
|
-
reconcileInvocation,
|
|
332
|
-
} from "./activation-reconciliation.ts";
|
|
333
|
-
export type {
|
|
334
|
-
DispatchPointer,
|
|
335
|
-
DispatchStubFact,
|
|
336
|
-
DispatchStubFactInput,
|
|
337
|
-
ProcessLivenessFact,
|
|
338
|
-
ReconciliationOutcome,
|
|
339
|
-
ReconciliationSubject,
|
|
340
|
-
} from "./activation-reconciliation.ts";
|
|
341
306
|
export {
|
|
342
307
|
TOOL_EXECUTION_UPDATE_HEARTBEAT,
|
|
343
308
|
TOOL_EXECUTION_UPDATE_THROTTLE_MS,
|
|
@@ -2195,11 +2160,9 @@ export function createRoleRuntimeExtension(
|
|
|
2195
2160
|
// Production topology only (ADR 0048/0049): no test-only ledger hooks.
|
|
2196
2161
|
// Admit durable session file first so lifecycle getEntries/appendEntry are truthful:
|
|
2197
2162
|
// deferred SM materialization must not wipe an in-memory principal marker.
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
const ledgerHome = resolveActivationLedgerHomeForPath(sessionFile);
|
|
2202
|
-
const session = durableSessionPointer(ctx.sessionManager);
|
|
2163
|
+
// #855: two-face waiting.jsonl write removed — fail-closed book-key + session only.
|
|
2164
|
+
resolveBookKeyFromGit(ctx.cwd);
|
|
2165
|
+
durableSessionPointer(ctx.sessionManager);
|
|
2203
2166
|
|
|
2204
2167
|
// Station children (court diarist, inner-gate summons) omit navigator sidecar (#840).
|
|
2205
2168
|
// Top-level public entry legs still attend automatically.
|
|
@@ -2314,16 +2277,6 @@ export function createRoleRuntimeExtension(
|
|
|
2314
2277
|
if (entry.role === "coder") coder.armSubmissionGate(ctx.cwd, ctx.sessionManager);
|
|
2315
2278
|
else fixer.armSubmissionGate(ctx.cwd, ctx.sessionManager);
|
|
2316
2279
|
}
|
|
2317
|
-
appendAcceptedActivationToBook({
|
|
2318
|
-
ledgerHome,
|
|
2319
|
-
fact: buildAcceptedActivationFact({
|
|
2320
|
-
role: entry.role,
|
|
2321
|
-
observedAt: clock(),
|
|
2322
|
-
bookKey,
|
|
2323
|
-
session,
|
|
2324
|
-
correlation,
|
|
2325
|
-
}),
|
|
2326
|
-
});
|
|
2327
2280
|
admitted = true;
|
|
2328
2281
|
} catch (error) {
|
|
2329
2282
|
failInfrastructure(error, ctx);
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
const DISPATCH_STUB_EVENT = "dispatch-stub";
|
|
2
|
-
function buildDispatchStubFact(input) {
|
|
3
|
-
return {
|
|
4
|
-
event: DISPATCH_STUB_EVENT,
|
|
5
|
-
observedAt: input.observedAt,
|
|
6
|
-
bookKey: input.bookKey,
|
|
7
|
-
dispatch: input.dispatch.kind === "process" ? { kind: "process", pid: input.dispatch.pid } : { kind: "opaque", ref: input.dispatch.ref },
|
|
8
|
-
correlation: { kind: "caller", id: input.correlation.id }
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
function callerCorrelationId(correlation) {
|
|
12
|
-
return correlation.kind === "caller" ? correlation.id : void 0;
|
|
13
|
-
}
|
|
14
|
-
function dispatchMatchesActivation(dispatch, activation) {
|
|
15
|
-
const activationId = callerCorrelationId(activation.correlation);
|
|
16
|
-
return activationId !== void 0 && activationId === dispatch.correlation.id && activation.bookKey === dispatch.bookKey;
|
|
17
|
-
}
|
|
18
|
-
function reconcileInvocation(subject) {
|
|
19
|
-
const { dispatch, activation, process } = subject;
|
|
20
|
-
if (activation !== void 0) {
|
|
21
|
-
if (dispatch !== void 0 && dispatchMatchesActivation(dispatch, activation)) {
|
|
22
|
-
return {
|
|
23
|
-
kind: "matched",
|
|
24
|
-
correlationId: dispatch.correlation.id,
|
|
25
|
-
bookKey: dispatch.bookKey
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
return {
|
|
29
|
-
kind: "activation-without-dispatch",
|
|
30
|
-
correlationId: callerCorrelationId(activation.correlation),
|
|
31
|
-
bookKey: activation.bookKey
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
if (dispatch !== void 0) {
|
|
35
|
-
if (process === void 0) {
|
|
36
|
-
throw new TypeError(
|
|
37
|
-
"reconcileInvocation requires process liveness when activation is absent"
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
if (process.state === "alive") {
|
|
41
|
-
return {
|
|
42
|
-
kind: "pending",
|
|
43
|
-
correlationId: dispatch.correlation.id,
|
|
44
|
-
bookKey: dispatch.bookKey
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
kind: "ghost",
|
|
49
|
-
correlationId: dispatch.correlation.id,
|
|
50
|
-
bookKey: dispatch.bookKey
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
throw new TypeError(
|
|
54
|
-
"reconcileInvocation requires a dispatch stub and/or an accepted-activation fact"
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
export {
|
|
58
|
-
DISPATCH_STUB_EVENT,
|
|
59
|
-
buildDispatchStubFact,
|
|
60
|
-
reconcileInvocation
|
|
61
|
-
};
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import type { AcceptedActivationFact } from "./activation-ledger.ts";
|
|
2
|
-
|
|
3
|
-
/** Event category for pre-dispatch stub facts (#11 producer contract). */
|
|
4
|
-
export const DISPATCH_STUB_EVENT = "dispatch-stub" as const;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Non-content dispatch pointer. Carries process identity or an opaque ref —
|
|
8
|
-
* never prompt/argv/content bytes (ADR 0049).
|
|
9
|
-
*/
|
|
10
|
-
export type DispatchPointer =
|
|
11
|
-
| { readonly kind: "process"; readonly pid: number }
|
|
12
|
-
| { readonly kind: "opaque"; readonly ref: string };
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Minimum typed dispatch stub the #11 producer supplies before ignition.
|
|
16
|
-
* Index-only: correlation, book, time, event category, non-content pointer.
|
|
17
|
-
*/
|
|
18
|
-
export type DispatchStubFact = {
|
|
19
|
-
readonly event: typeof DISPATCH_STUB_EVENT;
|
|
20
|
-
readonly observedAt: string;
|
|
21
|
-
readonly bookKey: string;
|
|
22
|
-
readonly dispatch: DispatchPointer;
|
|
23
|
-
readonly correlation: { readonly kind: "caller"; readonly id: string };
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/** Trusted typed inputs for building the closed dispatch stub (fact minus event discriminant). */
|
|
27
|
-
export type DispatchStubFactInput = Omit<DispatchStubFact, "event">;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Construct the closed dispatch stub from trusted typed inputs only.
|
|
31
|
-
* Nested dispatch/correlation are rebuilt closed (ADR 0049: first-phase ledger holds zero content bytes by construction).
|
|
32
|
-
*/
|
|
33
|
-
export function buildDispatchStubFact(input: DispatchStubFactInput): DispatchStubFact {
|
|
34
|
-
return {
|
|
35
|
-
event: DISPATCH_STUB_EVENT,
|
|
36
|
-
observedAt: input.observedAt,
|
|
37
|
-
bookKey: input.bookKey,
|
|
38
|
-
dispatch: input.dispatch.kind === "process"
|
|
39
|
-
? { kind: "process", pid: input.dispatch.pid }
|
|
40
|
-
: { kind: "opaque", ref: input.dispatch.ref },
|
|
41
|
-
correlation: { kind: "caller", id: input.correlation.id },
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Truthful process liveness supplied by the consumer.
|
|
47
|
-
* Reconciliation never probes, kills, or retries processes (D1a / #78).
|
|
48
|
-
*/
|
|
49
|
-
export type ProcessLivenessFact =
|
|
50
|
-
| { readonly state: "alive" }
|
|
51
|
-
| { readonly state: "terminated" };
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* One reconciliation subject as an exclusive complete union (four outcomes only):
|
|
55
|
-
* - activation present → activation required; dispatch/process optional
|
|
56
|
-
* - activation absent → dispatch + truthful process required; activation prohibited
|
|
57
|
-
* No session-file existence oracle (ADR 0047). Untyped JS still hits runtime TypeError.
|
|
58
|
-
*/
|
|
59
|
-
export type ReconciliationSubject =
|
|
60
|
-
| {
|
|
61
|
-
readonly activation: AcceptedActivationFact;
|
|
62
|
-
readonly dispatch?: DispatchStubFact;
|
|
63
|
-
readonly process?: ProcessLivenessFact;
|
|
64
|
-
}
|
|
65
|
-
| {
|
|
66
|
-
readonly activation?: undefined;
|
|
67
|
-
readonly dispatch: DispatchStubFact;
|
|
68
|
-
readonly process: ProcessLivenessFact;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Exactly four typed outcomes (D1a):
|
|
73
|
-
* - matched — dispatch stub and matching accepted-activation fact
|
|
74
|
-
* - pending — dispatch present, no activation, process still alive
|
|
75
|
-
* - ghost — dispatch present, no activation, process terminated
|
|
76
|
-
* - activation-without-dispatch — activation fact with no matching stub
|
|
77
|
-
*/
|
|
78
|
-
export type ReconciliationOutcome =
|
|
79
|
-
| {
|
|
80
|
-
readonly kind: "matched";
|
|
81
|
-
readonly correlationId: string;
|
|
82
|
-
readonly bookKey: string;
|
|
83
|
-
}
|
|
84
|
-
| {
|
|
85
|
-
readonly kind: "pending";
|
|
86
|
-
readonly correlationId: string;
|
|
87
|
-
readonly bookKey: string;
|
|
88
|
-
}
|
|
89
|
-
| {
|
|
90
|
-
readonly kind: "ghost";
|
|
91
|
-
readonly correlationId: string;
|
|
92
|
-
readonly bookKey: string;
|
|
93
|
-
}
|
|
94
|
-
| {
|
|
95
|
-
readonly kind: "activation-without-dispatch";
|
|
96
|
-
readonly correlationId: string | undefined;
|
|
97
|
-
readonly bookKey: string;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
function callerCorrelationId(
|
|
101
|
-
correlation: AcceptedActivationFact["correlation"] | DispatchStubFact["correlation"],
|
|
102
|
-
): string | undefined {
|
|
103
|
-
return correlation.kind === "caller" ? correlation.id : undefined;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function dispatchMatchesActivation(
|
|
107
|
-
dispatch: DispatchStubFact,
|
|
108
|
-
activation: AcceptedActivationFact,
|
|
109
|
-
): boolean {
|
|
110
|
-
const activationId = callerCorrelationId(activation.correlation);
|
|
111
|
-
return (
|
|
112
|
-
activationId !== undefined
|
|
113
|
-
&& activationId === dispatch.correlation.id
|
|
114
|
-
&& activation.bookKey === dispatch.bookKey
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Reconcile one invocation's typed dispatch/activation facts against truthful
|
|
120
|
-
* process liveness. Pure: never kills, never retries, never reads session files.
|
|
121
|
-
*/
|
|
122
|
-
export function reconcileInvocation(subject: ReconciliationSubject): ReconciliationOutcome {
|
|
123
|
-
const { dispatch, activation, process } = subject;
|
|
124
|
-
|
|
125
|
-
if (activation !== undefined) {
|
|
126
|
-
if (dispatch !== undefined && dispatchMatchesActivation(dispatch, activation)) {
|
|
127
|
-
return {
|
|
128
|
-
kind: "matched",
|
|
129
|
-
correlationId: dispatch.correlation.id,
|
|
130
|
-
bookKey: dispatch.bookKey,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
return {
|
|
134
|
-
kind: "activation-without-dispatch",
|
|
135
|
-
correlationId: callerCorrelationId(activation.correlation),
|
|
136
|
-
bookKey: activation.bookKey,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (dispatch !== undefined) {
|
|
141
|
-
if (process === undefined) {
|
|
142
|
-
throw new TypeError(
|
|
143
|
-
"reconcileInvocation requires process liveness when activation is absent",
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
if (process.state === "alive") {
|
|
147
|
-
return {
|
|
148
|
-
kind: "pending",
|
|
149
|
-
correlationId: dispatch.correlation.id,
|
|
150
|
-
bookKey: dispatch.bookKey,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
return {
|
|
154
|
-
kind: "ghost",
|
|
155
|
-
correlationId: dispatch.correlation.id,
|
|
156
|
-
bookKey: dispatch.bookKey,
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
throw new TypeError(
|
|
161
|
-
"reconcileInvocation requires a dispatch stub and/or an accepted-activation fact",
|
|
162
|
-
);
|
|
163
|
-
}
|