@akagilnc/pi-workflow-roles 0.1.4620 → 0.1.4641
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 +962 -547
- package/dist/activation-ledger-topology.js +0 -4
- package/dist/gatekeeper-role.js +76 -6
- package/dist/headless-host/production-host.js +923 -508
- package/dist/migrate-book-topology.js +9 -0
- package/dist/pi/role-turn-host.js +0 -3
- package/dist/public-cli/auto-resume.js +26 -0
- package/dist/public-cli/countersign-run.js +31 -10
- package/dist/public-cli/main.js +1456 -1068
- package/dist/public-cli/post-admission.js +101 -7
- package/dist/public-cli/process-cancel.js +57 -0
- package/dist/public-cli/settlement.js +95 -2
- package/dist/public-role-summons.js +19 -0
- package/dist/secretariat-contracts.js +4 -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 +3 -3
- package/src/audit-escalation.ts +1 -1
- package/src/gatekeeper-pass-envelope.ts +37 -5
- package/src/gatekeeper-role.ts +125 -20
- package/src/host-contracts.ts +22 -2
- package/src/pi/adapter.ts +5 -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/countersign-run.ts +50 -13
- 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/public-cli/settlement.ts +117 -1
- package/src/public-role-summons.ts +30 -4
- package/src/role-envelope.ts +27 -10
- package/src/role-runtime.ts +146 -55
- package/src/secretariat-contracts.ts +18 -0
- package/dist/activation-reconciliation.js +0 -61
- package/src/activation-reconciliation.ts +0 -163
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,
|
|
@@ -349,15 +314,23 @@ export {
|
|
|
349
314
|
writeToolExecutionObservationRecord,
|
|
350
315
|
} from "./tool-execution-observation.ts";
|
|
351
316
|
export type { ToolExecutionObservationRecord, ToolExecutionObservationWriter } from "./tool-execution-observation.ts";
|
|
352
|
-
|
|
317
|
+
import {
|
|
353
318
|
NOTARY_OUTPUT_TOOL,
|
|
354
319
|
INSPECTOR_OUTPUT_TOOL,
|
|
355
320
|
GatekeeperDecisionError,
|
|
356
321
|
createGatekeeperOutputTool,
|
|
357
322
|
runGatekeeper,
|
|
323
|
+
gateOfficerForSubject,
|
|
358
324
|
} from "./gatekeeper-role.ts";
|
|
325
|
+
export {
|
|
326
|
+
NOTARY_OUTPUT_TOOL,
|
|
327
|
+
INSPECTOR_OUTPUT_TOOL,
|
|
328
|
+
GatekeeperDecisionError,
|
|
329
|
+
createGatekeeperOutputTool,
|
|
330
|
+
runGatekeeper,
|
|
331
|
+
gateOfficerForSubject,
|
|
332
|
+
};
|
|
359
333
|
export type { GatekeeperResult, GatekeeperSubject, GatekeeperNonPassResult, GateOfficer, RunGatekeeperOptions } from "./gatekeeper-role.ts";
|
|
360
|
-
export { gateOfficerForSubject } from "./gatekeeper-role.ts";
|
|
361
334
|
import { ParentQueueReaskError } from "./submission-errors.ts";
|
|
362
335
|
|
|
363
336
|
export {
|
|
@@ -581,6 +554,13 @@ type NavigatorAttendanceDependency = Omit<NavigatorAttendance, "knownRoutePlaybo
|
|
|
581
554
|
export type RoleRuntimeDependencies = {
|
|
582
555
|
/** Package root for packaged engine-note resolution (#879). */
|
|
583
556
|
packageRoot?: string;
|
|
557
|
+
/**
|
|
558
|
+
* Composition-root adapter table for nested gate officer summons (#969).
|
|
559
|
+
* Production leaves unset (default pi + packaged externals). Tests inject
|
|
560
|
+
* faux nested hosts so prepareRoleEnvelope.requireGatekeeperPass is bitten
|
|
561
|
+
* without reimplementing the envelope summon closure.
|
|
562
|
+
*/
|
|
563
|
+
hostAdapters?: readonly import("./public-cli/role-turn-host-resolution.ts").NamedRoleTurnHostAdapter[];
|
|
584
564
|
/** Non-identity opening materials delivered in the ordinary role brief. */
|
|
585
565
|
loadRoleReferenceMaterials?(role: PackagedRole): Promise<string>;
|
|
586
566
|
loadJudgeSoul(): Promise<string>;
|
|
@@ -1034,18 +1014,137 @@ const COUNTERSIGN_QUEUE_STATUSES = new Set(["converged", "continue", "escalate"]
|
|
|
1034
1014
|
const COUNTERSIGN_STATUS_REASK =
|
|
1035
1015
|
"countersignStatus 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。" as const;
|
|
1036
1016
|
|
|
1017
|
+
/**
|
|
1018
|
+
* #969 authorized non-pi hosts: Secretariat converged is a candidate ticket —
|
|
1019
|
+
* AK-side submission gate summons 给事中. pi keeps mid-turn summon tool only.
|
|
1020
|
+
*/
|
|
1021
|
+
const SECRETARIAT_SUBMISSION_GATE_HOSTS = new Set(["codex", "claude", "grok-build"]);
|
|
1022
|
+
|
|
1023
|
+
/** Secretariat status words the non-pi submission gate reads (#969). */
|
|
1024
|
+
const SECRETARIAT_QUEUE_STATUSES = new Set(["converged", "escalate"]);
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* Plain-language re-ask when non-pi secretariatStatus is not a known queue word.
|
|
1028
|
+
* Back to secretariat itself — 给事中 is not summoned (#969 / ADR 0055).
|
|
1029
|
+
*/
|
|
1030
|
+
const SECRETARIAT_STATUS_REASK =
|
|
1031
|
+
"secretariatStatus 不是 converged、escalate 之一。请重新交卷,status 写明其一。" as const;
|
|
1032
|
+
|
|
1037
1033
|
/**
|
|
1038
1034
|
* #924 Secretariat on the shared filed-officer envelope + non-terminating
|
|
1039
1035
|
* summon-countersign tool (auditor dossier extension pattern).
|
|
1040
1036
|
* Nested countersign lifecycle stays on summonPublicRole (ADR 0018).
|
|
1041
|
-
* #924 / 第 0
|
|
1037
|
+
* #924 / 第 0 条 (pi): output tool records the receipt as submitted — no status
|
|
1042
1038
|
* shape/value reject or reask; legality is content-layer / downstream.
|
|
1039
|
+
* #969 (codex/claude/grok-build): converged arms 既有交卷闸 → 给事中; escalate
|
|
1040
|
+
* skips the gate; other values reask secretariat (ADR 0055).
|
|
1043
1041
|
*/
|
|
1044
1042
|
export function createSecretariatRoleRuntime(
|
|
1045
1043
|
roleHost: RoleHost,
|
|
1046
1044
|
dependencies: SecretariatRuntimeDependencies,
|
|
1045
|
+
hostActions?: import("./host-contracts.ts").HostGatekeeperActions,
|
|
1047
1046
|
) {
|
|
1048
1047
|
let parentInstruction = "";
|
|
1048
|
+
// #969: non-pi submission gate only — pi mid-turn summon path stays untouched.
|
|
1049
|
+
const beforeAccept: FiledOfficerBeforeAccept | undefined =
|
|
1050
|
+
hostActions !== undefined && roleHost.requireGatekeeperPass !== undefined
|
|
1051
|
+
? async ({ toolCallId, parameters, signal, ctx }) => {
|
|
1052
|
+
const host =
|
|
1053
|
+
typeof ctx.host === "string" && ctx.host.trim() !== ""
|
|
1054
|
+
? ctx.host.trim()
|
|
1055
|
+
: undefined;
|
|
1056
|
+
if (host === undefined || !SECRETARIAT_SUBMISSION_GATE_HOSTS.has(host)) {
|
|
1057
|
+
return undefined;
|
|
1058
|
+
}
|
|
1059
|
+
const record =
|
|
1060
|
+
parameters !== null && typeof parameters === "object" && !Array.isArray(parameters)
|
|
1061
|
+
? (parameters as Record<string, unknown>)
|
|
1062
|
+
: undefined;
|
|
1063
|
+
const status =
|
|
1064
|
+
record !== undefined && typeof record.secretariatStatus === "string"
|
|
1065
|
+
? record.secretariatStatus
|
|
1066
|
+
: undefined;
|
|
1067
|
+
if (status === undefined || !SECRETARIAT_QUEUE_STATUSES.has(status)) {
|
|
1068
|
+
throw new ParentQueueReaskError(SECRETARIAT_STATUS_REASK);
|
|
1069
|
+
}
|
|
1070
|
+
if (status === "escalate") {
|
|
1071
|
+
// Parent escalate → throw to caller as-is; 给事中 does not attend (#969).
|
|
1072
|
+
return undefined;
|
|
1073
|
+
}
|
|
1074
|
+
try {
|
|
1075
|
+
const pass = await roleHost.requireGatekeeperPass!({
|
|
1076
|
+
context: ctx,
|
|
1077
|
+
subject: { kind: "secretariat_verdict" },
|
|
1078
|
+
...(signal === undefined ? {} : { signal }),
|
|
1079
|
+
hostActions,
|
|
1080
|
+
toolCallId,
|
|
1081
|
+
// #879: this-turn typed payload — identity-bound at submit site.
|
|
1082
|
+
submission: parameters,
|
|
1083
|
+
});
|
|
1084
|
+
// #969: book 给事中 署 snapshot for seat settlement projection (receipt + runId).
|
|
1085
|
+
// Ledger accepted stays LLM params (#836); public terminal reads this entry.
|
|
1086
|
+
if (
|
|
1087
|
+
pass !== undefined
|
|
1088
|
+
&& pass !== null
|
|
1089
|
+
&& typeof pass === "object"
|
|
1090
|
+
&& "receipt" in pass
|
|
1091
|
+
) {
|
|
1092
|
+
const {
|
|
1093
|
+
SECRETARIAT_GATE_OFFICER_ENTRY_TYPE,
|
|
1094
|
+
} = await import("./secretariat-contracts.ts");
|
|
1095
|
+
const runId =
|
|
1096
|
+
typeof pass.runId === "string" && pass.runId.trim() !== ""
|
|
1097
|
+
? pass.runId
|
|
1098
|
+
: undefined;
|
|
1099
|
+
ctx.sessionManager.appendCustomEntry?.(SECRETARIAT_GATE_OFFICER_ENTRY_TYPE, {
|
|
1100
|
+
officer: "countersign",
|
|
1101
|
+
receipt: pass.receipt,
|
|
1102
|
+
...(runId === undefined ? {} : { runId }),
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
return undefined;
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
// #969: 给事中上呈 ends parent with officer receipt (no retry / 不擅改).
|
|
1108
|
+
if (
|
|
1109
|
+
error instanceof GatekeeperDecisionError
|
|
1110
|
+
&& error.result.status === "escalate"
|
|
1111
|
+
) {
|
|
1112
|
+
const receipt = error.result.receipt;
|
|
1113
|
+
const receiptRecord =
|
|
1114
|
+
receipt !== null && typeof receipt === "object" && !Array.isArray(receipt)
|
|
1115
|
+
? (receipt as Record<string, unknown>)
|
|
1116
|
+
: undefined;
|
|
1117
|
+
const runId =
|
|
1118
|
+
typeof error.result.runId === "string" && error.result.runId.trim() !== ""
|
|
1119
|
+
? error.result.runId
|
|
1120
|
+
: undefined;
|
|
1121
|
+
// Durable on every host: envelope persists custom entries only
|
|
1122
|
+
// (toolResult rows are memory-only on headless/ACP — #617/#959).
|
|
1123
|
+
const {
|
|
1124
|
+
SECRETARIAT_GATE_OFFICER_ENTRY_TYPE,
|
|
1125
|
+
} = await import("./secretariat-contracts.ts");
|
|
1126
|
+
ctx.sessionManager.appendCustomEntry?.(SECRETARIAT_GATE_OFFICER_ENTRY_TYPE, {
|
|
1127
|
+
officer: "countersign",
|
|
1128
|
+
receipt,
|
|
1129
|
+
...(runId === undefined ? {} : { runId }),
|
|
1130
|
+
});
|
|
1131
|
+
const { buildAuditEscalationResult } = await import("./audit-escalation.ts");
|
|
1132
|
+
return buildAuditEscalationResult(
|
|
1133
|
+
{
|
|
1134
|
+
status: "escalate",
|
|
1135
|
+
officer: "countersign",
|
|
1136
|
+
...(receiptRecord !== undefined
|
|
1137
|
+
&& Object.prototype.hasOwnProperty.call(receiptRecord, "decisionGate")
|
|
1138
|
+
? { decisionGate: receiptRecord.decisionGate }
|
|
1139
|
+
: {}),
|
|
1140
|
+
},
|
|
1141
|
+
receipt,
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
throw error;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
: undefined;
|
|
1049
1148
|
const base = createFiledOfficerRuntime(
|
|
1050
1149
|
roleHost,
|
|
1051
1150
|
{
|
|
@@ -1053,6 +1152,7 @@ export function createSecretariatRoleRuntime(
|
|
|
1053
1152
|
tool: SECRETARIAT_OUTPUT_TOOL_SPEC,
|
|
1054
1153
|
acceptedText: SECRETARIAT_ACCEPTED_TEXT,
|
|
1055
1154
|
soulTag: "secretariat",
|
|
1155
|
+
...(beforeAccept === undefined ? {} : { beforeAccept }),
|
|
1056
1156
|
},
|
|
1057
1157
|
dependencies,
|
|
1058
1158
|
);
|
|
@@ -1893,7 +1993,10 @@ export function createRoleRuntimeExtension(
|
|
|
1893
1993
|
...(dependencies.packageRoot === undefined
|
|
1894
1994
|
? {}
|
|
1895
1995
|
: { packageRoot: dependencies.packageRoot }),
|
|
1896
|
-
|
|
1996
|
+
...(dependencies.hostAdapters === undefined
|
|
1997
|
+
? {}
|
|
1998
|
+
: { hostAdapters: dependencies.hostAdapters }),
|
|
1999
|
+
}, hostActions);
|
|
1897
2000
|
const merger = createMergerRoleRuntime(roleHost, {
|
|
1898
2001
|
async loadSoul() { if (!dependencies.loadMergerSoul) throw new Error("Merger runtime dependencies are not configured"); return dependencies.loadMergerSoul(); },
|
|
1899
2002
|
async loadInput(path) { if (!dependencies.loadMergerInput) throw new Error("Merger runtime dependencies are not configured"); return dependencies.loadMergerInput(path); },
|
|
@@ -2195,11 +2298,9 @@ export function createRoleRuntimeExtension(
|
|
|
2195
2298
|
// Production topology only (ADR 0048/0049): no test-only ledger hooks.
|
|
2196
2299
|
// Admit durable session file first so lifecycle getEntries/appendEntry are truthful:
|
|
2197
2300
|
// 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);
|
|
2301
|
+
// #855: two-face waiting.jsonl write removed — fail-closed book-key + session only.
|
|
2302
|
+
resolveBookKeyFromGit(ctx.cwd);
|
|
2303
|
+
durableSessionPointer(ctx.sessionManager);
|
|
2203
2304
|
|
|
2204
2305
|
// Station children (court diarist, inner-gate summons) omit navigator sidecar (#840).
|
|
2205
2306
|
// Top-level public entry legs still attend automatically.
|
|
@@ -2314,16 +2415,6 @@ export function createRoleRuntimeExtension(
|
|
|
2314
2415
|
if (entry.role === "coder") coder.armSubmissionGate(ctx.cwd, ctx.sessionManager);
|
|
2315
2416
|
else fixer.armSubmissionGate(ctx.cwd, ctx.sessionManager);
|
|
2316
2417
|
}
|
|
2317
|
-
appendAcceptedActivationToBook({
|
|
2318
|
-
ledgerHome,
|
|
2319
|
-
fact: buildAcceptedActivationFact({
|
|
2320
|
-
role: entry.role,
|
|
2321
|
-
observedAt: clock(),
|
|
2322
|
-
bookKey,
|
|
2323
|
-
session,
|
|
2324
|
-
correlation,
|
|
2325
|
-
}),
|
|
2326
|
-
});
|
|
2327
2418
|
admitted = true;
|
|
2328
2419
|
} catch (error) {
|
|
2329
2420
|
failInfrastructure(error, ctx);
|
|
@@ -11,6 +11,24 @@ export const SECRETARIAT_OUTPUT_TOOL_NAME = "ak_secretariat_output";
|
|
|
11
11
|
export const SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_NAME =
|
|
12
12
|
"ak_secretariat_summon_countersign";
|
|
13
13
|
export const SECRETARIAT_ACCEPTED_TEXT = "中书省回执已接受";
|
|
14
|
+
/**
|
|
15
|
+
* #969 durable custom entry: 给事中 terminal (署|上呈) for seat settlement projection.
|
|
16
|
+
* Carries original receipt bytes + nested runId. Envelope persists custom entries
|
|
17
|
+
* (not toolResult rows) — headless/ACP safe. Single authority for public terminal
|
|
18
|
+
* officer projection (pass and escalate share this entry).
|
|
19
|
+
*/
|
|
20
|
+
export const SECRETARIAT_GATE_OFFICER_ENTRY_TYPE =
|
|
21
|
+
"ak-secretariat-gate-officer" as const;
|
|
22
|
+
|
|
23
|
+
/** Decisive-facts key for nested 给事中 terminal on public Secretariat settlement. */
|
|
24
|
+
export const SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY = "countersignTerminal" as const;
|
|
25
|
+
|
|
26
|
+
export type SecretariatCountersignTerminalFact = {
|
|
27
|
+
/** Officer receipt original bytes — never rewritten. */
|
|
28
|
+
readonly receipt: unknown;
|
|
29
|
+
/** Nested 给事中 runId when known. */
|
|
30
|
+
readonly runId?: string;
|
|
31
|
+
};
|
|
14
32
|
|
|
15
33
|
export type SecretariatVerdict =
|
|
16
34
|
| {
|
|
@@ -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
|
-
}
|