@akagilnc/pi-workflow-roles 0.1.2139 → 0.1.2146

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import { Type } from "typebox";
2
2
  import { canonicalJson } from "./canonical-json.js";
3
+ import { seatFallbackBaseStatus, seatFallbackStatusHasLawfulEvidence, } from "./engine-labor-fallback.js";
3
4
  import { openToolObjectFromUnion } from "./open-tool-schema.js";
4
5
  export const DOCTOR_EVIDENCE_TOOL_NAME = "ak_doctor_evidence";
5
6
  export const DOCTOR_OUTPUT_TOOL_NAME = "ak_doctor_output";
@@ -66,13 +67,20 @@ catch {
66
67
  } }
67
68
  export function validateDoctorSubmissionShape(value) {
68
69
  const status = read(value, "status");
69
- if (status !== "completed" && status !== "refused")
70
+ const base = typeof status === "string" ? seatFallbackBaseStatus(status) : status;
71
+ if (base !== "completed" && base !== "refused")
70
72
  throw new DoctorSubmissionContractError("Doctor submission has no recognized execution status");
73
+ // ADR 0071: tainted status requires latch-shaped engineLaborFallback evidence.
74
+ if (typeof status === "string" && !seatFallbackStatusHasLawfulEvidence(status, value)) {
75
+ throw new DoctorSubmissionContractError("Doctor submission has no recognized execution status");
76
+ }
71
77
  return value;
72
78
  }
73
79
  export function validateRecordedDoctorOutput(value) {
74
80
  const output = validateDoctorSubmissionShape(value);
75
- if (read(output, "status") === "completed" && read(output, "cost") === undefined)
81
+ const status = read(output, "status");
82
+ const base = typeof status === "string" ? seatFallbackBaseStatus(status) : status;
83
+ if (base === "completed" && read(output, "cost") === undefined)
76
84
  throw new Error("Completed Doctor receipt has no runtime-owned cost testimony");
77
85
  return output;
78
86
  }
@@ -1,121 +1,136 @@
1
- /**
2
- * #380 — sole shared seat-fallback declaration after engine detour failure.
3
- * Detour rejoins the main road (ADR 0069); silence is the only crime.
4
- * Construction of the typed receipt field lives in exactly one site (S1).
5
- */
6
- /** Activation-scoped latch holder (one parent seat activation at a time). */
7
1
  let activationLatch;
8
- /** Sole construction site for typed receipt field `engineLaborFallback` (#380 S1). */
9
- export function buildEngineLaborFallbackField(input) {
10
- return Object.freeze({
11
- engineLaborFallback: Object.freeze({
12
- engine: input.engine,
13
- failure: input.failure,
14
- laborBy: "seat",
15
- }),
16
- });
2
+ function buildEngineLaborFallbackField(input) {
3
+ return Object.freeze({
4
+ engineLaborFallback: Object.freeze({
5
+ engine: input.engine,
6
+ failure: input.failure,
7
+ laborBy: "seat"
8
+ })
9
+ });
17
10
  }
18
- export function createEngineLaborFallbackLatch() {
19
- return { field: undefined };
20
- }
21
- /**
22
- * Record first detour failure for this latch (first wins; parallel legs share one field).
23
- * Always returns the latched first-wins value so tool details and receipt projection match.
24
- */
25
- export function recordEngineLaborFallback(latch, input) {
26
- const field = buildEngineLaborFallbackField(input);
27
- if (latch.field === undefined)
28
- latch.field = field;
29
- return latch.field;
30
- }
31
- export function readEngineLaborFallbackField(latch) {
32
- return latch?.field;
33
- }
34
- /**
35
- * Merge sole-built field into typed receipt details.
36
- * Spread only — must not construct the field key again (S1).
37
- * Without a mechanical latch, strip any model-injected reserved key (no forged declaration).
38
- */
39
- export function withEngineLaborFallbackField(receipt, field) {
40
- if (field !== undefined) {
41
- return { ...receipt, ...field };
42
- }
43
- if (!Object.prototype.hasOwnProperty.call(receipt, "engineLaborFallback")) {
44
- return receipt;
45
- }
46
- const { engineLaborFallback: _forged, ...rest } = receipt;
47
- return rest;
48
- }
49
- /** Install the activation-scoped latch (any role session_start with engine). */
50
- export function installActivationEngineLaborFallbackLatch(latch) {
51
- activationLatch = latch;
52
- }
53
- /** Clear activation latch (session end / next activation). */
54
- export function clearActivationEngineLaborFallbackLatch() {
55
- activationLatch = undefined;
56
- }
57
- /** Active activation latch, if any (legs inherit parent seat activation). */
58
- export function activationEngineLaborFallbackLatch() {
59
- return activationLatch;
60
- }
61
- /** Read fallback field from the active activation latch. */
62
- export function readActivationEngineLaborFallbackField() {
63
- return readEngineLaborFallbackField(activationLatch);
64
- }
65
- /**
66
- * Read a previously attached declaration from a typed receipt / details object.
67
- * Rebuilds via the sole construction site — callers must spread, never re-key.
68
- */
69
- export function readEngineLaborFallbackFieldFrom(source) {
70
- if (typeof source !== "object" || source === null || Array.isArray(source)) {
71
- return undefined;
72
- }
73
- let raw;
11
+ const SEAT_FALLBACK_STATUS_SUFFIX = "-by-fallback";
12
+ function isSeatFallbackTaintedStatus(status) {
13
+ return status.endsWith(SEAT_FALLBACK_STATUS_SUFFIX);
14
+ }
15
+ function seatFallbackBaseStatus(status) {
16
+ return isSeatFallbackTaintedStatus(status) ? status.slice(0, -SEAT_FALLBACK_STATUS_SUFFIX.length) : status;
17
+ }
18
+ function seatFallbackStatusHasLawfulEvidence(status, source) {
19
+ if (!isSeatFallbackTaintedStatus(status)) return true;
20
+ return readEngineLaborFallbackFieldFrom(source) !== void 0;
21
+ }
22
+ function taintStatusForSeatFallback(status) {
23
+ if (status.length === 0 || isSeatFallbackTaintedStatus(status)) return status;
24
+ return `${status}${SEAT_FALLBACK_STATUS_SUFFIX}`;
25
+ }
26
+ const STATUS_DISCRIMINATOR_KEYS = ["judgeStatus", "status"];
27
+ function taintReceiptStatusDiscriminators(receipt) {
28
+ let next;
29
+ for (const key of STATUS_DISCRIMINATOR_KEYS) {
30
+ if (!Object.prototype.hasOwnProperty.call(receipt, key)) continue;
31
+ let value;
74
32
  try {
75
- raw = source.engineLaborFallback;
76
- }
77
- catch {
78
- return undefined;
33
+ value = receipt[key];
34
+ } catch {
35
+ continue;
79
36
  }
80
- if (typeof raw !== "object" || raw === null || Array.isArray(raw))
81
- return undefined;
82
- const rec = raw;
83
- if (typeof rec.engine !== "string" ||
84
- typeof rec.failure !== "string" ||
85
- rec.laborBy !== "seat") {
86
- return undefined;
37
+ if (typeof value !== "string" || value.length === 0) continue;
38
+ const tainted = taintStatusForSeatFallback(value);
39
+ if (tainted === value) continue;
40
+ if (next === void 0) {
41
+ next = { ...receipt };
87
42
  }
88
- return buildEngineLaborFallbackField({
89
- engine: rec.engine,
90
- failure: rec.failure,
91
- });
43
+ next[key] = tainted;
44
+ }
45
+ return next === void 0 ? receipt : next;
92
46
  }
93
- /**
94
- * Restore activation latch from durable session tool results (#380 resume).
95
- * Scans existing same-session detour tool results only — no sidecar / new entry type.
96
- * Replays through recordEngineLaborFallback so first-wins + sole producer stay intact.
97
- */
98
- export function restoreEngineLaborFallbackFromSessionEntries(latch, entries, toolName) {
99
- for (const entry of entries) {
100
- if (typeof entry !== "object" || entry === null)
101
- continue;
102
- const row = entry;
103
- if (row.type !== "message")
104
- continue;
105
- const message = row.message;
106
- if (typeof message !== "object" || message === null)
107
- continue;
108
- const msg = message;
109
- if (msg.role !== "toolResult")
110
- continue;
111
- if (msg.toolName !== toolName)
112
- continue;
113
- const field = readEngineLaborFallbackFieldFrom(msg.details);
114
- if (field === undefined)
115
- continue;
116
- recordEngineLaborFallback(latch, {
117
- engine: field.engineLaborFallback.engine,
118
- failure: field.engineLaborFallback.failure,
119
- });
120
- }
47
+ function createEngineLaborFallbackLatch() {
48
+ return { field: void 0 };
49
+ }
50
+ function recordEngineLaborFallback(latch, input) {
51
+ const field = buildEngineLaborFallbackField(input);
52
+ if (latch.field === void 0) latch.field = field;
53
+ return latch.field;
54
+ }
55
+ function readEngineLaborFallbackField(latch) {
56
+ return latch?.field;
57
+ }
58
+ function withEngineLaborFallbackField(receipt, field) {
59
+ if (field !== void 0) {
60
+ const taintedReceipt = taintReceiptStatusDiscriminators(receipt);
61
+ return { ...taintedReceipt, ...field };
62
+ }
63
+ if (!Object.prototype.hasOwnProperty.call(receipt, "engineLaborFallback")) {
64
+ return receipt;
65
+ }
66
+ const { engineLaborFallback: _forged, ...rest } = receipt;
67
+ return rest;
68
+ }
69
+ function installActivationEngineLaborFallbackLatch(latch) {
70
+ activationLatch = latch;
71
+ }
72
+ function clearActivationEngineLaborFallbackLatch() {
73
+ activationLatch = void 0;
74
+ }
75
+ function activationEngineLaborFallbackLatch() {
76
+ return activationLatch;
77
+ }
78
+ function readActivationEngineLaborFallbackField() {
79
+ return readEngineLaborFallbackField(activationLatch);
80
+ }
81
+ function readEngineLaborFallbackFieldFrom(source) {
82
+ if (typeof source !== "object" || source === null || Array.isArray(source)) {
83
+ return void 0;
84
+ }
85
+ let raw;
86
+ try {
87
+ raw = source.engineLaborFallback;
88
+ } catch {
89
+ return void 0;
90
+ }
91
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
92
+ const rec = raw;
93
+ if (typeof rec.engine !== "string" || typeof rec.failure !== "string" || rec.laborBy !== "seat") {
94
+ return void 0;
95
+ }
96
+ return buildEngineLaborFallbackField({
97
+ engine: rec.engine,
98
+ failure: rec.failure
99
+ });
100
+ }
101
+ function restoreEngineLaborFallbackFromSessionEntries(latch, entries, toolName) {
102
+ for (const entry of entries) {
103
+ if (typeof entry !== "object" || entry === null) continue;
104
+ const row = entry;
105
+ if (row.type !== "message") continue;
106
+ const message = row.message;
107
+ if (typeof message !== "object" || message === null) continue;
108
+ const msg = message;
109
+ if (msg.role !== "toolResult") continue;
110
+ if (msg.toolName !== toolName) continue;
111
+ const field = readEngineLaborFallbackFieldFrom(msg.details);
112
+ if (field === void 0) continue;
113
+ recordEngineLaborFallback(latch, {
114
+ engine: field.engineLaborFallback.engine,
115
+ failure: field.engineLaborFallback.failure
116
+ });
117
+ }
121
118
  }
119
+ export {
120
+ SEAT_FALLBACK_STATUS_SUFFIX,
121
+ activationEngineLaborFallbackLatch,
122
+ buildEngineLaborFallbackField,
123
+ clearActivationEngineLaborFallbackLatch,
124
+ createEngineLaborFallbackLatch,
125
+ installActivationEngineLaborFallbackLatch,
126
+ isSeatFallbackTaintedStatus,
127
+ readActivationEngineLaborFallbackField,
128
+ readEngineLaborFallbackField,
129
+ readEngineLaborFallbackFieldFrom,
130
+ recordEngineLaborFallback,
131
+ restoreEngineLaborFallbackFromSessionEntries,
132
+ seatFallbackBaseStatus,
133
+ seatFallbackStatusHasLawfulEvidence,
134
+ taintStatusForSeatFallback,
135
+ withEngineLaborFallbackField
136
+ };
@@ -1,6 +1,7 @@
1
1
  import { Type } from "typebox";
2
2
  import { isFullGitObjectId } from "./git-object-id.js";
3
3
  import { exactUtf8 } from "./exact-utf8.js";
4
+ import { seatFallbackBaseStatus, seatFallbackStatusHasLawfulEvidence, } from "./engine-labor-fallback.js";
4
5
  import { sha256Hex } from "./sha256.js";
5
6
  import { openToolObjectFromUnion } from "./open-tool-schema.js";
6
7
  const oidPattern = "^(?:[0-9a-f]{40}|[0-9a-f]{64})$";
@@ -68,9 +69,15 @@ export function validateMergerInput(value) {
68
69
  export function validateMergerOutput(value, expectedAttemptId) {
69
70
  if (!record(value) || (expectedAttemptId !== undefined && value.attemptId !== expectedAttemptId))
70
71
  throw new Error("Merger output attempt mismatch");
71
- if (value.status === "completed" && isFullGitObjectId(value.mergeCommitId))
72
+ const status = typeof value.status === "string" ? value.status : undefined;
73
+ const statusBase = status !== undefined ? seatFallbackBaseStatus(status) : undefined;
74
+ // ADR 0071: tainted status requires latch-shaped engineLaborFallback evidence.
75
+ if (status !== undefined && !seatFallbackStatusHasLawfulEvidence(status, value)) {
76
+ throw new Error("Merger output has no recognized execution discriminator");
77
+ }
78
+ if (statusBase === "completed" && isFullGitObjectId(value.mergeCommitId))
72
79
  return structuredClone(value);
73
- if (value.status === "escalate")
80
+ if (statusBase === "escalate")
74
81
  return structuredClone(value);
75
82
  throw new Error("Merger output has no recognized execution discriminator");
76
83
  }
@@ -6,6 +6,7 @@ import { ModelRuntime } from "@earendil-works/pi-coding-agent";
6
6
  import { createAssistantMessageEventStream } from "@earendil-works/pi-ai";
7
7
  import { Type } from "typebox";
8
8
  import { Value } from "typebox/value";
9
+ import { seatFallbackBaseStatus } from "./engine-labor-fallback.js";
9
10
  import {
10
11
  NAVIGATOR_INVOCATION_ENTRY,
11
12
  mintNavigatorInvocationId
@@ -257,6 +258,13 @@ function createNavigatorPrepareTool(onOutput) {
257
258
  }
258
259
  };
259
260
  }
261
+ function statusListMatchesSettlement(candidateStatuses, settlementStatus) {
262
+ if (candidateStatuses.includes(settlementStatus)) return true;
263
+ const settlementBase = seatFallbackBaseStatus(settlementStatus);
264
+ return candidateStatuses.some(
265
+ (status) => seatFallbackBaseStatus(status) === settlementBase
266
+ );
267
+ }
260
268
  function selectNavigatorCandidate(candidates, settlement) {
261
269
  if (settlement.kind !== "accepted") return void 0;
262
270
  const usable = candidates.filter((candidate) => candidate.next !== void 0);
@@ -267,7 +275,7 @@ function selectNavigatorCandidate(candidates, settlement) {
267
275
  if (rolePhaseMatched.length > 0) {
268
276
  if (settlement.status !== void 0) {
269
277
  const statusSpecific = rolePhaseMatched.find(
270
- (candidate) => candidate.matches?.statuses?.includes(settlement.status) === true
278
+ (candidate) => candidate.matches?.statuses !== void 0 && statusListMatchesSettlement(candidate.matches.statuses, settlement.status)
271
279
  );
272
280
  if (statusSpecific !== void 0) {
273
281
  return { candidate: statusSpecific, matchedToSettlement: true };
@@ -1,4 +1,5 @@
1
1
  /** Package-owned Judge output leaf — no role registration surface. */
2
+ import { seatFallbackBaseStatus, seatFallbackStatusHasLawfulEvidence, } from "../engine-labor-fallback.js";
2
3
  export const JUDGE_OUTPUT_TOOL_NAME = "ak_judge_output";
3
4
  export const JUDGE_ACCEPTED_TEXT = "Judge verdict accepted";
4
5
  export function validateAcceptedJudgeDetails(verdict) {
@@ -11,7 +12,13 @@ export function validateAcceptedJudgeDetails(verdict) {
11
12
  catch {
12
13
  throw new Error("Judge verdict has no execution discriminator");
13
14
  }
14
- if (["converged", "continue", "escalate"].includes(String(judgeStatus)))
15
+ if (typeof judgeStatus !== "string") {
16
+ throw new Error("Judge verdict has no execution discriminator");
17
+ }
18
+ const base = seatFallbackBaseStatus(judgeStatus);
19
+ if (["converged", "continue", "escalate"].includes(base) &&
20
+ seatFallbackStatusHasLawfulEvidence(judgeStatus, verdict)) {
15
21
  return verdict;
22
+ }
16
23
  throw new Error("Judge verdict has no execution discriminator");
17
24
  }
@@ -1,4 +1,5 @@
1
1
  /** Package-owned Reviewer intent and runtime-receipt leaves — no role registration surface. */
2
+ import { seatFallbackBaseStatus, seatFallbackStatusHasLawfulEvidence, } from "../engine-labor-fallback.js";
2
3
  export const REVIEWER_OUTPUT_TOOL_NAME = "ak_reviewer_output";
3
4
  export const REVIEWER_ACCEPTED_TEXT = "Reviewer report accepted";
4
5
  function isRecord(value) {
@@ -24,6 +25,11 @@ export function validateReviewerIntent(output) {
24
25
  }
25
26
  /** Validate runtime-owned facts at their real identity seams (target pins + plain text). */
26
27
  export function validateRuntimeReviewerReceipt(output) {
28
+ const status = read(output, "status");
29
+ // ADR 0071: tainted top-level status requires latch-shaped engineLaborFallback evidence.
30
+ if (typeof status === "string" && !seatFallbackStatusHasLawfulEvidence(status, output)) {
31
+ throw new Error("Reviewer receipt has no recognized execution discriminator");
32
+ }
27
33
  const acceptedBatch = read(output, "acceptedBatch");
28
34
  const identities = read(output, "identities");
29
35
  const construction = read(identities, "construction");
@@ -91,7 +97,9 @@ export function validateRuntimeReviewerReceipt(output) {
91
97
  export function projectReviewerIntentToReceipt(intentValue, receiptValue) {
92
98
  const intent = validateReviewerIntent(intentValue);
93
99
  const receipt = validateRuntimeReviewerReceipt(receiptValue);
94
- if (receipt.status !== intent.status || (intent.status === "completed" ? receipt.diagnostic !== undefined : receipt.diagnostic !== intent.diagnostic)) {
100
+ const receiptStatus = String(receipt.status);
101
+ const receiptBase = seatFallbackBaseStatus(receiptStatus);
102
+ if (receiptBase !== intent.status || (intent.status === "completed" ? receipt.diagnostic !== undefined : receipt.diagnostic !== intent.diagnostic)) {
95
103
  throw new Error("Reviewer intent and runtime receipt disagree");
96
104
  }
97
105
  return receipt;
@@ -6,6 +6,7 @@ import { COLLECTOR_ACCEPTED_TEXT, COLLECTOR_OUTPUT_TOOL, validateAcceptedCollect
6
6
  import { JUDGE_ACCEPTED_TEXT, JUDGE_OUTPUT_TOOL_NAME, validateAcceptedJudgeDetails, } from "./judge-output.js";
7
7
  import { REVIEWER_ACCEPTED_TEXT, REVIEWER_OUTPUT_TOOL_NAME, projectReviewerIntentToReceipt, validateReviewerIntent, validateRuntimeReviewerReceipt, } from "./reviewer-output.js";
8
8
  import { isAuditEscalationResult } from "../audit-escalation.js";
9
+ import { seatFallbackBaseStatus, seatFallbackStatusHasLawfulEvidence, } from "../engine-labor-fallback.js";
9
10
  import { DOCTOR_OUTPUT_TOOL_NAME, validateDoctorSubmissionShape, validateRecordedDoctorOutput } from "../doctor-contracts.js";
10
11
  import { MERGER_ACCEPTED_TEXT, MERGER_OUTPUT_TOOL_NAME, validateMergerOutput } from "../merger-contracts.js";
11
12
  import { CODER_ACCEPTED_TEXT, CODER_OUTPUT_TOOL_NAME, FIXER_ACCEPTED_TEXT, FIXER_OUTPUT_TOOL_NAME, validateAcceptedWorkerDetails, } from "./worker-output.js";
@@ -79,9 +80,15 @@ export function validateAcceptedDetails(toolName, details) {
79
80
  [MERGER_OUTPUT_TOOL_NAME]: ["completed", "escalate"],
80
81
  };
81
82
  const collectorDiscriminator = toolName === COLLECTOR_OUTPUT_TOOL && Array.isArray(candidate?.groups);
82
- const runtimeBindingMissing = (toolName === DOCTOR_OUTPUT_TOOL_NAME && discriminator === "completed" && !(candidate?.cost !== null && typeof candidate?.cost === "object")) ||
83
+ const baseDiscriminator = typeof discriminator === "string" ? seatFallbackBaseStatus(discriminator) : discriminator;
84
+ // ADR 0071: `-by-fallback` is not independently lawful — require latch-shaped evidence.
85
+ const taintedWithoutEvidence = typeof discriminator === "string" &&
86
+ !seatFallbackStatusHasLawfulEvidence(discriminator, details);
87
+ const runtimeBindingMissing = (toolName === DOCTOR_OUTPUT_TOOL_NAME && baseDiscriminator === "completed" && !(candidate?.cost !== null && typeof candidate?.cost === "object")) ||
83
88
  (toolName === REVIEWER_OUTPUT_TOOL_NAME && candidate?.version !== 2);
84
- if (runtimeBindingMissing || (!collectorDiscriminator && (typeof discriminator !== "string" || !lawfulStatuses[toolName].includes(discriminator)))) {
89
+ if (taintedWithoutEvidence ||
90
+ runtimeBindingMissing ||
91
+ (!collectorDiscriminator && (typeof discriminator !== "string" || !lawfulStatuses[toolName].includes(baseDiscriminator)))) {
85
92
  throw new AcceptedDetailsContractError("terminating receipt has no recognized execution discriminator");
86
93
  }
87
94
  try {
@@ -112,14 +119,15 @@ export function validateAcceptedLifecycle(toolName, argumentsValue, detailsValue
112
119
  const details = validateAcceptedDetails(toolName, detailsValue);
113
120
  if (toolName === DOCTOR_OUTPUT_TOOL_NAME) {
114
121
  const testimony = validateDoctorSubmissionShape(argumentsValue);
115
- if (testimony.status === "refused") {
122
+ if (seatFallbackBaseStatus(String(testimony.status)) === "refused") {
116
123
  if (!deepEqual(testimony, details))
117
124
  throw new Error("accepted tool lifecycle details mismatch");
118
125
  return details;
119
126
  }
120
127
  const receipt = details;
121
- if (receipt.status !== "completed")
128
+ if (seatFallbackBaseStatus(String(receipt.status)) !== "completed") {
122
129
  throw new Error("accepted tool lifecycle details mismatch");
130
+ }
123
131
  const { cost: _runtimeCost, ...projected } = receipt;
124
132
  if (!deepEqual(testimony, projected))
125
133
  throw new Error("accepted tool lifecycle details mismatch");
@@ -139,7 +147,8 @@ export function acceptedFacts(toolName, details) {
139
147
  case JUDGE_OUTPUT_TOOL_NAME: return { status: details.judgeStatus };
140
148
  case MERGER_OUTPUT_TOOL_NAME: {
141
149
  const output = details;
142
- return { status: output.status, ...(output.status === "completed" && typeof output.mergeCommitId === "string" ? { commit: output.mergeCommitId } : {}) };
150
+ const status = output.status;
151
+ return { status, ...(seatFallbackBaseStatus(status) === "completed" && typeof output.mergeCommitId === "string" ? { commit: output.mergeCommitId } : {}) };
143
152
  }
144
153
  case COLLECTOR_OUTPUT_TOOL:
145
154
  return { status: "collected" };