@akagilnc/pi-workflow-roles 0.1.2139 → 0.1.2146
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/doctor-contracts.js +10 -2
- package/dist/engine-labor-fallback.js +129 -114
- package/dist/merger-contracts.js +9 -2
- package/dist/navigator-attendance.js +9 -1
- package/dist/package-contracts/judge-output.js +8 -1
- package/dist/package-contracts/reviewer-output.js +9 -1
- package/dist/package-contracts/terminating-tools.js +14 -5
- package/dist/public-cli/main.js +116 -64
- package/package.json +1 -1
- package/scripts/build-package.mjs +2 -0
- package/src/doctor-contracts.ts +19 -3
- package/src/engine-labor-fallback.ts +100 -2
- package/src/factory-board.ts +6 -2
- package/src/merger-contracts.ts +18 -3
- package/src/navigator-attendance.ts +16 -1
- package/src/package-contracts/judge-output.ts +25 -2
- package/src/package-contracts/reviewer-output.ts +23 -2
- package/src/package-contracts/terminating-tools.ts +22 -6
- package/src/package-contracts/worker-output.ts +10 -2
- package/src/public-cli/settlement.ts +51 -17
- package/src/role-runtime.ts +8 -1
- package/src/taishi-metric-families/acceptance-success-rework.ts +6 -3
|
@@ -8,6 +8,7 @@ import { createAssistantMessageEventStream } from "@earendil-works/pi-ai";
|
|
|
8
8
|
import { Type, type Static } from "typebox";
|
|
9
9
|
import { Value } from "typebox/value";
|
|
10
10
|
|
|
11
|
+
import { seatFallbackBaseStatus } from "./engine-labor-fallback.ts";
|
|
11
12
|
import {
|
|
12
13
|
NAVIGATOR_INVOCATION_ENTRY,
|
|
13
14
|
mintNavigatorInvocationId,
|
|
@@ -461,6 +462,18 @@ export type NavigatorCandidateSelection = {
|
|
|
461
462
|
readonly matchedToSettlement: boolean;
|
|
462
463
|
};
|
|
463
464
|
|
|
465
|
+
/** Match route-playbook statuses against settlement, treating seat-fallback taint as base-equivalent. */
|
|
466
|
+
function statusListMatchesSettlement(
|
|
467
|
+
candidateStatuses: readonly string[],
|
|
468
|
+
settlementStatus: string,
|
|
469
|
+
): boolean {
|
|
470
|
+
if (candidateStatuses.includes(settlementStatus)) return true;
|
|
471
|
+
const settlementBase = seatFallbackBaseStatus(settlementStatus);
|
|
472
|
+
return candidateStatuses.some(
|
|
473
|
+
(status) => seatFallbackBaseStatus(status) === settlementBase,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
464
477
|
export function selectNavigatorCandidate(
|
|
465
478
|
candidates: readonly NavigatorCandidate[],
|
|
466
479
|
settlement: NavigatorSettlement,
|
|
@@ -477,7 +490,9 @@ export function selectNavigatorCandidate(
|
|
|
477
490
|
if (rolePhaseMatched.length > 0) {
|
|
478
491
|
if (settlement.status !== undefined) {
|
|
479
492
|
const statusSpecific = rolePhaseMatched.find(
|
|
480
|
-
(candidate) =>
|
|
493
|
+
(candidate) =>
|
|
494
|
+
candidate.matches?.statuses !== undefined &&
|
|
495
|
+
statusListMatchesSettlement(candidate.matches.statuses, settlement.status!),
|
|
481
496
|
);
|
|
482
497
|
if (statusSpecific !== undefined) {
|
|
483
498
|
return { candidate: statusSpecific, matchedToSettlement: true };
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
/** Package-owned Judge output leaf — no role registration surface. */
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
seatFallbackBaseStatus,
|
|
5
|
+
seatFallbackStatusHasLawfulEvidence,
|
|
6
|
+
type SeatFallbackTaintedStatus,
|
|
7
|
+
type WithEngineLaborFallback,
|
|
8
|
+
} from "../engine-labor-fallback.ts";
|
|
9
|
+
|
|
3
10
|
export const JUDGE_OUTPUT_TOOL_NAME = "ak_judge_output";
|
|
4
11
|
export const JUDGE_ACCEPTED_TEXT = "Judge verdict accepted";
|
|
5
12
|
|
|
@@ -10,7 +17,7 @@ export type JudgeClass = {
|
|
|
10
17
|
disposition: string;
|
|
11
18
|
};
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
type JudgeVerdictClean =
|
|
14
21
|
| { judgeStatus: "converged"; note?: string; evidence?: unknown }
|
|
15
22
|
| {
|
|
16
23
|
judgeStatus: "continue";
|
|
@@ -26,6 +33,11 @@ export type JudgeVerdict =
|
|
|
26
33
|
evidence?: unknown;
|
|
27
34
|
};
|
|
28
35
|
|
|
36
|
+
/** Clean submission shape or seat-fallback tainted accepted receipt (ADR 0071). */
|
|
37
|
+
export type JudgeVerdict =
|
|
38
|
+
| JudgeVerdictClean
|
|
39
|
+
| WithEngineLaborFallback<JudgeVerdictClean>;
|
|
40
|
+
|
|
29
41
|
export function validateAcceptedJudgeDetails(verdict: unknown): JudgeVerdict {
|
|
30
42
|
if (verdict === null || typeof verdict !== "object" || Array.isArray(verdict)) throw new Error("Judge verdict has no execution discriminator");
|
|
31
43
|
let judgeStatus: unknown;
|
|
@@ -34,6 +46,17 @@ export function validateAcceptedJudgeDetails(verdict: unknown): JudgeVerdict {
|
|
|
34
46
|
} catch {
|
|
35
47
|
throw new Error("Judge verdict has no execution discriminator");
|
|
36
48
|
}
|
|
37
|
-
if (
|
|
49
|
+
if (typeof judgeStatus !== "string") {
|
|
50
|
+
throw new Error("Judge verdict has no execution discriminator");
|
|
51
|
+
}
|
|
52
|
+
const base = seatFallbackBaseStatus(judgeStatus) as SeatFallbackTaintedStatus<
|
|
53
|
+
"converged" | "continue" | "escalate"
|
|
54
|
+
>;
|
|
55
|
+
if (
|
|
56
|
+
["converged", "continue", "escalate"].includes(base) &&
|
|
57
|
+
seatFallbackStatusHasLawfulEvidence(judgeStatus, verdict)
|
|
58
|
+
) {
|
|
59
|
+
return verdict as JudgeVerdict;
|
|
60
|
+
}
|
|
38
61
|
throw new Error("Judge verdict has no execution discriminator");
|
|
39
62
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/** Package-owned Reviewer intent and runtime-receipt leaves — no role registration surface. */
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
seatFallbackBaseStatus,
|
|
5
|
+
seatFallbackStatusHasLawfulEvidence,
|
|
6
|
+
type SeatFallbackTaintedStatus,
|
|
7
|
+
type WithEngineLaborFallback,
|
|
8
|
+
} from "../engine-labor-fallback.ts";
|
|
3
9
|
import type { ReviewerAcceptedEvidence, ReviewerFailureClassification, ReviewerWorkspaceDisposition } from "../reviewer-execution-ledger.ts";
|
|
4
10
|
|
|
5
11
|
export const REVIEWER_OUTPUT_TOOL_NAME = "ak_reviewer_output";
|
|
@@ -28,7 +34,7 @@ export type RuntimeReviewerAcceptedBatch = Readonly<{
|
|
|
28
34
|
}>;
|
|
29
35
|
/** Honest Spec-child disposition on the receipt face. */
|
|
30
36
|
export type RuntimeReviewerSpecDisposition = "launched" | "skipped-missing";
|
|
31
|
-
|
|
37
|
+
type RuntimeReviewerReceiptV2Clean = Readonly<{
|
|
32
38
|
version: 2;
|
|
33
39
|
status: "completed" | "refused";
|
|
34
40
|
diagnostic?: string;
|
|
@@ -45,6 +51,14 @@ export type RuntimeReviewerReceiptV2 = Readonly<{
|
|
|
45
51
|
target?: ReviewerAcceptedEvidence["target"];
|
|
46
52
|
}>;
|
|
47
53
|
}>;
|
|
54
|
+
/** Clean runtime receipt or seat-fallback tainted accepted receipt (ADR 0071). */
|
|
55
|
+
export type RuntimeReviewerReceiptV2 =
|
|
56
|
+
| RuntimeReviewerReceiptV2Clean
|
|
57
|
+
| WithEngineLaborFallback<
|
|
58
|
+
Omit<RuntimeReviewerReceiptV2Clean, "status"> & {
|
|
59
|
+
status: SeatFallbackTaintedStatus<"completed" | "refused">;
|
|
60
|
+
}
|
|
61
|
+
>;
|
|
48
62
|
|
|
49
63
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
50
64
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -63,6 +77,11 @@ export function validateReviewerIntent(output: unknown): ReviewerIntent {
|
|
|
63
77
|
|
|
64
78
|
/** Validate runtime-owned facts at their real identity seams (target pins + plain text). */
|
|
65
79
|
export function validateRuntimeReviewerReceipt(output: unknown): RuntimeReviewerReceiptV2 {
|
|
80
|
+
const status = read(output, "status");
|
|
81
|
+
// ADR 0071: tainted top-level status requires latch-shaped engineLaborFallback evidence.
|
|
82
|
+
if (typeof status === "string" && !seatFallbackStatusHasLawfulEvidence(status, output)) {
|
|
83
|
+
throw new Error("Reviewer receipt has no recognized execution discriminator");
|
|
84
|
+
}
|
|
66
85
|
const acceptedBatch = read(output, "acceptedBatch");
|
|
67
86
|
const identities = read(output, "identities");
|
|
68
87
|
const construction = read(identities, "construction");
|
|
@@ -129,7 +148,9 @@ export function validateRuntimeReviewerReceipt(output: unknown): RuntimeReviewer
|
|
|
129
148
|
export function projectReviewerIntentToReceipt(intentValue: unknown, receiptValue: unknown): RuntimeReviewerReceiptV2 {
|
|
130
149
|
const intent = validateReviewerIntent(intentValue);
|
|
131
150
|
const receipt = validateRuntimeReviewerReceipt(receiptValue);
|
|
132
|
-
|
|
151
|
+
const receiptStatus = String(receipt.status);
|
|
152
|
+
const receiptBase = seatFallbackBaseStatus(receiptStatus);
|
|
153
|
+
if (receiptBase !== intent.status || (intent.status === "completed" ? receipt.diagnostic !== undefined : receipt.diagnostic !== intent.diagnostic)) {
|
|
133
154
|
throw new Error("Reviewer intent and runtime receipt disagree");
|
|
134
155
|
}
|
|
135
156
|
return receipt;
|
|
@@ -25,6 +25,10 @@ import {
|
|
|
25
25
|
type RuntimeReviewerReceiptV2,
|
|
26
26
|
} from "./reviewer-output.ts";
|
|
27
27
|
import { isAuditEscalationResult } from "../audit-escalation.ts";
|
|
28
|
+
import {
|
|
29
|
+
seatFallbackBaseStatus,
|
|
30
|
+
seatFallbackStatusHasLawfulEvidence,
|
|
31
|
+
} from "../engine-labor-fallback.ts";
|
|
28
32
|
import { DOCTOR_OUTPUT_TOOL_NAME, validateDoctorSubmissionShape, validateRecordedDoctorOutput, type DoctorOutput, type DoctorSubmission } from "../doctor-contracts.ts";
|
|
29
33
|
import { MERGER_ACCEPTED_TEXT, MERGER_OUTPUT_TOOL_NAME, validateMergerOutput, type MergerOutput } from "../merger-contracts.ts";
|
|
30
34
|
import {
|
|
@@ -159,10 +163,19 @@ export function validateAcceptedDetails(
|
|
|
159
163
|
[MERGER_OUTPUT_TOOL_NAME]: ["completed", "escalate"],
|
|
160
164
|
};
|
|
161
165
|
const collectorDiscriminator = toolName === COLLECTOR_OUTPUT_TOOL && Array.isArray(candidate?.groups);
|
|
166
|
+
const baseDiscriminator = typeof discriminator === "string" ? seatFallbackBaseStatus(discriminator) : discriminator;
|
|
167
|
+
// ADR 0071: `-by-fallback` is not independently lawful — require latch-shaped evidence.
|
|
168
|
+
const taintedWithoutEvidence =
|
|
169
|
+
typeof discriminator === "string" &&
|
|
170
|
+
!seatFallbackStatusHasLawfulEvidence(discriminator, details);
|
|
162
171
|
const runtimeBindingMissing =
|
|
163
|
-
(toolName === DOCTOR_OUTPUT_TOOL_NAME &&
|
|
172
|
+
(toolName === DOCTOR_OUTPUT_TOOL_NAME && baseDiscriminator === "completed" && !(candidate?.cost !== null && typeof candidate?.cost === "object")) ||
|
|
164
173
|
(toolName === REVIEWER_OUTPUT_TOOL_NAME && candidate?.version !== 2);
|
|
165
|
-
if (
|
|
174
|
+
if (
|
|
175
|
+
taintedWithoutEvidence ||
|
|
176
|
+
runtimeBindingMissing ||
|
|
177
|
+
(!collectorDiscriminator && (typeof discriminator !== "string" || !lawfulStatuses[toolName].includes(baseDiscriminator as string)))
|
|
178
|
+
) {
|
|
166
179
|
throw new AcceptedDetailsContractError("terminating receipt has no recognized execution discriminator");
|
|
167
180
|
}
|
|
168
181
|
try {
|
|
@@ -196,12 +209,14 @@ export function validateAcceptedLifecycle(
|
|
|
196
209
|
const details = validateAcceptedDetails(toolName, detailsValue);
|
|
197
210
|
if (toolName === DOCTOR_OUTPUT_TOOL_NAME) {
|
|
198
211
|
const testimony = validateDoctorSubmissionShape(argumentsValue);
|
|
199
|
-
if (testimony.status === "refused") {
|
|
212
|
+
if (seatFallbackBaseStatus(String(testimony.status)) === "refused") {
|
|
200
213
|
if (!deepEqual(testimony, details)) throw new Error("accepted tool lifecycle details mismatch");
|
|
201
214
|
return details;
|
|
202
215
|
}
|
|
203
|
-
const receipt = details as DoctorOutput;
|
|
204
|
-
if (receipt.status !== "completed")
|
|
216
|
+
const receipt = details as DoctorOutput & { cost?: unknown };
|
|
217
|
+
if (seatFallbackBaseStatus(String(receipt.status)) !== "completed") {
|
|
218
|
+
throw new Error("accepted tool lifecycle details mismatch");
|
|
219
|
+
}
|
|
205
220
|
const { cost: _runtimeCost, ...projected } = receipt;
|
|
206
221
|
if (!deepEqual(testimony, projected)) throw new Error("accepted tool lifecycle details mismatch");
|
|
207
222
|
return details;
|
|
@@ -226,7 +241,8 @@ export function acceptedFacts(toolName: TerminatingToolName, details: AcceptedDe
|
|
|
226
241
|
case JUDGE_OUTPUT_TOOL_NAME: return { status: (details as { judgeStatus: string }).judgeStatus };
|
|
227
242
|
case MERGER_OUTPUT_TOOL_NAME: {
|
|
228
243
|
const output = details as unknown as Record<string, unknown>;
|
|
229
|
-
|
|
244
|
+
const status = output.status as string;
|
|
245
|
+
return { status, ...(seatFallbackBaseStatus(status) === "completed" && typeof output.mergeCommitId === "string" ? { commit: output.mergeCommitId } : {}) };
|
|
230
246
|
}
|
|
231
247
|
case COLLECTOR_OUTPUT_TOOL:
|
|
232
248
|
return { status: "collected" };
|
|
@@ -15,16 +15,24 @@ export type {
|
|
|
15
15
|
} from "./fixer-output.ts";
|
|
16
16
|
export { fixerPrerequisiteSchema, fixerPrerequisitesSchema, parseFixerPrerequisites, validateFixerPrerequisites } from "./fixer-packet.ts";
|
|
17
17
|
export type { FixerInvocationInput, FixerPrerequisite } from "./fixer-packet.ts";
|
|
18
|
+
import type { WithEngineLaborFallback } from "../engine-labor-fallback.ts";
|
|
18
19
|
import { validateFixerOutput, type FixerOutput } from "./fixer-output.ts";
|
|
19
20
|
|
|
20
21
|
export const CODER_OUTPUT_TOOL_NAME = "ak_coder_output";
|
|
21
22
|
export const CODER_ACCEPTED_TEXT = "Coder report accepted";
|
|
22
23
|
export type WorkerRoleLabel = "Coder" | "Fixer";
|
|
23
|
-
|
|
24
|
+
type CoderOutputClean =
|
|
24
25
|
| { status: "planned"; report: string }
|
|
25
26
|
| { status: "completed" | "refused"; report: string }
|
|
26
27
|
| { status: "unfinished"; report: string; remainingScope: string; reason?: string };
|
|
27
|
-
|
|
28
|
+
/** Clean submission shape or seat-fallback tainted accepted receipt (ADR 0071). */
|
|
29
|
+
export type CoderOutput =
|
|
30
|
+
| CoderOutputClean
|
|
31
|
+
| WithEngineLaborFallback<CoderOutputClean>;
|
|
32
|
+
export type WorkerOutput =
|
|
33
|
+
| CoderOutput
|
|
34
|
+
| FixerOutput
|
|
35
|
+
| WithEngineLaborFallback<FixerOutput>;
|
|
28
36
|
|
|
29
37
|
export function validateAcceptedCoderDetails(output: unknown): CoderOutput {
|
|
30
38
|
return output as CoderOutput;
|
|
@@ -34,7 +34,11 @@ import {
|
|
|
34
34
|
COLLECTOR_WAIT_TOOL,
|
|
35
35
|
} from "../collector-ledger.ts";
|
|
36
36
|
import { ENGINE_DETOUR_TOOL_NAME } from "../engine-detour.ts";
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
readEngineLaborFallbackFieldFrom,
|
|
39
|
+
seatFallbackBaseStatus,
|
|
40
|
+
seatFallbackStatusHasLawfulEvidence,
|
|
41
|
+
} from "../engine-labor-fallback.ts";
|
|
38
42
|
import {
|
|
39
43
|
JUDGE_OUTPUT_TOOL_NAME,
|
|
40
44
|
type JudgeVerdict,
|
|
@@ -1059,7 +1063,7 @@ function auditNoReceiptDecisiveFact(candidate: object): Record<string, unknown>
|
|
|
1059
1063
|
|
|
1060
1064
|
function judgeDecisiveFacts(
|
|
1061
1065
|
verdict: object,
|
|
1062
|
-
judgeStatus:
|
|
1066
|
+
judgeStatus: string,
|
|
1063
1067
|
): Record<string, unknown> {
|
|
1064
1068
|
const facts: Record<string, unknown> = {
|
|
1065
1069
|
judgeStatus,
|
|
@@ -1067,7 +1071,8 @@ function judgeDecisiveFacts(
|
|
|
1067
1071
|
// #380: spread sole-built field; do not re-key here (S1).
|
|
1068
1072
|
...readEngineLaborFallbackFieldFrom(verdict),
|
|
1069
1073
|
};
|
|
1070
|
-
|
|
1074
|
+
const statusBase = seatFallbackBaseStatus(judgeStatus);
|
|
1075
|
+
if (statusBase === "continue") {
|
|
1071
1076
|
const fix = safelyRead(verdict, "fix");
|
|
1072
1077
|
if (fix.readable && isRecord(fix.value)) {
|
|
1073
1078
|
const summary = safelyRead(fix.value, "summary");
|
|
@@ -1093,7 +1098,7 @@ function judgeDecisiveFacts(
|
|
|
1093
1098
|
}
|
|
1094
1099
|
}
|
|
1095
1100
|
}
|
|
1096
|
-
if (
|
|
1101
|
+
if (statusBase === "escalate") {
|
|
1097
1102
|
const gate = safelyRead(verdict, "decisionGate");
|
|
1098
1103
|
if (gate.readable && isRecord(gate.value)) {
|
|
1099
1104
|
const question = safelyRead(gate.value, "question");
|
|
@@ -1118,10 +1123,14 @@ function coderDecisiveFacts(output: CoderOutput): Record<string, unknown> {
|
|
|
1118
1123
|
const status = safelyRead(candidate, "status");
|
|
1119
1124
|
const facts: Record<string, unknown> = {};
|
|
1120
1125
|
if (status.readable && typeof status.value === "string") facts.coderStatus = status.value;
|
|
1126
|
+
const statusBase =
|
|
1127
|
+
status.readable && typeof status.value === "string"
|
|
1128
|
+
? seatFallbackBaseStatus(status.value)
|
|
1129
|
+
: undefined;
|
|
1121
1130
|
const remainingScope = safelyRead(candidate, "remainingScope");
|
|
1122
|
-
if (
|
|
1131
|
+
if (statusBase === "unfinished" && remainingScope.readable && typeof remainingScope.value === "string") facts.remainingScope = remainingScope.value;
|
|
1123
1132
|
const reason = safelyRead(candidate, "reason");
|
|
1124
|
-
if (
|
|
1133
|
+
if (statusBase === "unfinished" && reason.readable && typeof reason.value === "string" && reason.value.trim().length > 0) {
|
|
1125
1134
|
facts.reason = reason.value;
|
|
1126
1135
|
}
|
|
1127
1136
|
const report = safelyRead(candidate, "report");
|
|
@@ -1134,14 +1143,18 @@ function fixerDecisiveFacts(output: FixerOutput): Record<string, unknown> {
|
|
|
1134
1143
|
const status = safelyRead(candidate, "status");
|
|
1135
1144
|
const facts: Record<string, unknown> = {};
|
|
1136
1145
|
if (status.readable && typeof status.value === "string") facts.fixerStatus = status.value;
|
|
1146
|
+
const statusBase =
|
|
1147
|
+
status.readable && typeof status.value === "string"
|
|
1148
|
+
? seatFallbackBaseStatus(status.value)
|
|
1149
|
+
: undefined;
|
|
1137
1150
|
const remainingScope = safelyRead(candidate, "remainingScope");
|
|
1138
|
-
if (
|
|
1151
|
+
if ((statusBase === "unfinished" || statusBase === "refused") && remainingScope.readable && typeof remainingScope.value === "string") facts.remainingScope = remainingScope.value;
|
|
1139
1152
|
const reason = safelyRead(candidate, "reason");
|
|
1140
|
-
if (
|
|
1153
|
+
if (statusBase === "unfinished" && reason.readable && typeof reason.value === "string" && reason.value.trim().length > 0) {
|
|
1141
1154
|
facts.reason = reason.value;
|
|
1142
1155
|
}
|
|
1143
1156
|
const blockerRead = safelyRead(candidate, "blocker");
|
|
1144
|
-
if (
|
|
1157
|
+
if (statusBase === "refused" && blockerRead.readable && isRecord(blockerRead.value)) {
|
|
1145
1158
|
const cause = safelyRead(blockerRead.value, "cause");
|
|
1146
1159
|
if (cause.readable && typeof cause.value === "string") facts.blockerCause = cause.value;
|
|
1147
1160
|
const prerequisiteId = safelyRead(blockerRead.value, "prerequisiteId");
|
|
@@ -1223,7 +1236,11 @@ function doctorDecisiveFacts(output: DoctorOutput): Record<string, unknown> {
|
|
|
1223
1236
|
const status = safelyRead(candidate, "status");
|
|
1224
1237
|
const facts: Record<string, unknown> = { ...auditNoReceiptDecisiveFact(candidate) };
|
|
1225
1238
|
if (status.readable && typeof status.value === "string") facts.doctorStatus = status.value;
|
|
1226
|
-
|
|
1239
|
+
const statusBase =
|
|
1240
|
+
status.readable && typeof status.value === "string"
|
|
1241
|
+
? seatFallbackBaseStatus(status.value)
|
|
1242
|
+
: undefined;
|
|
1243
|
+
if (statusBase === "refused") {
|
|
1227
1244
|
const reason = safelyRead(candidate, "reason");
|
|
1228
1245
|
if (reason.readable && reason.value !== undefined) facts.reason = reason.value;
|
|
1229
1246
|
const missing = safelyRead(candidate, "missingEvidence");
|
|
@@ -1277,7 +1294,11 @@ function reviewerDecisiveFacts(
|
|
|
1277
1294
|
facts.specDisposition = specDisposition.value;
|
|
1278
1295
|
}
|
|
1279
1296
|
const diagnostic = safelyRead(candidate, "diagnostic");
|
|
1280
|
-
|
|
1297
|
+
const statusBase =
|
|
1298
|
+
status.readable && typeof status.value === "string"
|
|
1299
|
+
? seatFallbackBaseStatus(status.value)
|
|
1300
|
+
: undefined;
|
|
1301
|
+
if (statusBase === "refused" && diagnostic.readable) {
|
|
1281
1302
|
facts.diagnosticPresent = typeof diagnostic.value === "string" && diagnostic.value.trim().length > 0;
|
|
1282
1303
|
}
|
|
1283
1304
|
return facts;
|
|
@@ -1980,9 +2001,12 @@ export function extractJudgeRoleOutcome(
|
|
|
1980
2001
|
// must not become a second verdict-shape gate (ADR 0040).
|
|
1981
2002
|
if (!isRecord(details)) continue;
|
|
1982
2003
|
const statusRead = safelyRead(details, "judgeStatus");
|
|
1983
|
-
if (!statusRead.readable) continue;
|
|
2004
|
+
if (!statusRead.readable || typeof statusRead.value !== "string") continue;
|
|
1984
2005
|
const judgeStatus = statusRead.value;
|
|
1985
|
-
|
|
2006
|
+
const statusBase = seatFallbackBaseStatus(judgeStatus);
|
|
2007
|
+
if (statusBase !== "converged" && statusBase !== "continue" && statusBase !== "escalate") continue;
|
|
2008
|
+
// ADR 0071: `-by-fallback` without latch-shaped evidence is not a lawful terminal.
|
|
2009
|
+
if (!seatFallbackStatusHasLawfulEvidence(judgeStatus, details)) continue;
|
|
1986
2010
|
return {
|
|
1987
2011
|
kind: "accepted",
|
|
1988
2012
|
role: "judge",
|
|
@@ -2985,13 +3009,19 @@ async function settleLawfulDoctorTerminalResult(
|
|
|
2985
3009
|
const extracted = extractDoctorRoleOutcome(entries);
|
|
2986
3010
|
if (extracted === undefined) return undefined;
|
|
2987
3011
|
// Bind completed receipt case identity to the admitted Issue evidence case.
|
|
3012
|
+
// Seat-fallback may taint status; base semantics still require case binding.
|
|
2988
3013
|
if (
|
|
2989
3014
|
extracted.output !== undefined &&
|
|
2990
|
-
extracted.output.status === "completed"
|
|
3015
|
+
seatFallbackBaseStatus(String(extracted.output.status)) === "completed"
|
|
2991
3016
|
) {
|
|
3017
|
+
const completedCase = (
|
|
3018
|
+
extracted.output as {
|
|
3019
|
+
case: { issueNumber: number; runsPath: string };
|
|
3020
|
+
}
|
|
3021
|
+
).case;
|
|
2992
3022
|
if (
|
|
2993
|
-
|
|
2994
|
-
|
|
3023
|
+
completedCase.issueNumber !== admitted.caseIdentity.issueNumber ||
|
|
3024
|
+
completedCase.runsPath !== admitted.caseIdentity.runsPath
|
|
2995
3025
|
) {
|
|
2996
3026
|
const error = new Error(
|
|
2997
3027
|
"Doctor receipt case identity does not match admitted case identity",
|
|
@@ -3331,7 +3361,11 @@ function mergerDecisiveFacts(output: MergerOutput): Record<string, unknown> {
|
|
|
3331
3361
|
const attemptId = safelyRead(candidate, "attemptId");
|
|
3332
3362
|
if (status.readable && typeof status.value === "string") facts.mergerStatus = status.value;
|
|
3333
3363
|
if (attemptId.readable && attemptId.value !== undefined) facts.attemptId = attemptId.value;
|
|
3334
|
-
const
|
|
3364
|
+
const statusBase =
|
|
3365
|
+
status.readable && typeof status.value === "string"
|
|
3366
|
+
? seatFallbackBaseStatus(status.value)
|
|
3367
|
+
: undefined;
|
|
3368
|
+
const decisiveKey = statusBase === "completed" ? "mergeCommitId" : "diagnosis";
|
|
3335
3369
|
const decisive = safelyRead(candidate, decisiveKey);
|
|
3336
3370
|
if (decisive.readable && decisive.value !== undefined) facts[decisiveKey] = decisive.value;
|
|
3337
3371
|
return facts;
|
package/src/role-runtime.ts
CHANGED
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
createEngineLaborFallbackLatch,
|
|
28
28
|
installActivationEngineLaborFallbackLatch,
|
|
29
29
|
restoreEngineLaborFallbackFromSessionEntries,
|
|
30
|
+
seatFallbackBaseStatus,
|
|
31
|
+
seatFallbackStatusHasLawfulEvidence,
|
|
30
32
|
} from "./engine-labor-fallback.ts";
|
|
31
33
|
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.ts";
|
|
32
34
|
import { createOAuthKeepalive, type OAuthKeepaliveOptions } from "./oauth-keepalive.ts";
|
|
@@ -564,7 +566,12 @@ export function publicNavigatorSettlement(role: string, phase: NavigatorPhase, e
|
|
|
564
566
|
const status = typeof details.status === "string"
|
|
565
567
|
? details.status
|
|
566
568
|
: typeof details.judgeStatus === "string" ? details.judgeStatus : undefined;
|
|
567
|
-
|
|
569
|
+
// ADR 0071: `-by-fallback` without latch-shaped evidence is not a lawful terminal.
|
|
570
|
+
if (status !== undefined && !seatFallbackStatusHasLawfulEvidence(status, event.details)) {
|
|
571
|
+
return undefined;
|
|
572
|
+
}
|
|
573
|
+
// Seat-fallback taint keeps escalate semantics (base) while preserving the polluted token.
|
|
574
|
+
if (status !== undefined && seatFallbackBaseStatus(status) === "escalate") {
|
|
568
575
|
return { kind: "human_decision", role, phase, status };
|
|
569
576
|
}
|
|
570
577
|
return { kind: "accepted", role, phase, ...(status === undefined ? {} : { status }) };
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - success rate den = success-eligible accepted legs (no-receipt out; planned out)
|
|
10
10
|
* - planned = plan-duty acceptance; never success numerator or denominator
|
|
11
11
|
*/
|
|
12
|
+
import { seatFallbackBaseStatus } from "../engine-labor-fallback.ts";
|
|
12
13
|
import type { TaishiReadableRunFacts, TaishiRunTerminalFace } from "../taishi-ledger.ts";
|
|
13
14
|
import { medianNumber } from "../taishi-median.ts";
|
|
14
15
|
import type { TaishiMetricFamilyModule } from "../taishi-metric-family.ts";
|
|
@@ -185,8 +186,10 @@ function mapTerminal(
|
|
|
185
186
|
};
|
|
186
187
|
}
|
|
187
188
|
|
|
189
|
+
// Seat-fallback taint is visible on the label; acceptance/success use base semantics.
|
|
190
|
+
const statusBase = seatFallbackBaseStatus(status);
|
|
188
191
|
const acceptedSet = ACCEPTED_STATUS[role];
|
|
189
|
-
if (acceptedSet === undefined || !acceptedSet.has(
|
|
192
|
+
if (acceptedSet === undefined || !acceptedSet.has(statusBase)) {
|
|
190
193
|
return {
|
|
191
194
|
terminalLabel: status,
|
|
192
195
|
accepted: false,
|
|
@@ -196,9 +199,9 @@ function mapTerminal(
|
|
|
196
199
|
};
|
|
197
200
|
}
|
|
198
201
|
|
|
199
|
-
const plannedDuty = WORKER_ROLES.has(role) &&
|
|
202
|
+
const plannedDuty = WORKER_ROLES.has(role) && statusBase === "planned";
|
|
200
203
|
const successSet = SUCCESS_STATUS[role] ?? new Set<string>();
|
|
201
|
-
const success = !plannedDuty && successSet.has(
|
|
204
|
+
const success = !plannedDuty && successSet.has(statusBase);
|
|
202
205
|
const successEligible = !plannedDuty;
|
|
203
206
|
|
|
204
207
|
return {
|