@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
package/src/collector-role.ts
CHANGED
|
@@ -61,10 +61,6 @@ const requestSchema = collectorRequestArgsSchema;
|
|
|
61
61
|
const waitSchema = collectorWaitArgsSchema;
|
|
62
62
|
const outputSchema = collectorOutputArgsSchema;
|
|
63
63
|
|
|
64
|
-
// Well below the fixed 183s package-tool silence clock while avoiding noisy UI
|
|
65
|
-
// churn during Collector's legal five-minute wait.
|
|
66
|
-
const COLLECTOR_WAIT_PROGRESS_INTERVAL_MS = 60_000;
|
|
67
|
-
|
|
68
64
|
type RequestParams = Static<typeof requestSchema>;
|
|
69
65
|
type WaitParams = Static<typeof waitSchema>;
|
|
70
66
|
type OutputParams = Static<typeof outputSchema>;
|
|
@@ -377,40 +373,18 @@ export function createCollectorRoleRuntime(
|
|
|
377
373
|
"Call ak_collector_wait with a positive durationMs; runtime caps each wait to five minutes and to remaining eligibility.",
|
|
378
374
|
],
|
|
379
375
|
parameters: waitSchema,
|
|
380
|
-
async execute(toolCallId, params: WaitParams, signal,
|
|
376
|
+
async execute(toolCallId, params: WaitParams, signal, _onUpdate, ctx) {
|
|
381
377
|
if (activation === undefined) {
|
|
382
378
|
throw new Error("Collector is not activated");
|
|
383
379
|
}
|
|
384
|
-
const currentActivation = activation;
|
|
385
|
-
let progressTimer: ReturnType<typeof setTimeout> | undefined;
|
|
386
380
|
try {
|
|
387
|
-
|
|
388
|
-
const
|
|
389
|
-
let lastElapsedMs = 0;
|
|
390
|
-
const scheduleProgress = (): void => {
|
|
391
|
-
if (onUpdate === undefined) return;
|
|
392
|
-
progressTimer = setTimeout(() => {
|
|
393
|
-
const elapsedMs = Math.max(
|
|
394
|
-
0,
|
|
395
|
-
Math.floor(currentActivation.clock.monoNow() - waitStartedMono),
|
|
396
|
-
);
|
|
397
|
-
if (elapsedMs > lastElapsedMs) {
|
|
398
|
-
lastElapsedMs = elapsedMs;
|
|
399
|
-
onUpdate({
|
|
400
|
-
content: [],
|
|
401
|
-
details: { kind: "collector_wait_progress", elapsedMs },
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
scheduleProgress();
|
|
405
|
-
}, COLLECTOR_WAIT_PROGRESS_INTERVAL_MS);
|
|
406
|
-
};
|
|
407
|
-
scheduleProgress();
|
|
408
|
-
const details = await currentActivation.ledger.wait(
|
|
381
|
+
activation.ledger.beginOperational(COLLECTOR_WAIT_TOOL, toolCallId);
|
|
382
|
+
const details = await activation.ledger.wait(
|
|
409
383
|
params,
|
|
410
|
-
|
|
384
|
+
activation.clock,
|
|
411
385
|
signal,
|
|
412
386
|
);
|
|
413
|
-
|
|
387
|
+
activation.ledger.completeOperational(toolCallId);
|
|
414
388
|
return {
|
|
415
389
|
content: [{
|
|
416
390
|
type: "text" as const,
|
|
@@ -420,8 +394,6 @@ export function createCollectorRoleRuntime(
|
|
|
420
394
|
};
|
|
421
395
|
} catch (error) {
|
|
422
396
|
hostActions.failInfrastructure(error, ctx);
|
|
423
|
-
} finally {
|
|
424
|
-
if (progressTimer !== undefined) clearTimeout(progressTimer);
|
|
425
397
|
}
|
|
426
398
|
},
|
|
427
399
|
});
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
} from "./evidence-child-executor.ts";
|
|
8
8
|
import { createAuditorDossierTool } from "./auditor-dossier-tool.ts";
|
|
9
9
|
import type { DossierObservation } from "./dossier-resolution.ts";
|
|
10
|
-
import { withPackageOwnedToolIdleSuspended } from "./package-owned-tool-idle.ts";
|
|
11
10
|
import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
12
11
|
|
|
13
12
|
export type ComplianceCompletion = AuditorCompletion;
|
|
@@ -117,28 +116,24 @@ export type RunComplianceAuditOptions = {
|
|
|
117
116
|
};
|
|
118
117
|
|
|
119
118
|
export async function runComplianceAudit(options: RunComplianceAuditOptions): Promise<ComplianceDecision> {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
retainResponse: (response) => retainComplianceResponse(options.context, response),
|
|
132
|
-
...(options.runCompletion === undefined ? {} : { runCompletion: options.runCompletion }),
|
|
133
|
-
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
134
|
-
});
|
|
135
|
-
if (receipt.noReceiptLifecycle !== undefined) {
|
|
136
|
-
return {
|
|
137
|
-
status: "no-receipt",
|
|
138
|
-
...receipt.noReceiptLifecycle,
|
|
139
|
-
...(receipt.response.usage === undefined ? {} : { usage: receipt.response.usage }),
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
return readComplianceCandidate(receipt.decision, receipt.response.usage);
|
|
119
|
+
const prompt = options.serializedInput ?? AUDITOR_DOSSIER_PROMPT;
|
|
120
|
+
const receipt = await executeAuditorChild({
|
|
121
|
+
tool: options.tool,
|
|
122
|
+
dossierTool: createAuditorDossierTool(options.runDirectory),
|
|
123
|
+
systemPrompt: options.systemPrompt,
|
|
124
|
+
prompt,
|
|
125
|
+
roleLabel: options.roleLabel,
|
|
126
|
+
context: options.context,
|
|
127
|
+
retainResponse: (response) => retainComplianceResponse(options.context, response),
|
|
128
|
+
...(options.runCompletion === undefined ? {} : { runCompletion: options.runCompletion }),
|
|
129
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
143
130
|
});
|
|
131
|
+
if (receipt.noReceiptLifecycle !== undefined) {
|
|
132
|
+
return {
|
|
133
|
+
status: "no-receipt",
|
|
134
|
+
...receipt.noReceiptLifecycle,
|
|
135
|
+
...(receipt.response.usage === undefined ? {} : { usage: receipt.response.usage }),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return readComplianceCandidate(receipt.decision, receipt.response.usage);
|
|
144
139
|
}
|
package/src/doctor-contracts.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { canonicalJson } from "./canonical-json.ts";
|
|
3
|
+
import {
|
|
4
|
+
seatFallbackBaseStatus,
|
|
5
|
+
seatFallbackStatusHasLawfulEvidence,
|
|
6
|
+
type WithEngineLaborFallback,
|
|
7
|
+
} from "./engine-labor-fallback.ts";
|
|
3
8
|
import { openToolObjectFromUnion } from "./open-tool-schema.ts";
|
|
4
9
|
|
|
5
10
|
export const DOCTOR_EVIDENCE_TOOL_NAME = "ak_doctor_evidence";
|
|
@@ -37,9 +42,13 @@ export type DoctorFinding =
|
|
|
37
42
|
export type DoctorSubmission =
|
|
38
43
|
| { status: "completed"; case: DoctorCaseIdentity; findings: DoctorFinding[] }
|
|
39
44
|
| { status: "refused"; reason: string; missingEvidence: Array<{ need: string; targetKeys: string[] }> };
|
|
40
|
-
|
|
45
|
+
type DoctorOutputClean =
|
|
41
46
|
| { status: "completed"; case: DoctorCaseIdentity; findings: DoctorFinding[]; cost: DoctorCaseCost }
|
|
42
47
|
| Extract<DoctorSubmission, { status: "refused" }>;
|
|
48
|
+
/** Clean recorded shape or seat-fallback tainted accepted receipt (ADR 0071). */
|
|
49
|
+
export type DoctorOutput =
|
|
50
|
+
| DoctorOutputClean
|
|
51
|
+
| WithEngineLaborFallback<DoctorOutputClean>;
|
|
43
52
|
export type DoctorEvidenceEntry = { id: string; kind: "session" | "stderr"; byteLength: number; contentLength: number; sha256: string; content: string };
|
|
44
53
|
export type DoctorCase = { version: 1; identity: DoctorCaseIdentity; evidence: DoctorEvidenceEntry[]; cost: DoctorCaseCost };
|
|
45
54
|
|
|
@@ -96,12 +105,19 @@ function isRecord(value: unknown): value is Record<string, unknown> { return typ
|
|
|
96
105
|
function read(value: unknown, key: string): unknown { if (!isRecord(value)) return undefined; try { return value[key]; } catch { return undefined; } }
|
|
97
106
|
export function validateDoctorSubmissionShape(value: unknown): DoctorSubmission {
|
|
98
107
|
const status = read(value, "status");
|
|
99
|
-
|
|
108
|
+
const base = typeof status === "string" ? seatFallbackBaseStatus(status) : status;
|
|
109
|
+
if (base !== "completed" && base !== "refused") throw new DoctorSubmissionContractError("Doctor submission has no recognized execution status");
|
|
110
|
+
// ADR 0071: tainted status requires latch-shaped engineLaborFallback evidence.
|
|
111
|
+
if (typeof status === "string" && !seatFallbackStatusHasLawfulEvidence(status, value)) {
|
|
112
|
+
throw new DoctorSubmissionContractError("Doctor submission has no recognized execution status");
|
|
113
|
+
}
|
|
100
114
|
return value as DoctorSubmission;
|
|
101
115
|
}
|
|
102
116
|
export function validateRecordedDoctorOutput(value: unknown): DoctorOutput {
|
|
103
117
|
const output = validateDoctorSubmissionShape(value);
|
|
104
|
-
|
|
118
|
+
const status = read(output, "status");
|
|
119
|
+
const base = typeof status === "string" ? seatFallbackBaseStatus(status) : status;
|
|
120
|
+
if (base === "completed" && read(output, "cost") === undefined) throw new Error("Completed Doctor receipt has no runtime-owned cost testimony");
|
|
105
121
|
return output as DoctorOutput;
|
|
106
122
|
}
|
|
107
123
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Evidence-child legs install the same definition via customTools (no spawn in role modules).
|
|
5
5
|
* #380: engine process failure soft-returns so the seat rejoins the main road; declaration
|
|
6
6
|
* is recorded once via engine-labor-fallback (no fail-closed reject-leg).
|
|
7
|
-
* Caller AbortSignal cancel propagates without fallback
|
|
7
|
+
* Caller AbortSignal cancel propagates without fallback.
|
|
8
8
|
*/
|
|
9
9
|
import type {
|
|
10
10
|
AgentToolResult,
|
|
@@ -27,11 +27,6 @@ import {
|
|
|
27
27
|
recordEngineLaborFallback,
|
|
28
28
|
type EngineLaborFallbackField,
|
|
29
29
|
} from "./engine-labor-fallback.ts";
|
|
30
|
-
import {
|
|
31
|
-
isPackageOwnedToolIdleTimeoutError,
|
|
32
|
-
pokePackageOwnedToolIdle,
|
|
33
|
-
wrapPackageOwnedToolDefinition,
|
|
34
|
-
} from "./package-owned-tool-idle.ts";
|
|
35
30
|
|
|
36
31
|
const engineDetourArgsSchema = Type.Object(
|
|
37
32
|
{
|
|
@@ -81,18 +76,11 @@ function seatFallbackToolResult(
|
|
|
81
76
|
};
|
|
82
77
|
}
|
|
83
78
|
|
|
84
|
-
/** Caller/upper-layer cancel must propagate;
|
|
79
|
+
/** Caller/upper-layer cancel must propagate; process failure is seat-fallback, not cancel. */
|
|
85
80
|
function isCallerCancellation(
|
|
86
81
|
error: unknown,
|
|
87
82
|
signal: AbortSignal | undefined,
|
|
88
83
|
): boolean {
|
|
89
|
-
if (isPackageOwnedToolIdleTimeoutError(error)) return false;
|
|
90
|
-
if (
|
|
91
|
-
signal !== undefined &&
|
|
92
|
-
isPackageOwnedToolIdleTimeoutError(signal.reason)
|
|
93
|
-
) {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
84
|
if (signal?.aborted === true) return true;
|
|
97
85
|
if (
|
|
98
86
|
typeof error === "object" &&
|
|
@@ -108,7 +96,7 @@ function isCallerCancellation(
|
|
|
108
96
|
* Build one once-latch detour tool definition for a configured engine name.
|
|
109
97
|
* `latch` is shared so parent registration can reset between activations.
|
|
110
98
|
* `fail` owns host abort (parent) vs throw (evidence child) for tool misuse only.
|
|
111
|
-
* Engine process failure (nonzero/empty/spawn
|
|
99
|
+
* Engine process failure (nonzero/empty/spawn) soft-returns seat fallback (#380).
|
|
112
100
|
* Caller AbortSignal cancel propagates without writing fallback.
|
|
113
101
|
*/
|
|
114
102
|
export function createEngineDetourToolDefinition(input: {
|
|
@@ -118,7 +106,7 @@ export function createEngineDetourToolDefinition(input: {
|
|
|
118
106
|
}): ToolDefinition {
|
|
119
107
|
const latch = input.latch ?? { used: false };
|
|
120
108
|
const engineName = input.engineName;
|
|
121
|
-
return
|
|
109
|
+
return {
|
|
122
110
|
name: ENGINE_DETOUR_TOOL_NAME,
|
|
123
111
|
label: "Engine Detour",
|
|
124
112
|
description:
|
|
@@ -170,16 +158,13 @@ export function createEngineDetourToolDefinition(input: {
|
|
|
170
158
|
|
|
171
159
|
let result: Awaited<ReturnType<typeof runEngineDetourOnce>>;
|
|
172
160
|
try {
|
|
173
|
-
// Byte activity on stdout/stderr touches the outer package-owned idle clock
|
|
174
|
-
// (183s silence law unchanged). True hangs still die; slow streaming engines live.
|
|
175
161
|
result = await runEngineDetourOnce({
|
|
176
162
|
argv,
|
|
177
163
|
cwd: ctx.cwd,
|
|
178
164
|
...(signal === undefined ? {} : { signal }),
|
|
179
|
-
onOutputActivity: pokePackageOwnedToolIdle,
|
|
180
165
|
});
|
|
181
166
|
} catch (error) {
|
|
182
|
-
// Caller cancel: propagate.
|
|
167
|
+
// Caller cancel: propagate. Spawn/engine failure: seat fallback.
|
|
183
168
|
if (isCallerCancellation(error, signal)) {
|
|
184
169
|
throw error;
|
|
185
170
|
}
|
|
@@ -200,7 +185,7 @@ export function createEngineDetourToolDefinition(input: {
|
|
|
200
185
|
},
|
|
201
186
|
};
|
|
202
187
|
},
|
|
203
|
-
}
|
|
188
|
+
} as ToolDefinition;
|
|
204
189
|
}
|
|
205
190
|
|
|
206
191
|
/**
|
package/src/engine-detour.ts
CHANGED
|
@@ -44,11 +44,6 @@ export type EngineDetourRunInput = Readonly<{
|
|
|
44
44
|
cwd: string;
|
|
45
45
|
env?: NodeJS.ProcessEnv;
|
|
46
46
|
signal?: AbortSignal;
|
|
47
|
-
/**
|
|
48
|
-
* Called when the child emits at least one byte on stdout or stderr.
|
|
49
|
-
* Host wires this to the package-owned idle clock (activity = touch); no timer here.
|
|
50
|
-
*/
|
|
51
|
-
onOutputActivity?: () => void;
|
|
52
47
|
}>;
|
|
53
48
|
|
|
54
49
|
function abortReasonError(signal: AbortSignal): Error {
|
|
@@ -78,8 +73,8 @@ export async function runEngineDetourOnce(
|
|
|
78
73
|
return await new Promise<EngineDetourResult>((resolve, reject) => {
|
|
79
74
|
let settled = false;
|
|
80
75
|
const signal = input.signal;
|
|
81
|
-
// Own abort→kill explicitly so rejection preserves signal.reason (caller cancel
|
|
82
|
-
//
|
|
76
|
+
// Own abort→kill explicitly so rejection preserves signal.reason (caller cancel).
|
|
77
|
+
// Do not pass `signal` to spawn (Node replaces reason).
|
|
83
78
|
const child = spawn(command, args, {
|
|
84
79
|
cwd: input.cwd,
|
|
85
80
|
env: input.env ?? process.env,
|
|
@@ -87,17 +82,11 @@ export async function runEngineDetourOnce(
|
|
|
87
82
|
});
|
|
88
83
|
let stdout = "";
|
|
89
84
|
let stderr = "";
|
|
90
|
-
const noteActivity = (chunk: string): void => {
|
|
91
|
-
if (chunk.length === 0) return;
|
|
92
|
-
input.onOutputActivity?.();
|
|
93
|
-
};
|
|
94
85
|
child.stdout.setEncoding("utf8").on("data", (chunk: string) => {
|
|
95
86
|
stdout += chunk;
|
|
96
|
-
noteActivity(chunk);
|
|
97
87
|
});
|
|
98
88
|
child.stderr.setEncoding("utf8").on("data", (chunk: string) => {
|
|
99
89
|
stderr += chunk;
|
|
100
|
-
noteActivity(chunk);
|
|
101
90
|
});
|
|
102
91
|
const fail = (error: unknown): void => {
|
|
103
92
|
if (settled) return;
|
|
@@ -116,8 +105,7 @@ export async function runEngineDetourOnce(
|
|
|
116
105
|
resolve(result);
|
|
117
106
|
};
|
|
118
107
|
const onAbort = (): void => {
|
|
119
|
-
// Fail synchronously so
|
|
120
|
-
// before the outer package-owned idle hard-reject drain.
|
|
108
|
+
// Fail synchronously so caller-cancel soft-settle preserves signal.reason.
|
|
121
109
|
fail(signal !== undefined ? abortReasonError(signal) : new Error("aborted"));
|
|
122
110
|
try {
|
|
123
111
|
child.kill("SIGTERM");
|
|
@@ -32,6 +32,87 @@ export type EngineLaborFallbackField = ReturnType<
|
|
|
32
32
|
typeof buildEngineLaborFallbackField
|
|
33
33
|
>;
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Seat-fallback status taint suffix.
|
|
37
|
+
* When engineLaborFallback is declared, typed status discriminators
|
|
38
|
+
* (judgeStatus / status) must not remain clean values like converged/completed.
|
|
39
|
+
* Mechanical form: `${base}-by-fallback` — readable on the status line alone.
|
|
40
|
+
*/
|
|
41
|
+
export const SEAT_FALLBACK_STATUS_SUFFIX = "-by-fallback" as const;
|
|
42
|
+
|
|
43
|
+
export function isSeatFallbackTaintedStatus(status: string): boolean {
|
|
44
|
+
return status.endsWith(SEAT_FALLBACK_STATUS_SUFFIX);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Strip seat-fallback taint for semantic routing / acceptance matching. */
|
|
48
|
+
export function seatFallbackBaseStatus(status: string): string {
|
|
49
|
+
return isSeatFallbackTaintedStatus(status)
|
|
50
|
+
? status.slice(0, -SEAT_FALLBACK_STATUS_SUFFIX.length)
|
|
51
|
+
: status;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* ADR 0071: `-by-fallback` is lawful only with a valid `engineLaborFallback`
|
|
56
|
+
* declaration (latch-shaped triple). Clean statuses do not need the field.
|
|
57
|
+
*/
|
|
58
|
+
export function seatFallbackStatusHasLawfulEvidence(
|
|
59
|
+
status: string,
|
|
60
|
+
source: unknown,
|
|
61
|
+
): boolean {
|
|
62
|
+
if (!isSeatFallbackTaintedStatus(status)) return true;
|
|
63
|
+
return readEngineLaborFallbackFieldFrom(source) !== undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Idempotent taint: clean → `${clean}-by-fallback`; already tainted stays. */
|
|
67
|
+
export function taintStatusForSeatFallback(status: string): string {
|
|
68
|
+
if (status.length === 0 || isSeatFallbackTaintedStatus(status)) return status;
|
|
69
|
+
return `${status}${SEAT_FALLBACK_STATUS_SUFFIX}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Clean status literal or its seat-fallback tainted form. */
|
|
73
|
+
export type SeatFallbackTaintedStatus<S extends string = string> =
|
|
74
|
+
| S
|
|
75
|
+
| `${S}${typeof SEAT_FALLBACK_STATUS_SUFFIX}`;
|
|
76
|
+
|
|
77
|
+
type TaintDiscriminatorFields<T> = {
|
|
78
|
+
[K in keyof T]: K extends "status" | "judgeStatus"
|
|
79
|
+
? T[K] extends string
|
|
80
|
+
? SeatFallbackTaintedStatus<Extract<T[K], string>>
|
|
81
|
+
: T[K]
|
|
82
|
+
: T[K];
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** Receipt shape after sole-built field attach + status taint (runtime truth). */
|
|
86
|
+
export type WithEngineLaborFallback<T extends object> = T extends unknown
|
|
87
|
+
? TaintDiscriminatorFields<T> & EngineLaborFallbackField
|
|
88
|
+
: never;
|
|
89
|
+
|
|
90
|
+
const STATUS_DISCRIMINATOR_KEYS = ["judgeStatus", "status"] as const;
|
|
91
|
+
|
|
92
|
+
/** Rewrite known status discriminators on a receipt when seat fallback is declared. */
|
|
93
|
+
function taintReceiptStatusDiscriminators<T extends object>(
|
|
94
|
+
receipt: T,
|
|
95
|
+
): TaintDiscriminatorFields<T> {
|
|
96
|
+
let next: Record<string, unknown> | undefined;
|
|
97
|
+
for (const key of STATUS_DISCRIMINATOR_KEYS) {
|
|
98
|
+
if (!Object.prototype.hasOwnProperty.call(receipt, key)) continue;
|
|
99
|
+
let value: unknown;
|
|
100
|
+
try {
|
|
101
|
+
value = (receipt as Record<string, unknown>)[key];
|
|
102
|
+
} catch {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (typeof value !== "string" || value.length === 0) continue;
|
|
106
|
+
const tainted = taintStatusForSeatFallback(value);
|
|
107
|
+
if (tainted === value) continue;
|
|
108
|
+
if (next === undefined) {
|
|
109
|
+
next = { ...(receipt as object as Record<string, unknown>) };
|
|
110
|
+
}
|
|
111
|
+
next[key] = tainted;
|
|
112
|
+
}
|
|
113
|
+
return (next === undefined ? receipt : next) as TaintDiscriminatorFields<T>;
|
|
114
|
+
}
|
|
115
|
+
|
|
35
116
|
export type EngineLaborFallbackLatch = {
|
|
36
117
|
field: EngineLaborFallbackField | undefined;
|
|
37
118
|
};
|
|
@@ -62,14 +143,31 @@ export function readEngineLaborFallbackField(
|
|
|
62
143
|
/**
|
|
63
144
|
* Merge sole-built field into typed receipt details.
|
|
64
145
|
* Spread only — must not construct the field key again (S1).
|
|
146
|
+
* When the field is present, also taint typed status discriminators so a
|
|
147
|
+
* status-line-only reader cannot mistake seat labor for clean engine labor.
|
|
65
148
|
* Without a mechanical latch, strip any model-injected reserved key (no forged declaration).
|
|
149
|
+
* Return type widens when field is attached: discriminators may carry `-by-fallback`
|
|
150
|
+
* and the sole-built field is present (callers must not assume clean-only unions).
|
|
66
151
|
*/
|
|
152
|
+
export function withEngineLaborFallbackField<T extends object>(
|
|
153
|
+
receipt: T,
|
|
154
|
+
field: EngineLaborFallbackField,
|
|
155
|
+
): WithEngineLaborFallback<T>;
|
|
156
|
+
export function withEngineLaborFallbackField<T extends object>(
|
|
157
|
+
receipt: T,
|
|
158
|
+
field: undefined,
|
|
159
|
+
): T;
|
|
160
|
+
export function withEngineLaborFallbackField<T extends object>(
|
|
161
|
+
receipt: T,
|
|
162
|
+
field: EngineLaborFallbackField | undefined,
|
|
163
|
+
): T | WithEngineLaborFallback<T>;
|
|
67
164
|
export function withEngineLaborFallbackField<T extends object>(
|
|
68
165
|
receipt: T,
|
|
69
166
|
field: EngineLaborFallbackField | undefined,
|
|
70
|
-
): T {
|
|
167
|
+
): T | WithEngineLaborFallback<T> {
|
|
71
168
|
if (field !== undefined) {
|
|
72
|
-
|
|
169
|
+
const taintedReceipt = taintReceiptStatusDiscriminators(receipt);
|
|
170
|
+
return { ...taintedReceipt, ...field } as WithEngineLaborFallback<T>;
|
|
73
171
|
}
|
|
74
172
|
if (
|
|
75
173
|
!Object.prototype.hasOwnProperty.call(receipt, "engineLaborFallback")
|
|
@@ -36,7 +36,6 @@ import {
|
|
|
36
36
|
engineSessionMaterialFromOptions,
|
|
37
37
|
type EngineSessionMaterial,
|
|
38
38
|
} from "./package-resources/engine-material.ts";
|
|
39
|
-
import { wrapPackageOwnedToolDefinition } from "./package-owned-tool-idle.ts";
|
|
40
39
|
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT, type NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
41
40
|
import { REVIEWER_VERIFICATION_BOUNDARY } from "./reviewer-construction.ts";
|
|
42
41
|
import type { ReviewerPromptText } from "./reviewer-prompt-identity.ts";
|
|
@@ -750,7 +749,7 @@ export async function executeAuditorChild(
|
|
|
750
749
|
let decisionToolFailure: unknown;
|
|
751
750
|
const decisionToolFailures = new Map<string, unknown>();
|
|
752
751
|
const delivery = createReceiptDeliveryPolicy();
|
|
753
|
-
const tool =
|
|
752
|
+
const tool = {
|
|
754
753
|
...options.tool,
|
|
755
754
|
label: options.roleLabel,
|
|
756
755
|
async execute(...args: any[]) {
|
|
@@ -775,7 +774,7 @@ export async function executeAuditorChild(
|
|
|
775
774
|
throw error;
|
|
776
775
|
}
|
|
777
776
|
},
|
|
778
|
-
}
|
|
777
|
+
};
|
|
779
778
|
|
|
780
779
|
const parentSessionManager = options.context.sessionManager;
|
|
781
780
|
const parentHeader = parentSessionManager?.getHeader?.();
|
|
@@ -796,7 +795,7 @@ export async function executeAuditorChild(
|
|
|
796
795
|
thinkingLevel: options.context.thinkingLevel ?? "off",
|
|
797
796
|
modelRuntime: inherited.runtime,
|
|
798
797
|
systemPrompt: options.systemPrompt,
|
|
799
|
-
customTools: [
|
|
798
|
+
customTools: [{ ...options.dossierTool, label: options.roleLabel }, tool],
|
|
800
799
|
sessionManager: auditorSessionManager,
|
|
801
800
|
});
|
|
802
801
|
|
package/src/factory-board.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { randomUUID } from "node:crypto";
|
|
|
20
20
|
import { lstat, mkdir, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
21
21
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
22
22
|
|
|
23
|
+
import { seatFallbackBaseStatus } from "./engine-labor-fallback.ts";
|
|
23
24
|
import {
|
|
24
25
|
formatDurationZh,
|
|
25
26
|
formatLocalDateTime,
|
|
@@ -176,7 +177,8 @@ export function decideTicketCurrentState(input: {
|
|
|
176
177
|
const latest = sortRunsByStart(input.runs).at(-1)!;
|
|
177
178
|
if (!latest.hasResult) return unacceptedBand(latest.mtimeMs, input.now.getTime());
|
|
178
179
|
// Escalate is an awaiting overlay: same placement and sort band, distinct state.
|
|
179
|
-
|
|
180
|
+
// Seat-fallback taint (`escalate-by-fallback`) keeps escalate base semantics.
|
|
181
|
+
if (seatFallbackBaseStatus(latest.resultStatus) === "escalate") return "escalate-awaiting";
|
|
180
182
|
return "accepted-awaiting";
|
|
181
183
|
}
|
|
182
184
|
|
|
@@ -843,7 +845,9 @@ function buildBreadcrumbSteps(runs: readonly TicketTrajectoryRun[]): BreadcrumbS
|
|
|
843
845
|
const isReturn = seen.has(group.station);
|
|
844
846
|
seen.add(group.station);
|
|
845
847
|
const isRejected = group.runs.some(
|
|
846
|
-
(run) =>
|
|
848
|
+
(run) =>
|
|
849
|
+
run.hasResult &&
|
|
850
|
+
REJECTED_RESULT_STATUSES.has(seatFallbackBaseStatus(run.resultStatus)),
|
|
847
851
|
);
|
|
848
852
|
steps.push({
|
|
849
853
|
station: group.station,
|
package/src/merger-contracts.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { Type, type Static } from "typebox";
|
|
2
2
|
import { isFullGitObjectId } from "./git-object-id.ts";
|
|
3
3
|
import { exactUtf8 } from "./exact-utf8.ts";
|
|
4
|
+
import {
|
|
5
|
+
seatFallbackBaseStatus,
|
|
6
|
+
seatFallbackStatusHasLawfulEvidence,
|
|
7
|
+
type WithEngineLaborFallback,
|
|
8
|
+
} from "./engine-labor-fallback.ts";
|
|
4
9
|
import { sha256Hex } from "./sha256.ts";
|
|
5
10
|
import { openToolObjectFromUnion } from "./open-tool-schema.ts";
|
|
6
11
|
|
|
@@ -24,9 +29,13 @@ export const mergerOutputSchema = openToolObjectFromUnion(mergerOutputVariants);
|
|
|
24
29
|
export type DeepReadonly<T> = T extends (...args: never[]) => unknown ? T : T extends readonly (infer U)[] ? readonly DeepReadonly<U>[] : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T;
|
|
25
30
|
export type MergerMaterial = DeepReadonly<Static<typeof materialSchema>>;
|
|
26
31
|
export type MergerInput = DeepReadonly<Static<typeof mergerInputSchema>>;
|
|
27
|
-
|
|
32
|
+
type MergerOutputClean =
|
|
28
33
|
| { status: "completed"; attemptId: string; report: string; mergeCommitId: string }
|
|
29
34
|
| { status: "escalate"; attemptId: string; diagnosis: string; report: string };
|
|
35
|
+
/** Clean submission shape or seat-fallback tainted accepted receipt (ADR 0071). */
|
|
36
|
+
export type MergerOutput =
|
|
37
|
+
| MergerOutputClean
|
|
38
|
+
| WithEngineLaborFallback<MergerOutputClean>;
|
|
30
39
|
export const MERGER_OUTPUT_TOOL_NAME = "ak_merger_output";
|
|
31
40
|
export const MERGER_ACCEPTED_TEXT = "Merger output accepted";
|
|
32
41
|
|
|
@@ -65,7 +74,13 @@ export function validateMergerInput(value: unknown): MergerInput {
|
|
|
65
74
|
|
|
66
75
|
export function validateMergerOutput(value: unknown, expectedAttemptId?: string): MergerOutput {
|
|
67
76
|
if (!record(value) || (expectedAttemptId !== undefined && value.attemptId !== expectedAttemptId)) throw new Error("Merger output attempt mismatch");
|
|
68
|
-
|
|
69
|
-
|
|
77
|
+
const status = typeof value.status === "string" ? value.status : undefined;
|
|
78
|
+
const statusBase = status !== undefined ? seatFallbackBaseStatus(status) : undefined;
|
|
79
|
+
// ADR 0071: tainted status requires latch-shaped engineLaborFallback evidence.
|
|
80
|
+
if (status !== undefined && !seatFallbackStatusHasLawfulEvidence(status, value)) {
|
|
81
|
+
throw new Error("Merger output has no recognized execution discriminator");
|
|
82
|
+
}
|
|
83
|
+
if (statusBase === "completed" && isFullGitObjectId(value.mergeCommitId)) return structuredClone(value) as MergerOutput;
|
|
84
|
+
if (statusBase === "escalate") return structuredClone(value) as MergerOutput;
|
|
70
85
|
throw new Error("Merger output has no recognized execution discriminator");
|
|
71
86
|
}
|
|
@@ -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,
|
|
@@ -16,7 +17,6 @@ import { PACKAGED_ROLE_REGISTRY, type PackagedRole, packagedRoleMetadata } from
|
|
|
16
17
|
import { openInProcessAgentSession } from "./in-process-session.ts";
|
|
17
18
|
import { renderPublicAkRoleCommand } from "./public-command-renderer.ts";
|
|
18
19
|
import { issueRoot, subjectPath } from "./work-subject-identity.ts";
|
|
19
|
-
import { wrapPackageOwnedToolDefinition } from "./package-owned-tool-idle.ts";
|
|
20
20
|
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.ts";
|
|
21
21
|
import { recordTypedProviderHttpStatus } from "./typed-provider-http.ts";
|
|
22
22
|
import {
|
|
@@ -437,7 +437,7 @@ export function parseNavigatorModelSetting(value: string): { provider: string; m
|
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
export function createNavigatorPrepareTool(onOutput: (value: PrepareOutput) => void): ToolDefinition {
|
|
440
|
-
return
|
|
440
|
+
return {
|
|
441
441
|
name: NAVIGATOR_PREPARE_TOOL_NAME,
|
|
442
442
|
label: "Navigator preparation",
|
|
443
443
|
description: "Submit Navigator direction advice. Provide candidates with next.role (phase when meaningful). route/matches/reason/command are optional context, not acceptance gates.",
|
|
@@ -448,7 +448,7 @@ export function createNavigatorPrepareTool(onOutput: (value: PrepareOutput) => v
|
|
|
448
448
|
onOutput(value as PrepareOutput);
|
|
449
449
|
return { content: [{ type: "text" as const, text: "Navigator preparation accepted" }], details: value, terminate: true as const };
|
|
450
450
|
},
|
|
451
|
-
}
|
|
451
|
+
};
|
|
452
452
|
}
|
|
453
453
|
|
|
454
454
|
/**
|
|
@@ -462,6 +462,18 @@ export type NavigatorCandidateSelection = {
|
|
|
462
462
|
readonly matchedToSettlement: boolean;
|
|
463
463
|
};
|
|
464
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
|
+
|
|
465
477
|
export function selectNavigatorCandidate(
|
|
466
478
|
candidates: readonly NavigatorCandidate[],
|
|
467
479
|
settlement: NavigatorSettlement,
|
|
@@ -478,7 +490,9 @@ export function selectNavigatorCandidate(
|
|
|
478
490
|
if (rolePhaseMatched.length > 0) {
|
|
479
491
|
if (settlement.status !== undefined) {
|
|
480
492
|
const statusSpecific = rolePhaseMatched.find(
|
|
481
|
-
(candidate) =>
|
|
493
|
+
(candidate) =>
|
|
494
|
+
candidate.matches?.statuses !== undefined &&
|
|
495
|
+
statusListMatchesSettlement(candidate.matches.statuses, settlement.status!),
|
|
482
496
|
);
|
|
483
497
|
if (statusSpecific !== undefined) {
|
|
484
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
|
}
|