@akagilnc/pi-workflow-roles 0.1.2139 → 0.1.2148

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.
@@ -136,6 +136,54 @@ var init_collector_output = __esm({
136
136
  }
137
137
  });
138
138
 
139
+ // src/engine-labor-fallback.ts
140
+ function buildEngineLaborFallbackField(input) {
141
+ return Object.freeze({
142
+ engineLaborFallback: Object.freeze({
143
+ engine: input.engine,
144
+ failure: input.failure,
145
+ laborBy: "seat"
146
+ })
147
+ });
148
+ }
149
+ function isSeatFallbackTaintedStatus(status) {
150
+ return status.endsWith(SEAT_FALLBACK_STATUS_SUFFIX);
151
+ }
152
+ function seatFallbackBaseStatus(status) {
153
+ return isSeatFallbackTaintedStatus(status) ? status.slice(0, -SEAT_FALLBACK_STATUS_SUFFIX.length) : status;
154
+ }
155
+ function seatFallbackStatusHasLawfulEvidence(status, source) {
156
+ if (!isSeatFallbackTaintedStatus(status)) return true;
157
+ return readEngineLaborFallbackFieldFrom(source) !== void 0;
158
+ }
159
+ function readEngineLaborFallbackFieldFrom(source) {
160
+ if (typeof source !== "object" || source === null || Array.isArray(source)) {
161
+ return void 0;
162
+ }
163
+ let raw;
164
+ try {
165
+ raw = source.engineLaborFallback;
166
+ } catch {
167
+ return void 0;
168
+ }
169
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
170
+ const rec = raw;
171
+ if (typeof rec.engine !== "string" || typeof rec.failure !== "string" || rec.laborBy !== "seat") {
172
+ return void 0;
173
+ }
174
+ return buildEngineLaborFallbackField({
175
+ engine: rec.engine,
176
+ failure: rec.failure
177
+ });
178
+ }
179
+ var SEAT_FALLBACK_STATUS_SUFFIX;
180
+ var init_engine_labor_fallback = __esm({
181
+ "src/engine-labor-fallback.ts"() {
182
+ "use strict";
183
+ SEAT_FALLBACK_STATUS_SUFFIX = "-by-fallback";
184
+ }
185
+ });
186
+
139
187
  // src/package-contracts/judge-output.ts
140
188
  function validateAcceptedJudgeDetails(verdict) {
141
189
  if (verdict === null || typeof verdict !== "object" || Array.isArray(verdict)) throw new Error("Judge verdict has no execution discriminator");
@@ -145,13 +193,20 @@ function validateAcceptedJudgeDetails(verdict) {
145
193
  } catch {
146
194
  throw new Error("Judge verdict has no execution discriminator");
147
195
  }
148
- if (["converged", "continue", "escalate"].includes(String(judgeStatus))) return verdict;
196
+ if (typeof judgeStatus !== "string") {
197
+ throw new Error("Judge verdict has no execution discriminator");
198
+ }
199
+ const base = seatFallbackBaseStatus(judgeStatus);
200
+ if (["converged", "continue", "escalate"].includes(base) && seatFallbackStatusHasLawfulEvidence(judgeStatus, verdict)) {
201
+ return verdict;
202
+ }
149
203
  throw new Error("Judge verdict has no execution discriminator");
150
204
  }
151
205
  var JUDGE_OUTPUT_TOOL_NAME;
152
206
  var init_judge_output = __esm({
153
207
  "src/package-contracts/judge-output.ts"() {
154
208
  "use strict";
209
+ init_engine_labor_fallback();
155
210
  JUDGE_OUTPUT_TOOL_NAME = "ak_judge_output";
156
211
  }
157
212
  });
@@ -169,6 +224,10 @@ function read(value, key) {
169
224
  }
170
225
  }
171
226
  function validateRuntimeReviewerReceipt(output) {
227
+ const status = read(output, "status");
228
+ if (typeof status === "string" && !seatFallbackStatusHasLawfulEvidence(status, output)) {
229
+ throw new Error("Reviewer receipt has no recognized execution discriminator");
230
+ }
172
231
  const acceptedBatch = read(output, "acceptedBatch");
173
232
  const identities = read(output, "identities");
174
233
  const construction = read(identities, "construction");
@@ -199,11 +258,11 @@ function validateRuntimeReviewerReceipt(output) {
199
258
  const expectedPrompt = read(read(legs[index], "prompt"), "text");
200
259
  const actualPrompt = read(read(outcome, "prompt"), "text");
201
260
  if (expectedPrompt !== actualPrompt) throw new Error("Reviewer outcome prompt disagrees with accepted leg");
202
- const status = read(outcome, "status");
261
+ const status2 = read(outcome, "status");
203
262
  const report = read(reports, axis);
204
- if (status === "successful" && report === void 0)
263
+ if (status2 === "successful" && report === void 0)
205
264
  throw new Error("Successful Reviewer outcome lacks report");
206
- if (status === "failed" && report !== void 0) throw new Error("Failed Reviewer outcome cannot bind a report");
265
+ if (status2 === "failed" && report !== void 0) throw new Error("Failed Reviewer outcome cannot bind a report");
207
266
  }
208
267
  const specDisposition = read(output, "specDisposition");
209
268
  if (specDisposition === "launched") {
@@ -224,6 +283,7 @@ var REVIEWER_OUTPUT_TOOL_NAME;
224
283
  var init_reviewer_output = __esm({
225
284
  "src/package-contracts/reviewer-output.ts"() {
226
285
  "use strict";
286
+ init_engine_labor_fallback();
227
287
  REVIEWER_OUTPUT_TOOL_NAME = "ak_reviewer_output";
228
288
  }
229
289
  });
@@ -14086,12 +14146,18 @@ function read2(value, key) {
14086
14146
  }
14087
14147
  function validateDoctorSubmissionShape(value) {
14088
14148
  const status = read2(value, "status");
14089
- if (status !== "completed" && status !== "refused") throw new DoctorSubmissionContractError("Doctor submission has no recognized execution status");
14149
+ const base = typeof status === "string" ? seatFallbackBaseStatus(status) : status;
14150
+ if (base !== "completed" && base !== "refused") throw new DoctorSubmissionContractError("Doctor submission has no recognized execution status");
14151
+ if (typeof status === "string" && !seatFallbackStatusHasLawfulEvidence(status, value)) {
14152
+ throw new DoctorSubmissionContractError("Doctor submission has no recognized execution status");
14153
+ }
14090
14154
  return value;
14091
14155
  }
14092
14156
  function validateRecordedDoctorOutput(value) {
14093
14157
  const output = validateDoctorSubmissionShape(value);
14094
- if (read2(output, "status") === "completed" && read2(output, "cost") === void 0) throw new Error("Completed Doctor receipt has no runtime-owned cost testimony");
14158
+ const status = read2(output, "status");
14159
+ const base = typeof status === "string" ? seatFallbackBaseStatus(status) : status;
14160
+ if (base === "completed" && read2(output, "cost") === void 0) throw new Error("Completed Doctor receipt has no runtime-owned cost testimony");
14095
14161
  return output;
14096
14162
  }
14097
14163
  var DOCTOR_OUTPUT_TOOL_NAME, DOCTOR_TARGET_KINDS, nonblank, count, evidenceIds, guardrail, lastRealBite, assetKinds, findingBody, finding, caseIdentity, cost, doctorSubmissionVariants, doctorSubmissionSchema, doctorOutputSchema, doctorEvidenceReadSchema, DoctorSubmissionContractError;
@@ -14100,6 +14166,7 @@ var init_doctor_contracts = __esm({
14100
14166
  "use strict";
14101
14167
  init_build();
14102
14168
  init_canonical_json();
14169
+ init_engine_labor_fallback();
14103
14170
  init_open_tool_schema();
14104
14171
  DOCTOR_OUTPUT_TOOL_NAME = "ak_doctor_output";
14105
14172
  DOCTOR_TARGET_KINDS = ["law", "gate", "template", "station", "seat"];
@@ -14243,8 +14310,13 @@ function validateMergerInput(value) {
14243
14310
  }
14244
14311
  function validateMergerOutput(value, expectedAttemptId) {
14245
14312
  if (!record(value) || expectedAttemptId !== void 0 && value.attemptId !== expectedAttemptId) throw new Error("Merger output attempt mismatch");
14246
- if (value.status === "completed" && isFullGitObjectId(value.mergeCommitId)) return structuredClone(value);
14247
- if (value.status === "escalate") return structuredClone(value);
14313
+ const status = typeof value.status === "string" ? value.status : void 0;
14314
+ const statusBase = status !== void 0 ? seatFallbackBaseStatus(status) : void 0;
14315
+ if (status !== void 0 && !seatFallbackStatusHasLawfulEvidence(status, value)) {
14316
+ throw new Error("Merger output has no recognized execution discriminator");
14317
+ }
14318
+ if (statusBase === "completed" && isFullGitObjectId(value.mergeCommitId)) return structuredClone(value);
14319
+ if (statusBase === "escalate") return structuredClone(value);
14248
14320
  throw new Error("Merger output has no recognized execution discriminator");
14249
14321
  }
14250
14322
  var oidPattern, materialSchema, checkSchema, mergerInputSchema, mergerOutputVariants, mergerOutputSchema, MERGER_OUTPUT_TOOL_NAME, record, blank, MergerInputContractError;
@@ -14254,6 +14326,7 @@ var init_merger_contracts = __esm({
14254
14326
  init_build();
14255
14327
  init_git_object_id();
14256
14328
  init_exact_utf8();
14329
+ init_engine_labor_fallback();
14257
14330
  init_sha256();
14258
14331
  init_open_tool_schema();
14259
14332
  oidPattern = "^(?:[0-9a-f]{40}|[0-9a-f]{64})$";
@@ -15190,8 +15263,10 @@ function validateAcceptedDetails(toolName, details) {
15190
15263
  [MERGER_OUTPUT_TOOL_NAME]: ["completed", "escalate"]
15191
15264
  };
15192
15265
  const collectorDiscriminator = toolName === COLLECTOR_OUTPUT_TOOL && Array.isArray(candidate?.groups);
15193
- const runtimeBindingMissing = toolName === DOCTOR_OUTPUT_TOOL_NAME && discriminator === "completed" && !(candidate?.cost !== null && typeof candidate?.cost === "object") || toolName === REVIEWER_OUTPUT_TOOL_NAME && candidate?.version !== 2;
15194
- if (runtimeBindingMissing || !collectorDiscriminator && (typeof discriminator !== "string" || !lawfulStatuses[toolName].includes(discriminator))) {
15266
+ const baseDiscriminator = typeof discriminator === "string" ? seatFallbackBaseStatus(discriminator) : discriminator;
15267
+ const taintedWithoutEvidence = typeof discriminator === "string" && !seatFallbackStatusHasLawfulEvidence(discriminator, details);
15268
+ const runtimeBindingMissing = toolName === DOCTOR_OUTPUT_TOOL_NAME && baseDiscriminator === "completed" && !(candidate?.cost !== null && typeof candidate?.cost === "object") || toolName === REVIEWER_OUTPUT_TOOL_NAME && candidate?.version !== 2;
15269
+ if (taintedWithoutEvidence || runtimeBindingMissing || !collectorDiscriminator && (typeof discriminator !== "string" || !lawfulStatuses[toolName].includes(baseDiscriminator))) {
15195
15270
  throw new AcceptedDetailsContractError("terminating receipt has no recognized execution discriminator");
15196
15271
  }
15197
15272
  try {
@@ -15227,7 +15302,8 @@ function acceptedFacts(toolName, details) {
15227
15302
  return { status: details.judgeStatus };
15228
15303
  case MERGER_OUTPUT_TOOL_NAME: {
15229
15304
  const output = details;
15230
- return { status: output.status, ...output.status === "completed" && typeof output.mergeCommitId === "string" ? { commit: output.mergeCommitId } : {} };
15305
+ const status = output.status;
15306
+ return { status, ...seatFallbackBaseStatus(status) === "completed" && typeof output.mergeCommitId === "string" ? { commit: output.mergeCommitId } : {} };
15231
15307
  }
15232
15308
  case COLLECTOR_OUTPUT_TOOL:
15233
15309
  return { status: "collected" };
@@ -15241,6 +15317,7 @@ var init_terminating_tools = __esm({
15241
15317
  init_judge_output();
15242
15318
  init_reviewer_output();
15243
15319
  init_audit_escalation();
15320
+ init_engine_labor_fallback();
15244
15321
  init_doctor_contracts();
15245
15322
  init_merger_contracts();
15246
15323
  init_worker_output();
@@ -19266,42 +19343,6 @@ var init_auditor_dossier_tool = __esm({
19266
19343
  }
19267
19344
  });
19268
19345
 
19269
- // src/engine-labor-fallback.ts
19270
- function buildEngineLaborFallbackField(input) {
19271
- return Object.freeze({
19272
- engineLaborFallback: Object.freeze({
19273
- engine: input.engine,
19274
- failure: input.failure,
19275
- laborBy: "seat"
19276
- })
19277
- });
19278
- }
19279
- function readEngineLaborFallbackFieldFrom(source) {
19280
- if (typeof source !== "object" || source === null || Array.isArray(source)) {
19281
- return void 0;
19282
- }
19283
- let raw;
19284
- try {
19285
- raw = source.engineLaborFallback;
19286
- } catch {
19287
- return void 0;
19288
- }
19289
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
19290
- const rec = raw;
19291
- if (typeof rec.engine !== "string" || typeof rec.failure !== "string" || rec.laborBy !== "seat") {
19292
- return void 0;
19293
- }
19294
- return buildEngineLaborFallbackField({
19295
- engine: rec.engine,
19296
- failure: rec.failure
19297
- });
19298
- }
19299
- var init_engine_labor_fallback = __esm({
19300
- "src/engine-labor-fallback.ts"() {
19301
- "use strict";
19302
- }
19303
- });
19304
-
19305
19346
  // src/engine-detour-tool.ts
19306
19347
  var engineDetourArgsSchema;
19307
19348
  var init_engine_detour_tool = __esm({
@@ -20403,7 +20444,8 @@ function judgeDecisiveFacts(verdict, judgeStatus) {
20403
20444
  // #380: spread sole-built field; do not re-key here (S1).
20404
20445
  ...readEngineLaborFallbackFieldFrom(verdict)
20405
20446
  };
20406
- if (judgeStatus === "continue") {
20447
+ const statusBase = seatFallbackBaseStatus(judgeStatus);
20448
+ if (statusBase === "continue") {
20407
20449
  const fix = safelyRead(verdict, "fix");
20408
20450
  if (fix.readable && isRecord5(fix.value)) {
20409
20451
  const summary = safelyRead(fix.value, "summary");
@@ -20428,7 +20470,7 @@ function judgeDecisiveFacts(verdict, judgeStatus) {
20428
20470
  }
20429
20471
  }
20430
20472
  }
20431
- if (judgeStatus === "escalate") {
20473
+ if (statusBase === "escalate") {
20432
20474
  const gate = safelyRead(verdict, "decisionGate");
20433
20475
  if (gate.readable && isRecord5(gate.value)) {
20434
20476
  const question = safelyRead(gate.value, "question");
@@ -20452,10 +20494,11 @@ function coderDecisiveFacts(output) {
20452
20494
  const status = safelyRead(candidate, "status");
20453
20495
  const facts = {};
20454
20496
  if (status.readable && typeof status.value === "string") facts.coderStatus = status.value;
20497
+ const statusBase = status.readable && typeof status.value === "string" ? seatFallbackBaseStatus(status.value) : void 0;
20455
20498
  const remainingScope = safelyRead(candidate, "remainingScope");
20456
- if (status.readable && status.value === "unfinished" && remainingScope.readable && typeof remainingScope.value === "string") facts.remainingScope = remainingScope.value;
20499
+ if (statusBase === "unfinished" && remainingScope.readable && typeof remainingScope.value === "string") facts.remainingScope = remainingScope.value;
20457
20500
  const reason = safelyRead(candidate, "reason");
20458
- if (status.readable && status.value === "unfinished" && reason.readable && typeof reason.value === "string" && reason.value.trim().length > 0) {
20501
+ if (statusBase === "unfinished" && reason.readable && typeof reason.value === "string" && reason.value.trim().length > 0) {
20459
20502
  facts.reason = reason.value;
20460
20503
  }
20461
20504
  const report = safelyRead(candidate, "report");
@@ -20467,14 +20510,15 @@ function fixerDecisiveFacts(output) {
20467
20510
  const status = safelyRead(candidate, "status");
20468
20511
  const facts = {};
20469
20512
  if (status.readable && typeof status.value === "string") facts.fixerStatus = status.value;
20513
+ const statusBase = status.readable && typeof status.value === "string" ? seatFallbackBaseStatus(status.value) : void 0;
20470
20514
  const remainingScope = safelyRead(candidate, "remainingScope");
20471
- if (status.readable && (status.value === "unfinished" || status.value === "refused") && remainingScope.readable && typeof remainingScope.value === "string") facts.remainingScope = remainingScope.value;
20515
+ if ((statusBase === "unfinished" || statusBase === "refused") && remainingScope.readable && typeof remainingScope.value === "string") facts.remainingScope = remainingScope.value;
20472
20516
  const reason = safelyRead(candidate, "reason");
20473
- if (status.readable && status.value === "unfinished" && reason.readable && typeof reason.value === "string" && reason.value.trim().length > 0) {
20517
+ if (statusBase === "unfinished" && reason.readable && typeof reason.value === "string" && reason.value.trim().length > 0) {
20474
20518
  facts.reason = reason.value;
20475
20519
  }
20476
20520
  const blockerRead = safelyRead(candidate, "blocker");
20477
- if (status.readable && status.value === "refused" && blockerRead.readable && isRecord5(blockerRead.value)) {
20521
+ if (statusBase === "refused" && blockerRead.readable && isRecord5(blockerRead.value)) {
20478
20522
  const cause = safelyRead(blockerRead.value, "cause");
20479
20523
  if (cause.readable && typeof cause.value === "string") facts.blockerCause = cause.value;
20480
20524
  const prerequisiteId = safelyRead(blockerRead.value, "prerequisiteId");
@@ -20550,7 +20594,8 @@ function doctorDecisiveFacts(output) {
20550
20594
  const status = safelyRead(candidate, "status");
20551
20595
  const facts = { ...auditNoReceiptDecisiveFact(candidate) };
20552
20596
  if (status.readable && typeof status.value === "string") facts.doctorStatus = status.value;
20553
- if (status.readable && status.value === "refused") {
20597
+ const statusBase = status.readable && typeof status.value === "string" ? seatFallbackBaseStatus(status.value) : void 0;
20598
+ if (statusBase === "refused") {
20554
20599
  const reason = safelyRead(candidate, "reason");
20555
20600
  if (reason.readable && reason.value !== void 0) facts.reason = reason.value;
20556
20601
  const missing = safelyRead(candidate, "missingEvidence");
@@ -20597,7 +20642,8 @@ function reviewerDecisiveFacts(output) {
20597
20642
  facts.specDisposition = specDisposition.value;
20598
20643
  }
20599
20644
  const diagnostic = safelyRead(candidate, "diagnostic");
20600
- if (status.readable && status.value === "refused" && diagnostic.readable) {
20645
+ const statusBase = status.readable && typeof status.value === "string" ? seatFallbackBaseStatus(status.value) : void 0;
20646
+ if (statusBase === "refused" && diagnostic.readable) {
20601
20647
  facts.diagnosticPresent = typeof diagnostic.value === "string" && diagnostic.value.trim().length > 0;
20602
20648
  }
20603
20649
  return facts;
@@ -21038,9 +21084,11 @@ function extractJudgeRoleOutcome(entries) {
21038
21084
  if (isUnboundAuditEscalationFace(details)) continue;
21039
21085
  if (!isRecord5(details)) continue;
21040
21086
  const statusRead = safelyRead(details, "judgeStatus");
21041
- if (!statusRead.readable) continue;
21087
+ if (!statusRead.readable || typeof statusRead.value !== "string") continue;
21042
21088
  const judgeStatus = statusRead.value;
21043
- if (judgeStatus !== "converged" && judgeStatus !== "continue" && judgeStatus !== "escalate") continue;
21089
+ const statusBase = seatFallbackBaseStatus(judgeStatus);
21090
+ if (statusBase !== "converged" && statusBase !== "continue" && statusBase !== "escalate") continue;
21091
+ if (!seatFallbackStatusHasLawfulEvidence(judgeStatus, details)) continue;
21044
21092
  return {
21045
21093
  kind: "accepted",
21046
21094
  role: "judge",
@@ -21730,8 +21778,9 @@ async function settleLawfulDoctorTerminalResult(admitted) {
21730
21778
  if (entries === void 0) return void 0;
21731
21779
  const extracted = extractDoctorRoleOutcome(entries);
21732
21780
  if (extracted === void 0) return void 0;
21733
- if (extracted.output !== void 0 && extracted.output.status === "completed") {
21734
- if (extracted.output.case.issueNumber !== admitted.caseIdentity.issueNumber || extracted.output.case.runsPath !== admitted.caseIdentity.runsPath) {
21781
+ if (extracted.output !== void 0 && seatFallbackBaseStatus(String(extracted.output.status)) === "completed") {
21782
+ const completedCase = extracted.output.case;
21783
+ if (completedCase.issueNumber !== admitted.caseIdentity.issueNumber || completedCase.runsPath !== admitted.caseIdentity.runsPath) {
21735
21784
  const error = new Error(
21736
21785
  "Doctor receipt case identity does not match admitted case identity"
21737
21786
  );
@@ -21945,7 +21994,8 @@ function mergerDecisiveFacts(output) {
21945
21994
  const attemptId = safelyRead(candidate, "attemptId");
21946
21995
  if (status.readable && typeof status.value === "string") facts.mergerStatus = status.value;
21947
21996
  if (attemptId.readable && attemptId.value !== void 0) facts.attemptId = attemptId.value;
21948
- const decisiveKey = status.readable && status.value === "completed" ? "mergeCommitId" : "diagnosis";
21997
+ const statusBase = status.readable && typeof status.value === "string" ? seatFallbackBaseStatus(status.value) : void 0;
21998
+ const decisiveKey = statusBase === "completed" ? "mergeCommitId" : "diagnosis";
21949
21999
  const decisive = safelyRead(candidate, decisiveKey);
21950
22000
  if (decisive.readable && decisive.value !== void 0) facts[decisiveKey] = decisive.value;
21951
22001
  return facts;
@@ -25881,8 +25931,9 @@ function mapTerminal(role, terminal) {
25881
25931
  noReceipt: false
25882
25932
  };
25883
25933
  }
25934
+ const statusBase = seatFallbackBaseStatus(status);
25884
25935
  const acceptedSet = ACCEPTED_STATUS[role];
25885
- if (acceptedSet === void 0 || !acceptedSet.has(status)) {
25936
+ if (acceptedSet === void 0 || !acceptedSet.has(statusBase)) {
25886
25937
  return {
25887
25938
  terminalLabel: status,
25888
25939
  accepted: false,
@@ -25891,9 +25942,9 @@ function mapTerminal(role, terminal) {
25891
25942
  noReceipt: false
25892
25943
  };
25893
25944
  }
25894
- const plannedDuty = WORKER_ROLES.has(role) && status === "planned";
25945
+ const plannedDuty = WORKER_ROLES.has(role) && statusBase === "planned";
25895
25946
  const successSet = SUCCESS_STATUS[role] ?? /* @__PURE__ */ new Set();
25896
- const success = !plannedDuty && successSet.has(status);
25947
+ const success = !plannedDuty && successSet.has(statusBase);
25897
25948
  const successEligible = !plannedDuty;
25898
25949
  return {
25899
25950
  terminalLabel: status,
@@ -26015,6 +26066,7 @@ var WORKER_ROLES, ACCEPTED_STATUS, SUCCESS_STATUS, acceptanceSuccessReworkFamily
26015
26066
  var init_acceptance_success_rework = __esm({
26016
26067
  "src/taishi-metric-families/acceptance-success-rework.ts"() {
26017
26068
  "use strict";
26069
+ init_engine_labor_fallback();
26018
26070
  init_taishi_median();
26019
26071
  WORKER_ROLES = /* @__PURE__ */ new Set(["coder", "fixer"]);
26020
26072
  ACCEPTED_STATUS = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.2139",
3
+ "version": "0.1.2148",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -37,6 +37,11 @@ grok --prompt-file /path/to/labor-prompt.md -m grok-4.6 --always-approve --outpu
37
37
  alias `--effort`; a low-tier run completed correctly). If the mandate names
38
38
  no tier, use the seat's ordered thinking tier; never omit the flag — the
39
39
  CLI default is not guaranteed to match the ordered tier.
40
+ - **Labor prompt must be seat-authored**: write the complete task brief into a
41
+ temp file and pass it via `--prompt-file`. Attachments/receipt JSON are
42
+ evidence material, not the prompt body — feeding them directly is rejected
43
+ by the CLI as non-ACP JSON (`JSON object must have a type field`;
44
+ live-verified 2026-08-21).
40
45
 
41
46
  ## Failure handling
42
47
 
@@ -9,6 +9,8 @@ const entries = [
9
9
  "work-subject-identity",
10
10
  "navigator-invocation-identity",
11
11
  "navigator-attendance",
12
+ // Navigator package-graph dependency (seat-fallback base status / ADR 0071).
13
+ "engine-labor-fallback",
12
14
  "activation-ledger-git",
13
15
  "activation-ledger-topology",
14
16
  "activation-reconciliation",
@@ -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
- export type DoctorOutput =
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
- if (status !== "completed" && status !== "refused") throw new DoctorSubmissionContractError("Doctor submission has no recognized execution status");
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
- if (read(output, "status") === "completed" && read(output, "cost") === undefined) throw new Error("Completed Doctor receipt has no runtime-owned cost testimony");
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
 
@@ -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
- return { ...receipt, ...field };
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")
@@ -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
- if (latest.resultStatus === "escalate") return "escalate-awaiting";
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) => run.hasResult && REJECTED_RESULT_STATUSES.has(run.resultStatus),
848
+ (run) =>
849
+ run.hasResult &&
850
+ REJECTED_RESULT_STATUSES.has(seatFallbackBaseStatus(run.resultStatus)),
847
851
  );
848
852
  steps.push({
849
853
  station: group.station,
@@ -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
- export type MergerOutput =
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
- if (value.status === "completed" && isFullGitObjectId(value.mergeCommitId)) return structuredClone(value) as MergerOutput;
69
- if (value.status === "escalate") return structuredClone(value) as MergerOutput;
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
  }