@akagilnc/pi-workflow-roles 0.1.2135 → 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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/compliance-transport.js +19 -24
- package/dist/doctor-contracts.js +10 -2
- package/dist/engine-detour-tool.js +5 -15
- package/dist/engine-detour.js +3 -11
- package/dist/engine-labor-fallback.js +129 -114
- package/dist/evidence-child-executor.js +3 -4
- package/dist/merger-contracts.js +9 -2
- package/dist/navigator-attendance.js +11 -4
- 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 +123 -125
- package/package.json +1 -1
- package/resources/engines/codex.md +3 -4
- package/resources/engines/grok-4.6.md +5 -6
- package/resources/engines/kimi.md +2 -2
- package/resources/engines/opus.md +2 -2
- package/scripts/build-package.mjs +2 -0
- package/src/collector-role.ts +5 -33
- package/src/compliance-transport.ts +19 -24
- package/src/doctor-contracts.ts +19 -3
- package/src/engine-detour-tool.ts +6 -21
- package/src/engine-detour.ts +3 -15
- package/src/engine-labor-fallback.ts +100 -2
- package/src/evidence-child-executor.ts +3 -4
- package/src/factory-board.ts +6 -2
- package/src/merger-contracts.ts +18 -3
- package/src/navigator-attendance.ts +18 -4
- 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 -4
- package/src/taishi-metric-families/acceptance-success-rework.ts +6 -3
- package/dist/package-owned-tool-idle.js +0 -223
- package/src/package-owned-tool-idle.ts +0 -257
|
@@ -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,8 +27,9 @@ import {
|
|
|
27
27
|
createEngineLaborFallbackLatch,
|
|
28
28
|
installActivationEngineLaborFallbackLatch,
|
|
29
29
|
restoreEngineLaborFallbackFromSessionEntries,
|
|
30
|
+
seatFallbackBaseStatus,
|
|
31
|
+
seatFallbackStatusHasLawfulEvidence,
|
|
30
32
|
} from "./engine-labor-fallback.ts";
|
|
31
|
-
import { installPackageOwnedToolRegistration } from "./package-owned-tool-idle.ts";
|
|
32
33
|
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.ts";
|
|
33
34
|
import { createOAuthKeepalive, type OAuthKeepaliveOptions } from "./oauth-keepalive.ts";
|
|
34
35
|
|
|
@@ -565,7 +566,12 @@ export function publicNavigatorSettlement(role: string, phase: NavigatorPhase, e
|
|
|
565
566
|
const status = typeof details.status === "string"
|
|
566
567
|
? details.status
|
|
567
568
|
: typeof details.judgeStatus === "string" ? details.judgeStatus : undefined;
|
|
568
|
-
|
|
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") {
|
|
569
575
|
return { kind: "human_decision", role, phase, status };
|
|
570
576
|
}
|
|
571
577
|
return { kind: "accepted", role, phase, ...(status === undefined ? {} : { status }) };
|
|
@@ -575,8 +581,6 @@ export function createRoleRuntimeExtension(
|
|
|
575
581
|
dependencies: RoleRuntimeDependencies,
|
|
576
582
|
): (pi: ExtensionAPI) => void {
|
|
577
583
|
return (pi) => {
|
|
578
|
-
// #102: one shared package-owned tool registration surface for all role runtimes.
|
|
579
|
-
installPackageOwnedToolRegistration(pi);
|
|
580
584
|
pi.registerFlag(ROLE_FLAG.name, ROLE_FLAG.definition);
|
|
581
585
|
// Reviewer transport flags: shared envelope owns registration (ADR 0018).
|
|
582
586
|
for (const flag of REVIEWER_TRANSPORT_FLAGS) {
|
|
@@ -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 {
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* #102 package-owned tool idle backstop.
|
|
3
|
-
*
|
|
4
|
-
* Fixed 183000ms silence clock on package-owned tool execute only.
|
|
5
|
-
* Real producing onUpdate resets; final resolve/reject clears; timeout throws so
|
|
6
|
-
* Pi settles the current call as an LLM-visible isError tool result. No retry,
|
|
7
|
-
* role failure, process termination, config, or Pi built-in coverage.
|
|
8
|
-
*
|
|
9
|
-
* #339: do not name-exempt whole terminating tools. Outer idle stays armed for
|
|
10
|
-
* pre/post-audit work. Only the real compliance-audit await suspends this single
|
|
11
|
-
* layer (ADR 0059 owns that interval); resume re-arms the same outer backstop.
|
|
12
|
-
*
|
|
13
|
-
* #380: on idle, abort a derived tool signal (not the caller signal) so detour
|
|
14
|
-
* spawn cleanup and softFail can run through the existing AbortSignal path.
|
|
15
|
-
* Cooperative soft-settle may win; non-listeners still hard-reject after a
|
|
16
|
-
* microtask drain (no second timer).
|
|
17
|
-
*/
|
|
18
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
19
|
-
import { DEFAULT_STREAM_IDLE_TIMEOUT_MS, createStreamIdleGuard, } from "./stream-idle-guard.js";
|
|
20
|
-
import { isProducingToolUpdate } from "./tool-execution-observation.js";
|
|
21
|
-
export const PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS = DEFAULT_STREAM_IDLE_TIMEOUT_MS;
|
|
22
|
-
export const PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_CODE = "AK_PACKAGE_OWNED_TOOL_IDLE_TIMEOUT";
|
|
23
|
-
const WRAPPED = Symbol.for("ak.packageOwnedToolIdleWrapped");
|
|
24
|
-
/**
|
|
25
|
-
* Active outer package-owned execute idle, if any. Nested tool executes install
|
|
26
|
-
* their own store; compliance audit only suspends the store visible at await time.
|
|
27
|
-
*/
|
|
28
|
-
const packageOwnedToolIdleScope = new AsyncLocalStorage();
|
|
29
|
-
export class PackageOwnedToolIdleTimeoutError extends Error {
|
|
30
|
-
code = PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_CODE;
|
|
31
|
-
idleTimeoutMs = PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS;
|
|
32
|
-
constructor() {
|
|
33
|
-
super(`package-owned tool idle timeout after ${PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS}ms`);
|
|
34
|
-
this.name = "PackageOwnedToolIdleTimeoutError";
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
export function isPackageOwnedToolIdleTimeoutError(value) {
|
|
38
|
-
return (value instanceof PackageOwnedToolIdleTimeoutError ||
|
|
39
|
-
(typeof value === "object" &&
|
|
40
|
-
value !== null &&
|
|
41
|
-
value.name === "PackageOwnedToolIdleTimeoutError" &&
|
|
42
|
-
value.code === PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_CODE));
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Package-tool activity includes content production and host-only details
|
|
46
|
-
* progress. Keep the observation-plane oracle separate: its stderr heartbeat
|
|
47
|
-
* contract remains content-driven. Pi's known execute-entry placeholder
|
|
48
|
-
* (`content: [], details: undefined`) is not activity.
|
|
49
|
-
*/
|
|
50
|
-
function isPackageOwnedToolActivityUpdate(partialResult) {
|
|
51
|
-
if (isProducingToolUpdate(partialResult))
|
|
52
|
-
return true;
|
|
53
|
-
if (typeof partialResult !== "object" || partialResult === null)
|
|
54
|
-
return false;
|
|
55
|
-
const details = partialResult.details;
|
|
56
|
-
if (details === undefined || details === null)
|
|
57
|
-
return false;
|
|
58
|
-
if (typeof details === "string")
|
|
59
|
-
return details.length > 0;
|
|
60
|
-
if (Array.isArray(details))
|
|
61
|
-
return details.length > 0;
|
|
62
|
-
if (typeof details === "object")
|
|
63
|
-
return Reflect.ownKeys(details).length > 0;
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* #339: suspend the active package-owned execute idle for one real compliance
|
|
68
|
-
* audit await. Single-layer only — production has one runComplianceAudit seam.
|
|
69
|
-
* No-op outside a wrapped package-owned execute. No second timeout or retry.
|
|
70
|
-
*/
|
|
71
|
-
export async function withPackageOwnedToolIdleSuspended(run) {
|
|
72
|
-
const scope = packageOwnedToolIdleScope.getStore();
|
|
73
|
-
if (scope === undefined)
|
|
74
|
-
return run();
|
|
75
|
-
scope.suspend();
|
|
76
|
-
try {
|
|
77
|
-
return await run();
|
|
78
|
-
}
|
|
79
|
-
finally {
|
|
80
|
-
scope.resume();
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Touch the active package-owned execute idle clock (reuse stream-idle-guard poke).
|
|
85
|
-
* No-op outside a wrapped package-owned execute or while that layer is suspended.
|
|
86
|
-
* Used by long-running tools (engine detour) that see real subprocess output bytes.
|
|
87
|
-
*/
|
|
88
|
-
export function pokePackageOwnedToolIdle() {
|
|
89
|
-
packageOwnedToolIdleScope.getStore()?.poke();
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Single shared execute wrapper for package-owned tool definitions.
|
|
93
|
-
* Idempotent: wrapping twice returns the same protected definition.
|
|
94
|
-
*/
|
|
95
|
-
export function wrapPackageOwnedToolDefinition(tool) {
|
|
96
|
-
// Mark the execute function, not the tool object — callers may spread tool fields
|
|
97
|
-
// onto a new definition with a different execute (e.g. auditor customTools).
|
|
98
|
-
if (tool.execute[WRAPPED] === true)
|
|
99
|
-
return tool;
|
|
100
|
-
const originalExecute = tool.execute.bind(tool);
|
|
101
|
-
const wrappedExecute = function packageOwnedToolIdleExecute(...args) {
|
|
102
|
-
const parentSignal = args[2];
|
|
103
|
-
const onUpdate = args[3];
|
|
104
|
-
return new Promise((resolve, reject) => {
|
|
105
|
-
let settled = false;
|
|
106
|
-
let suspended = false;
|
|
107
|
-
let idle = createStreamIdleGuard({
|
|
108
|
-
idleTimeoutMs: PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS,
|
|
109
|
-
});
|
|
110
|
-
// Derived tool signal: caller cancel forwards; idle aborts this only (not parent).
|
|
111
|
-
const toolController = new AbortController();
|
|
112
|
-
const onParentAbort = () => {
|
|
113
|
-
if (toolController.signal.aborted)
|
|
114
|
-
return;
|
|
115
|
-
toolController.abort(parentSignal?.reason);
|
|
116
|
-
};
|
|
117
|
-
if (parentSignal !== undefined) {
|
|
118
|
-
if (parentSignal.aborted) {
|
|
119
|
-
toolController.abort(parentSignal.reason);
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
parentSignal.addEventListener("abort", onParentAbort);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
const cleanup = () => {
|
|
126
|
-
idle.signal.removeEventListener("abort", onIdle);
|
|
127
|
-
idle.dispose();
|
|
128
|
-
parentSignal?.removeEventListener("abort", onParentAbort);
|
|
129
|
-
};
|
|
130
|
-
const settle = (deliver) => {
|
|
131
|
-
if (settled)
|
|
132
|
-
return;
|
|
133
|
-
settled = true;
|
|
134
|
-
cleanup();
|
|
135
|
-
deliver();
|
|
136
|
-
};
|
|
137
|
-
const onIdle = () => {
|
|
138
|
-
const idleError = new PackageOwnedToolIdleTimeoutError();
|
|
139
|
-
if (!toolController.signal.aborted) {
|
|
140
|
-
toolController.abort(idleError);
|
|
141
|
-
}
|
|
142
|
-
// Cooperative tools (detour) soft-settle via the aborted signal in the
|
|
143
|
-
// next microtasks. Non-listeners still hard-reject after a short drain
|
|
144
|
-
// of those microtasks — no second wall-clock timer.
|
|
145
|
-
void Promise.resolve()
|
|
146
|
-
.then(() => { })
|
|
147
|
-
.then(() => { })
|
|
148
|
-
.then(() => {
|
|
149
|
-
settle(() => reject(idleError));
|
|
150
|
-
});
|
|
151
|
-
};
|
|
152
|
-
idle.signal.addEventListener("abort", onIdle, { once: true });
|
|
153
|
-
const suspension = {
|
|
154
|
-
suspend() {
|
|
155
|
-
if (settled || suspended)
|
|
156
|
-
return;
|
|
157
|
-
suspended = true;
|
|
158
|
-
// ADR 0059 owns the audit interval — drop this layer until audit returns.
|
|
159
|
-
idle.signal.removeEventListener("abort", onIdle);
|
|
160
|
-
idle.dispose();
|
|
161
|
-
},
|
|
162
|
-
resume() {
|
|
163
|
-
if (settled || !suspended)
|
|
164
|
-
return;
|
|
165
|
-
suspended = false;
|
|
166
|
-
// Fresh single-layer silence window for post-audit work (e.g. cleanup).
|
|
167
|
-
idle = createStreamIdleGuard({
|
|
168
|
-
idleTimeoutMs: PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS,
|
|
169
|
-
});
|
|
170
|
-
idle.signal.addEventListener("abort", onIdle, { once: true });
|
|
171
|
-
},
|
|
172
|
-
poke() {
|
|
173
|
-
if (settled || suspended)
|
|
174
|
-
return;
|
|
175
|
-
idle.poke();
|
|
176
|
-
},
|
|
177
|
-
};
|
|
178
|
-
const guardedOnUpdate = onUpdate === undefined
|
|
179
|
-
? undefined
|
|
180
|
-
: (partialResult) => {
|
|
181
|
-
if (settled)
|
|
182
|
-
return;
|
|
183
|
-
if (isPackageOwnedToolActivityUpdate(partialResult))
|
|
184
|
-
idle.poke();
|
|
185
|
-
onUpdate(partialResult);
|
|
186
|
-
};
|
|
187
|
-
const callArgs = args.slice();
|
|
188
|
-
// Pass derived signal so idle/cancel can clean up subprocesses without
|
|
189
|
-
// aborting the caller/parent agent signal identity.
|
|
190
|
-
callArgs[2] = toolController.signal;
|
|
191
|
-
callArgs[3] = guardedOnUpdate;
|
|
192
|
-
void packageOwnedToolIdleScope.run(suspension, async () => {
|
|
193
|
-
try {
|
|
194
|
-
const result = await originalExecute(...callArgs);
|
|
195
|
-
settle(() => resolve(result));
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
settle(() => reject(error));
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
};
|
|
203
|
-
wrappedExecute[WRAPPED] = true;
|
|
204
|
-
return {
|
|
205
|
-
...tool,
|
|
206
|
-
execute: wrappedExecute,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Install the shared registration surface on an ExtensionAPI once.
|
|
211
|
-
* All subsequent pi.registerTool calls for package-owned tools are wrapped.
|
|
212
|
-
*/
|
|
213
|
-
export function installPackageOwnedToolRegistration(pi) {
|
|
214
|
-
const current = pi.registerTool;
|
|
215
|
-
if (current[WRAPPED] === true)
|
|
216
|
-
return;
|
|
217
|
-
const original = current.bind(pi);
|
|
218
|
-
const installed = ((tool) => {
|
|
219
|
-
original(wrapPackageOwnedToolDefinition(tool));
|
|
220
|
-
});
|
|
221
|
-
installed[WRAPPED] = true;
|
|
222
|
-
pi.registerTool = installed;
|
|
223
|
-
}
|