@akagilnc/pi-workflow-roles 0.1.1792 → 0.1.1813

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.
@@ -17904,6 +17904,33 @@ var init_package_owned_tool_idle = __esm({
17904
17904
  }
17905
17905
  });
17906
17906
 
17907
+ // src/receipt-delivery-policy.ts
17908
+ function isRecord4(value) {
17909
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17910
+ }
17911
+ function parseNoReceiptLifecycleFacts(input) {
17912
+ if (!isRecord4(input) || typeof input.terminalToolCalled !== "boolean" || input.deliveryTurns !== RECEIPT_DELIVERY_TURN_LIMIT || input.sessionCompletion !== "settled-without-accepted-receipt" || input.acceptedReceipt !== false || typeof input.runPointer !== "string" || input.runPointer.trim() === "" || typeof input.attemptPointer !== "string" || input.attemptPointer.trim() === "" || !Array.isArray(input.rejectedReceipts) || !input.rejectedReceipts.every((item) => isRecord4(item) && typeof item.reason === "string" && item.reason.trim() !== "")) {
17913
+ throw new TypeError("malformed no-receipt lifecycle facts");
17914
+ }
17915
+ return {
17916
+ terminalToolCalled: input.terminalToolCalled,
17917
+ rejectedReceipts: input.rejectedReceipts.map((item) => ({ reason: item.reason })),
17918
+ deliveryTurns: RECEIPT_DELIVERY_TURN_LIMIT,
17919
+ sessionCompletion: "settled-without-accepted-receipt",
17920
+ runPointer: input.runPointer,
17921
+ attemptPointer: input.attemptPointer,
17922
+ acceptedReceipt: false
17923
+ };
17924
+ }
17925
+ var RECEIPT_DELIVERY_TURN_LIMIT, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE;
17926
+ var init_receipt_delivery_policy = __esm({
17927
+ "src/receipt-delivery-policy.ts"() {
17928
+ "use strict";
17929
+ RECEIPT_DELIVERY_TURN_LIMIT = 2;
17930
+ NO_RECEIPT_LIFECYCLE_ENTRY_TYPE = "ak-no-receipt-lifecycle";
17931
+ }
17932
+ });
17933
+
17907
17934
  // src/evidence-child-executor.ts
17908
17935
  var init_evidence_child_executor = __esm({
17909
17936
  "src/evidence-child-executor.ts"() {
@@ -17911,6 +17938,7 @@ var init_evidence_child_executor = __esm({
17911
17938
  init_compliance_transport();
17912
17939
  init_package_owned_tool_idle();
17913
17940
  init_stream_idle_guard();
17941
+ init_receipt_delivery_policy();
17914
17942
  }
17915
17943
  });
17916
17944
 
@@ -17939,6 +17967,7 @@ var init_compliance_transport = __esm({
17939
17967
  init_build();
17940
17968
  init_evidence_child_executor();
17941
17969
  init_auditor_dossier_tool();
17970
+ init_receipt_delivery_policy();
17942
17971
  nonblank2 = typebox_exports.String({ minLength: 1, pattern: "\\S" });
17943
17972
  decisionGateSchema = typebox_exports.Object({ question: nonblank2, options: typebox_exports.Array(nonblank2, { minItems: 1 }) }, { additionalProperties: false });
17944
17973
  complianceDecisionSchema = typebox_exports.Object({ status: typebox_exports.Unknown({ description: "Auditor decision status." }), violations: typebox_exports.Array(nonblank2, { description: "Observed compliance violations." }), conflicts: typebox_exports.Array(nonblank2, { description: "Unresolved authority or execution conflicts." }), decisionGate: typebox_exports.Union([decisionGateSchema, typebox_exports.Null()], { description: "Escalation question and available options." }) }, { additionalProperties: true, required: [] });
@@ -18301,7 +18330,7 @@ function jsonSafeComplianceCandidate(value) {
18301
18330
  return value === void 0 ? JSON_SAFE_UNDEFINED_ARGUMENT : value;
18302
18331
  }
18303
18332
  function isLawfulTypedTerminalOutcome(outcome) {
18304
- return outcome.kind === "accepted" || outcome.kind === "audit_escalation";
18333
+ return outcome.kind === "accepted" || outcome.kind === "audit_escalation" || outcome.kind === "no_receipt";
18305
18334
  }
18306
18335
  function exitCodeForTerminalOutcome(outcome) {
18307
18336
  return isLawfulTypedTerminalOutcome(outcome) ? 0 : 1;
@@ -18650,7 +18679,7 @@ function extractSessionProviderStop(entries) {
18650
18679
  for (let i = entries.length - 1; i >= attemptStart; i -= 1) {
18651
18680
  const entry = entries[i];
18652
18681
  if (entry?.type !== "custom" || entry.customType !== COMPLIANCE_RESPONSE_ENTRY_TYPE) continue;
18653
- const response = isRecord4(entry.data) && isRecord4(entry.data.response) ? entry.data.response : void 0;
18682
+ const response = isRecord5(entry.data) && isRecord5(entry.data.response) ? entry.data.response : void 0;
18654
18683
  if (response?.role === "assistant" && response.stopReason === "error") {
18655
18684
  return {
18656
18685
  stopReason: "error",
@@ -18701,7 +18730,7 @@ async function readBoundEvidenceChildKnownFailure(sessionFile) {
18701
18730
  throw sessionReadFailure(error, "failed to read discovered evidence-child session");
18702
18731
  }
18703
18732
  const header = entries.find((entry) => entry.type === "session");
18704
- if (!isRecord4(header) || header.parentSession !== sessionFile) continue;
18733
+ if (!isRecord5(header) || header.parentSession !== sessionFile) continue;
18705
18734
  const stop = extractSessionProviderStop(entries);
18706
18735
  if (stop === void 0) continue;
18707
18736
  const primary = knownFailureFromProviderStop(stop);
@@ -18749,9 +18778,9 @@ async function readBoundAuditorKnownFailure(sessionFile) {
18749
18778
  throw sessionReadFailure(error, "failed to read discovered auditor session");
18750
18779
  }
18751
18780
  const header = entries.find((entry) => entry.type === "session");
18752
- if (!isRecord4(header) || header.parentSession !== sessionFile) continue;
18781
+ if (!isRecord5(header) || header.parentSession !== sessionFile) continue;
18753
18782
  const bindingEntry = entries.find((entry) => entry.type === "custom" && entry.customType === AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE);
18754
- const bindingParent = isRecord4(bindingEntry?.data) && isRecord4(bindingEntry.data.parent) ? bindingEntry.data.parent : void 0;
18783
+ const bindingParent = isRecord5(bindingEntry?.data) && isRecord5(bindingEntry.data.parent) ? bindingEntry.data.parent : void 0;
18755
18784
  const attemptEntryId = typeof bindingParent?.attemptEntryId === "string" ? bindingParent.attemptEntryId : void 0;
18756
18785
  const attemptEntryIndex = attemptEntryId === void 0 ? -1 : parentEntries.findIndex((entry) => entry.id === attemptEntryId);
18757
18786
  if (bindingParent?.sessionId !== parentId || bindingParent.sessionFile !== sessionFile || attemptEntryIndex < latestParentUserIndex) continue;
@@ -18759,11 +18788,11 @@ async function readBoundAuditorKnownFailure(sessionFile) {
18759
18788
  if (stop === void 0) continue;
18760
18789
  for (let i = entries.length - 1; i >= 0; i -= 1) {
18761
18790
  const entry = entries[i];
18762
- if (entry?.type !== "custom" || entry.customType !== AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE || !isRecord4(entry.data)) continue;
18763
- const parent = isRecord4(entry.data.parent) ? entry.data.parent : void 0;
18764
- const failure = isRecord4(entry.data.failure) ? entry.data.failure : void 0;
18791
+ if (entry?.type !== "custom" || entry.customType !== AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE || !isRecord5(entry.data)) continue;
18792
+ const parent = isRecord5(entry.data.parent) ? entry.data.parent : void 0;
18793
+ const failure = isRecord5(entry.data.failure) ? entry.data.failure : void 0;
18765
18794
  if (parent?.sessionId !== parentId || parent.sessionFile !== sessionFile || parent.attemptEntryId !== attemptEntryId || failure?.cause !== "provider") continue;
18766
- const identity = isRecord4(failure.identity) ? failure.identity : void 0;
18795
+ const identity = isRecord5(failure.identity) ? failure.identity : void 0;
18767
18796
  return {
18768
18797
  cause: "provider",
18769
18798
  ...identity === void 0 ? {} : { identity: {
@@ -18771,7 +18800,7 @@ async function readBoundAuditorKnownFailure(sessionFile) {
18771
18800
  ...typeof identity.code === "string" || typeof identity.code === "number" ? { code: identity.code } : {}
18772
18801
  } },
18773
18802
  ...typeof failure.diagnostic === "string" ? { diagnostic: failure.diagnostic } : {},
18774
- ...isRecord4(failure.details) ? { details: failure.details } : {}
18803
+ ...isRecord5(failure.details) ? { details: failure.details } : {}
18775
18804
  };
18776
18805
  }
18777
18806
  const primary = knownFailureFromProviderStop(stop);
@@ -18802,8 +18831,8 @@ function typedFailedTerminatingToolKnownFailure(entries) {
18802
18831
  if (classification.kind !== "infrastructure") continue;
18803
18832
  if (typeof message.toolCallId !== "string" || typeof message.toolName !== "string") continue;
18804
18833
  if (boundRoleToolCallForResult(attemptEntries, i, message, message.toolName) === void 0) continue;
18805
- const textPart = Array.isArray(message.content) ? message.content.find((part) => isRecord4(part) && part.type === "text" && typeof part.text === "string") : void 0;
18806
- const diagnostic = isRecord4(textPart) ? textPart.text : void 0;
18834
+ const textPart = Array.isArray(message.content) ? message.content.find((part) => isRecord5(part) && part.type === "text" && typeof part.text === "string") : void 0;
18835
+ const diagnostic = isRecord5(textPart) ? textPart.text : void 0;
18807
18836
  return {
18808
18837
  cause: "output",
18809
18838
  identity: { name: message.toolName, code: message.toolCallId },
@@ -18864,7 +18893,7 @@ async function resolveAuditedRunnerKnownFailure(input) {
18864
18893
  const parentStop = await readSessionProviderStop(input.sessionFile);
18865
18894
  return parentStop === void 0 ? input.credential : knownFailureFromProviderStop(parentStop);
18866
18895
  }
18867
- function isRecord4(value) {
18896
+ function isRecord5(value) {
18868
18897
  return typeof value === "object" && value !== null && !Array.isArray(value);
18869
18898
  }
18870
18899
  function safelyRead(object, key) {
@@ -18874,11 +18903,20 @@ function safelyRead(object, key) {
18874
18903
  return { readable: false };
18875
18904
  }
18876
18905
  }
18906
+ function auditNoReceiptDecisiveFact(candidate) {
18907
+ const projected = safelyRead(candidate, "auditNoReceipt");
18908
+ if (!projected.readable || projected.value === void 0) return {};
18909
+ try {
18910
+ return { auditNoReceipt: parseNoReceiptLifecycleFacts(projected.value) };
18911
+ } catch {
18912
+ return {};
18913
+ }
18914
+ }
18877
18915
  function judgeDecisiveFacts(verdict, judgeStatus) {
18878
- const facts = { judgeStatus };
18916
+ const facts = { judgeStatus, ...auditNoReceiptDecisiveFact(verdict) };
18879
18917
  if (judgeStatus === "continue") {
18880
18918
  const fix = safelyRead(verdict, "fix");
18881
- if (fix.readable && isRecord4(fix.value)) {
18919
+ if (fix.readable && isRecord5(fix.value)) {
18882
18920
  const summary = safelyRead(fix.value, "summary");
18883
18921
  if (summary.readable && typeof summary.value === "string") {
18884
18922
  facts.fixSummary = summary.value;
@@ -18888,7 +18926,7 @@ function judgeDecisiveFacts(verdict, judgeStatus) {
18888
18926
  if (classes.readable && Array.isArray(classes.value)) {
18889
18927
  try {
18890
18928
  facts.classes = classes.value.map((entry) => {
18891
- if (!isRecord4(entry)) throw new Error("unreadable Judge class");
18929
+ if (!isRecord5(entry)) throw new Error("unreadable Judge class");
18892
18930
  return {
18893
18931
  name: entry.name,
18894
18932
  owner: entry.owner,
@@ -18903,7 +18941,7 @@ function judgeDecisiveFacts(verdict, judgeStatus) {
18903
18941
  }
18904
18942
  if (judgeStatus === "escalate") {
18905
18943
  const gate = safelyRead(verdict, "decisionGate");
18906
- if (gate.readable && isRecord4(gate.value)) {
18944
+ if (gate.readable && isRecord5(gate.value)) {
18907
18945
  const question = safelyRead(gate.value, "question");
18908
18946
  const options = safelyRead(gate.value, "options");
18909
18947
  if (question.readable && typeof question.value === "string") {
@@ -18947,7 +18985,7 @@ function fixerDecisiveFacts(output) {
18947
18985
  facts.reason = reason.value;
18948
18986
  }
18949
18987
  const blockerRead = safelyRead(candidate, "blocker");
18950
- if (status.readable && status.value === "refused" && blockerRead.readable && isRecord4(blockerRead.value)) {
18988
+ if (status.readable && status.value === "refused" && blockerRead.readable && isRecord5(blockerRead.value)) {
18951
18989
  const cause = safelyRead(blockerRead.value, "cause");
18952
18990
  if (cause.readable && typeof cause.value === "string") facts.blockerCause = cause.value;
18953
18991
  const prerequisiteId = safelyRead(blockerRead.value, "prerequisiteId");
@@ -18959,13 +18997,13 @@ function fixerDecisiveFacts(output) {
18959
18997
  const blockers = [];
18960
18998
  try {
18961
18999
  for (const entry of classResults.value) {
18962
- if (!isRecord4(entry)) throw new Error("unreadable class result");
19000
+ if (!isRecord5(entry)) throw new Error("unreadable class result");
18963
19001
  const name = safelyRead(entry, "name");
18964
19002
  const disposition = safelyRead(entry, "disposition");
18965
19003
  if (!name.readable || !disposition.readable) throw new Error("unreadable class result");
18966
19004
  rows.push({ name: name.value, disposition: disposition.value });
18967
19005
  const blocker = safelyRead(entry, "blocker");
18968
- if (disposition.value === "refused" && blocker.readable && isRecord4(blocker.value)) blockers.push(blocker.value);
19006
+ if (disposition.value === "refused" && blocker.readable && isRecord5(blocker.value)) blockers.push(blocker.value);
18969
19007
  }
18970
19008
  facts.classResultCount = rows.length;
18971
19009
  facts.classDispositions = rows;
@@ -18998,7 +19036,7 @@ function collectorDecisiveFacts(receipt) {
18998
19036
  if (groups.readable && Array.isArray(groups.value)) {
18999
19037
  try {
19000
19038
  facts.groups = groups.value.map((group) => {
19001
- if (!isRecord4(group)) throw new Error("unreadable Collector group");
19039
+ if (!isRecord5(group)) throw new Error("unreadable Collector group");
19002
19040
  const identity = safelyRead(group, "identity");
19003
19041
  const attendance = safelyRead(group, "attendance");
19004
19042
  const materials = safelyRead(group, "materials");
@@ -19021,7 +19059,7 @@ function collectorDecisiveFacts(receipt) {
19021
19059
  function doctorDecisiveFacts(output) {
19022
19060
  const candidate = output;
19023
19061
  const status = safelyRead(candidate, "status");
19024
- const facts = {};
19062
+ const facts = { ...auditNoReceiptDecisiveFact(candidate) };
19025
19063
  if (status.readable && typeof status.value === "string") facts.doctorStatus = status.value;
19026
19064
  if (status.readable && status.value === "refused") {
19027
19065
  const reason = safelyRead(candidate, "reason");
@@ -19031,7 +19069,7 @@ function doctorDecisiveFacts(output) {
19031
19069
  return facts;
19032
19070
  }
19033
19071
  const caseValue = safelyRead(candidate, "case");
19034
- if (caseValue.readable && isRecord4(caseValue.value)) {
19072
+ if (caseValue.readable && isRecord5(caseValue.value)) {
19035
19073
  const issueNumber = safelyRead(caseValue.value, "issueNumber");
19036
19074
  const runsPath = safelyRead(caseValue.value, "runsPath");
19037
19075
  if (issueNumber.readable && issueNumber.value !== void 0) facts.issueNumber = issueNumber.value;
@@ -19042,7 +19080,7 @@ function doctorDecisiveFacts(output) {
19042
19080
  return facts;
19043
19081
  }
19044
19082
  function reviewerAxes(value) {
19045
- if (!isRecord4(value)) return [];
19083
+ if (!isRecord5(value)) return [];
19046
19084
  return ["standards", "spec"].filter((axis) => {
19047
19085
  const projected = safelyRead(value, axis);
19048
19086
  return projected.readable && projected.value !== void 0;
@@ -19059,7 +19097,8 @@ function reviewerDecisiveFacts(output) {
19059
19097
  const facts = {
19060
19098
  axes,
19061
19099
  reportAxes,
19062
- acceptedBatchPresent: acceptedBatch.readable && acceptedBatch.value !== void 0
19100
+ acceptedBatchPresent: acceptedBatch.readable && acceptedBatch.value !== void 0,
19101
+ ...auditNoReceiptDecisiveFact(candidate)
19063
19102
  };
19064
19103
  if (status.readable && typeof status.value === "string") facts.reviewerStatus = status.value;
19065
19104
  const diagnostic = safelyRead(candidate, "diagnostic");
@@ -19137,9 +19176,9 @@ function assertCollectorReceiptMatchesAdmitted(receipt, admitted) {
19137
19176
  }
19138
19177
  }
19139
19178
  function isComplianceAuditIncomplete(value) {
19140
- if (!isRecord4(value) || value.status !== "audit-incomplete") return false;
19179
+ if (!isRecord5(value) || value.status !== "audit-incomplete") return false;
19141
19180
  const observation = value.observation;
19142
- if (!isRecord4(observation)) return false;
19181
+ if (!isRecord5(observation)) return false;
19143
19182
  if (observation.kind === "missing-dossier") return true;
19144
19183
  if (observation.kind === "missing-subject") {
19145
19184
  return typeof observation.subject === "string" && observation.subject.length > 0;
@@ -19189,7 +19228,7 @@ function boundRoleToolCallForResult(entries, resultIndex, message, outputToolNam
19189
19228
  const candidateMessage = entries[index]?.message;
19190
19229
  if (candidateMessage?.role === "assistant" && Array.isArray(candidateMessage.content)) {
19191
19230
  for (const part of candidateMessage.content) {
19192
- if (!isRecord4(part) || part.type !== "toolCall" || part.id !== callId) {
19231
+ if (!isRecord5(part) || part.type !== "toolCall" || part.id !== callId) {
19193
19232
  continue;
19194
19233
  }
19195
19234
  if (part.name !== outputToolName) return void 0;
@@ -19211,7 +19250,7 @@ function sameAuditValue(left, right) {
19211
19250
  (value, index) => sameAuditValue(value, right[index])
19212
19251
  );
19213
19252
  }
19214
- if (isRecord4(left) && isRecord4(right)) {
19253
+ if (isRecord5(left) && isRecord5(right)) {
19215
19254
  const leftKeys = Object.keys(left);
19216
19255
  const rightKeys = Object.keys(right);
19217
19256
  return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.hasOwn(right, key) && sameAuditValue(left[key], right[key]));
@@ -19249,7 +19288,7 @@ function boundAuditEscalationForResult(entries, resultIndex, message, role, outp
19249
19288
  const decision = readComplianceCandidate(retained.candidate);
19250
19289
  if (decision.status !== "escalate") return void 0;
19251
19290
  const details = message.details;
19252
- if (!isAuditEscalationResult(details) || !isRecord4(details)) return void 0;
19291
+ if (!isAuditEscalationResult(details) || !isRecord5(details)) return void 0;
19253
19292
  const projectedDetails = snapshotAuditDetails(details);
19254
19293
  const hasDecisionConflicts = Object.hasOwn(decision, "conflicts");
19255
19294
  const hasDetailsConflicts = Object.hasOwn(projectedDetails, "conflicts");
@@ -19269,7 +19308,7 @@ function isUnboundAuditEscalationFace(details) {
19269
19308
  if (isAuditEscalationResult(details)) return true;
19270
19309
  } catch {
19271
19310
  }
19272
- if (!isRecord4(details)) return false;
19311
+ if (!isRecord5(details)) return false;
19273
19312
  const kind = safelyRead(details, "kind");
19274
19313
  return kind.readable && kind.value === "audit_escalation";
19275
19314
  }
@@ -19286,11 +19325,11 @@ function boundRetainedAuditResponse(entries, callIndex, resultIndex, auditToolNa
19286
19325
  continue;
19287
19326
  }
19288
19327
  retainedResponseCount += 1;
19289
- if (!isRecord4(entry.data) || !isRecord4(entry.data.response)) continue;
19328
+ if (!isRecord5(entry.data) || !isRecord5(entry.data.response)) continue;
19290
19329
  const response = entry.data.response;
19291
19330
  if (!Array.isArray(response.content)) continue;
19292
19331
  const calls = response.content.filter(
19293
- (part) => isRecord4(part) && part.type === "toolCall"
19332
+ (part) => isRecord5(part) && part.type === "toolCall"
19294
19333
  );
19295
19334
  if (calls.length !== 1 || calls[0]?.name !== auditToolName) continue;
19296
19335
  matches.push({ candidate: calls[0]?.arguments });
@@ -19492,7 +19531,7 @@ function extractJudgeRoleOutcome(entries) {
19492
19531
  };
19493
19532
  }
19494
19533
  if (isUnboundAuditEscalationFace(details)) continue;
19495
- if (!isRecord4(details)) continue;
19534
+ if (!isRecord5(details)) continue;
19496
19535
  const statusRead = safelyRead(details, "judgeStatus");
19497
19536
  if (!statusRead.readable) continue;
19498
19537
  const judgeStatus = statusRead.value;
@@ -19566,7 +19605,7 @@ function parseNavigatorAttendanceDetails(details) {
19566
19605
  const advisoryDiagnostic = typeof details.routePlaybookReadFailure === "string" ? { advisoryDiagnostic: details.routePlaybookReadFailure } : {};
19567
19606
  if (disposition === "recommendation") {
19568
19607
  const next = details.next;
19569
- if (!isRecord4(next) || typeof next.role !== "string") {
19608
+ if (!isRecord5(next) || typeof next.role !== "string") {
19570
19609
  return {
19571
19610
  disposition: "unavailable",
19572
19611
  source: "unknown",
@@ -19574,7 +19613,7 @@ function parseNavigatorAttendanceDetails(details) {
19574
19613
  };
19575
19614
  }
19576
19615
  const reason = typeof details.reason === "string" ? details.reason : "";
19577
- const route = Array.isArray(details.route) ? details.route.filter(isRecord4).map((target) => ({
19616
+ const route = Array.isArray(details.route) ? details.route.filter(isRecord5).map((target) => ({
19578
19617
  role: String(target.role),
19579
19618
  phase: navigatorPhaseValue(target.phase)
19580
19619
  })) : void 0;
@@ -19652,7 +19691,7 @@ function extractNavigatorFact(entries, identity) {
19652
19691
  const entry = entries[i];
19653
19692
  if (entry?.type === "custom_message" && entry.customType === "ak-navigator-attendance") {
19654
19693
  const details = entry.message?.details ?? entry.details;
19655
- if (!isRecord4(details)) {
19694
+ if (!isRecord5(details)) {
19656
19695
  return {
19657
19696
  disposition: "unavailable",
19658
19697
  source: "unknown",
@@ -20116,7 +20155,7 @@ async function settleLawfulCollectorTerminalResult(admitted) {
20116
20155
  const residual = boundErroredToolCandidate(entries, index, message, COLLECTOR_WAIT_TOOL);
20117
20156
  if (residual === void 0) continue;
20118
20157
  const candidate = residual.candidate;
20119
- const duration = isRecord4(candidate) ? candidate.durationMs : void 0;
20158
+ const duration = isRecord5(candidate) ? candidate.durationMs : void 0;
20120
20159
  if (Number.isSafeInteger(duration) && duration >= 1 && duration <= 9e5) {
20121
20160
  continue;
20122
20161
  }
@@ -20576,8 +20615,8 @@ async function settleLawfulMergerTerminalResult(admitted, options) {
20576
20615
  const residual = boundErroredToolCandidate(entries, index, message, MERGER_OUTPUT_TOOL_NAME);
20577
20616
  if (residual === void 0) continue;
20578
20617
  const callMessage = entries[residual.callIndex]?.message;
20579
- const calls = callMessage?.role === "assistant" && Array.isArray(callMessage.content) ? callMessage.content.filter((part) => isRecord4(part) && part.type === "toolCall") : [];
20580
- const attemptId = isRecord4(residual.candidate) ? safelyRead(residual.candidate, "attemptId") : { readable: true, value: void 0 };
20618
+ const calls = callMessage?.role === "assistant" && Array.isArray(callMessage.content) ? callMessage.content.filter((part) => isRecord5(part) && part.type === "toolCall") : [];
20619
+ const attemptId = isRecord5(residual.candidate) ? safelyRead(residual.candidate, "attemptId") : { readable: true, value: void 0 };
20581
20620
  if (calls.length !== 1 || calls[0]?.name !== MERGER_OUTPUT_TOOL_NAME || !attemptId.readable || attemptId.value !== admitted.runId) {
20582
20621
  continue;
20583
20622
  }
@@ -20818,6 +20857,35 @@ function redactNavigatorFactForPublicTerminal(navigator, runId) {
20818
20857
  return { ...navigator, ...advisoryDiagnostic };
20819
20858
  }
20820
20859
  async function settleFailureTerminalResult(admitted, failure, options = {}) {
20860
+ if (failure.cause === "output") {
20861
+ const entries = await readBoundSessionEntries(admitted.sessionFile).catch(() => void 0);
20862
+ if (entries !== void 0) {
20863
+ let attemptStart = 0;
20864
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
20865
+ if (entries[index]?.type === "message" && entries[index]?.message?.role === "user") {
20866
+ attemptStart = index;
20867
+ break;
20868
+ }
20869
+ }
20870
+ const lifecycleEntry = entries.slice(attemptStart).reverse().find((entry) => entry.customType === NO_RECEIPT_LIFECYCLE_ENTRY_TYPE || entry.message?.customType === NO_RECEIPT_LIFECYCLE_ENTRY_TYPE);
20871
+ const raw = lifecycleEntry?.data ?? lifecycleEntry?.message?.details;
20872
+ if (raw !== void 0) {
20873
+ try {
20874
+ const facts = parseNoReceiptLifecycleFacts(raw);
20875
+ if (facts.runPointer === admitted.runDirectory && facts.attemptPointer === `current:${admitted.runDirectory}`) {
20876
+ const decisiveFacts2 = facts;
20877
+ return {
20878
+ roleOutcome: { kind: "no_receipt", role: admitted.role, status: "no-accepted-receipt", ...facts, decisiveFacts: decisiveFacts2 },
20879
+ navigator: await extractNavigatorFactFromAdmittedSession(admitted),
20880
+ artifacts: [],
20881
+ runId: admitted.runId
20882
+ };
20883
+ }
20884
+ } catch {
20885
+ }
20886
+ }
20887
+ }
20888
+ }
20821
20889
  const navigator = await extractNavigatorFactFromAdmittedSession(admitted);
20822
20890
  const artifacts = await publishFailureArtifacts(admitted, failure);
20823
20891
  const decisiveFacts = {
@@ -20871,16 +20939,16 @@ async function settleJudgeFailureTerminalResult(admitted, failure, options = {})
20871
20939
  return settleFailureTerminalResult(admitted, failure, options);
20872
20940
  }
20873
20941
  function presentFailureTerminal(terminal, io) {
20874
- if (terminal.roleOutcome.kind !== "failure") {
20875
- throw new TypeError("presentFailureTerminal requires a failure role outcome");
20942
+ if (terminal.roleOutcome.kind !== "failure" && terminal.roleOutcome.kind !== "no_receipt") {
20943
+ throw new TypeError("presentFailureTerminal requires a failure or no-receipt role outcome");
20876
20944
  }
20877
20945
  io.stdout(formatTerminalResult(terminal));
20878
- io.stderr(
20879
- formatFailureStderrDiagnostic({
20946
+ if (terminal.roleOutcome.kind === "failure") {
20947
+ io.stderr(formatFailureStderrDiagnostic({
20880
20948
  cause: terminal.roleOutcome.cause,
20881
20949
  diagnostic: terminal.roleOutcome.diagnostic
20882
- })
20883
- );
20950
+ }));
20951
+ }
20884
20952
  }
20885
20953
  var CONCISE_DIAGNOSTIC_MAX_CHARS, COLLECTOR_INFRASTRUCTURE_TOOLS;
20886
20954
  var init_settlement = __esm({
@@ -20903,6 +20971,7 @@ var init_settlement = __esm({
20903
20971
  init_merger_contracts();
20904
20972
  init_method_skill();
20905
20973
  init_navigator_invocation_identity();
20974
+ init_receipt_delivery_policy();
20906
20975
  init_packaged_role_registry();
20907
20976
  init_work_subject_identity();
20908
20977
  init_invocation();
@@ -0,0 +1,72 @@
1
+ /** Shared accepted-receipt delivery budget for role, auditor, and Navigator sessions (#288). */
2
+ export const RECEIPT_DELIVERY_TURN_LIMIT = 2;
3
+ export const RECEIPT_DELIVERY_PROMPT = "本 session 尚无已接受的 typed 回执。请现在调用具名终局工具交卷;若先前被打回,请按拒因修正后重交。";
4
+ export const NO_RECEIPT_LIFECYCLE_ENTRY_TYPE = "ak-no-receipt-lifecycle";
5
+ function isRecord(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+ /** Read only the facts required by Terminal consumers; persisted extensions are ignored. */
9
+ export function parseNoReceiptLifecycleFacts(input) {
10
+ if (!isRecord(input)
11
+ || typeof input.terminalToolCalled !== "boolean"
12
+ || input.deliveryTurns !== RECEIPT_DELIVERY_TURN_LIMIT
13
+ || input.sessionCompletion !== "settled-without-accepted-receipt"
14
+ || input.acceptedReceipt !== false
15
+ || typeof input.runPointer !== "string" || input.runPointer.trim() === ""
16
+ || typeof input.attemptPointer !== "string" || input.attemptPointer.trim() === ""
17
+ || !Array.isArray(input.rejectedReceipts)
18
+ || !input.rejectedReceipts.every((item) => isRecord(item)
19
+ && typeof item.reason === "string" && item.reason.trim() !== "")) {
20
+ throw new TypeError("malformed no-receipt lifecycle facts");
21
+ }
22
+ return {
23
+ terminalToolCalled: input.terminalToolCalled,
24
+ rejectedReceipts: input.rejectedReceipts.map((item) => ({ reason: item.reason })),
25
+ deliveryTurns: RECEIPT_DELIVERY_TURN_LIMIT,
26
+ sessionCompletion: "settled-without-accepted-receipt",
27
+ runPointer: input.runPointer,
28
+ attemptPointer: input.attemptPointer,
29
+ acceptedReceipt: false,
30
+ };
31
+ }
32
+ export function noReceiptLifecycleFacts(input) {
33
+ if (input.deliveryTurns !== RECEIPT_DELIVERY_TURN_LIMIT) {
34
+ throw new TypeError("no-receipt lifecycle requires an exhausted delivery budget");
35
+ }
36
+ return {
37
+ terminalToolCalled: input.terminalToolCalled,
38
+ rejectedReceipts: input.rejectedReceipts.map(({ reason }) => ({ reason })),
39
+ deliveryTurns: RECEIPT_DELIVERY_TURN_LIMIT,
40
+ sessionCompletion: "settled-without-accepted-receipt",
41
+ runPointer: input.runPointer,
42
+ attemptPointer: input.attemptPointer,
43
+ acceptedReceipt: false,
44
+ };
45
+ }
46
+ export function createReceiptDeliveryPolicy() {
47
+ let accepted = false;
48
+ let terminalToolCalled = false;
49
+ let deliveryTurns = 0;
50
+ const rejectedReceipts = [];
51
+ return {
52
+ recordAccepted() { accepted = true; terminalToolCalled = true; },
53
+ /** Infrastructure owns terminality and must never trigger receipt催交. */
54
+ stopForInfrastructure() { accepted = true; },
55
+ recordRejected(reason) {
56
+ terminalToolCalled = true;
57
+ rejectedReceipts.push({ reason });
58
+ deliveryTurns = Math.min(RECEIPT_DELIVERY_TURN_LIMIT, deliveryTurns + 1);
59
+ },
60
+ recordDeliveryRequest() {
61
+ deliveryTurns = Math.min(RECEIPT_DELIVERY_TURN_LIMIT, deliveryTurns + 1);
62
+ },
63
+ nextAction() {
64
+ if (accepted)
65
+ return "accepted";
66
+ return deliveryTurns < RECEIPT_DELIVERY_TURN_LIMIT ? "request-delivery" : "no-receipt";
67
+ },
68
+ facts(binding) {
69
+ return noReceiptLifecycleFacts({ terminalToolCalled, rejectedReceipts: [...rejectedReceipts], deliveryTurns, ...binding });
70
+ },
71
+ };
72
+ }
@@ -167,10 +167,13 @@ export async function loadNavigatorWorkContext(
167
167
  // Public ak-role run: admitted request is the typed Navigator work-context source.
168
168
  // Classification failure here stays source=context (distinct from model/session/transport).
169
169
  const publicRunDir = process.env.AK_ROLE_RUN_DIR;
170
+ const currentSessionDir = options.context.sessionManager.getSessionDir();
171
+ const isBoundPublicRun = typeof publicRunDir === "string"
172
+ && publicRunDir.trim() !== ""
173
+ && resolve(currentSessionDir) === resolve(publicRunDir, "session");
170
174
  if (
171
175
  options.role === "judge" &&
172
- typeof publicRunDir === "string" &&
173
- publicRunDir.trim() !== ""
176
+ isBoundPublicRun
174
177
  ) {
175
178
  let admitted;
176
179
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.1792",
3
+ "version": "0.1.1813",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -145,6 +145,10 @@ export function isAuditEscalationResult(
145
145
 
146
146
  export type ComplianceDecisionHandlers<T> = {
147
147
  pass: (usage: Usage | undefined) => T | PromiseLike<T>;
148
+ /** Project the accepted parent candidate beside the typed audit-leg facts. */
149
+ noReceipt?: (
150
+ facts: Extract<ComplianceDecision, { status: "no-receipt" }>,
151
+ ) => T | PromiseLike<T>;
148
152
  revise: (violations: readonly unknown[]) => T | PromiseLike<T>;
149
153
  escalate: (result: AuditEscalationToolResult) => T | PromiseLike<T>;
150
154
  auditIncomplete?: (result: AuditIncompleteToolResult) => T | PromiseLike<T>;
@@ -162,6 +166,13 @@ export async function disposeComplianceDecision<T>(
162
166
  switch (decision.status) {
163
167
  case "pass":
164
168
  return await handlers.pass(decision.usage);
169
+ case "no-receipt":
170
+ // The parent candidate remains accepted, but its parallel audit leg is a
171
+ // public typed fact and must not be collapsed into an ordinary pass.
172
+ if (handlers.noReceipt === undefined) {
173
+ throw new Error("Compliance no-receipt projection handler is unavailable");
174
+ }
175
+ return await handlers.noReceipt(decision);
165
176
  case "revise":
166
177
  return await handlers.revise(decision.violations);
167
178
  case "escalate":
@@ -7,6 +7,7 @@ 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 { parseNoReceiptLifecycleFacts, type NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
10
11
 
11
12
  export type ComplianceCompletion = AuditorCompletion;
12
13
  export type ComplianceArgumentRootType = "null" | "array" | "undefined" | "string" | "number" | "boolean" | "bigint" | "symbol" | "function";
@@ -15,7 +16,8 @@ export type ComplianceAuditObservation =
15
16
  | { kind: "object-status-unreadable"; status: "missing" | "unknown" }
16
17
  | DossierObservation;
17
18
  export type ComplianceAuditIncomplete = { status: "audit-incomplete"; observation: ComplianceAuditObservation; candidate: unknown; usage?: Usage };
18
- export type ComplianceDecision = { status: "pass"; usage?: Usage } | { status: "revise"; violations: readonly unknown[]; usage?: Usage } | { status: "escalate"; conflicts?: unknown; decisionGate?: unknown; usage?: Usage } | ComplianceAuditIncomplete;
19
+ export type ComplianceNoReceipt = NoReceiptLifecycleFacts & { status: "no-receipt" };
20
+ export type ComplianceDecision = { status: "pass"; usage?: Usage } | { status: "revise"; violations: readonly unknown[]; usage?: Usage } | { status: "escalate"; conflicts?: unknown; decisionGate?: unknown; usage?: Usage } | ComplianceNoReceipt | ComplianceAuditIncomplete;
19
21
  export type ComplianceDispatch = { model: Model<Api>; auth: { apiKey?: string; headers?: Record<string, string | null>; env?: Record<string, string> } };
20
22
 
21
23
  /** Zero-projection kickoff — soul already carries dossier-fetch duty; no hand-delivered materials. */
@@ -126,5 +128,9 @@ export async function runComplianceAudit(options: RunComplianceAuditOptions): Pr
126
128
  ...(options.runCompletion === undefined ? {} : { runCompletion: options.runCompletion }),
127
129
  ...(options.signal === undefined ? {} : { signal: options.signal }),
128
130
  });
129
- return readComplianceCandidate(receipt.decision, receipt.response.usage);
131
+ try {
132
+ return { status: "no-receipt", ...parseNoReceiptLifecycleFacts(receipt.decision) };
133
+ } catch {
134
+ return readComplianceCandidate(receipt.decision, receipt.response.usage);
135
+ }
130
136
  }
@@ -12,7 +12,7 @@ export function createDoctorRoleRuntime(pi: ExtensionAPI, dependencies: DoctorRo
12
12
  let activation: { soul: string; patient: DoctorCase; store: DoctorEvidenceStore } | undefined; let registered = false; pi.registerFlag(DOCTOR_CASE_FLAG.name, DOCTOR_CASE_FLAG.definition);
13
13
  return { async activate() { const path = pi.getFlag(DOCTOR_CASE_FLAG.name); if (typeof path !== "string" || !path.trim()) throw new Error("Doctor requires --ak-doctor-case"); const soul = (await dependencies.loadSoul()).trim(); if (!soul) throw new Error("Doctor soul is empty"); const patient = await dependencies.loadCase(path); activation = { soul, patient, store: new DoctorEvidenceStore(patient) };
14
14
  if (!registered) { registered = true; pi.registerTool({ name: DOCTOR_EVIDENCE_TOOL_NAME, label: "Doctor Evidence", description: "Read retained Pi session bytes with bounded pagination.", parameters: doctorEvidenceReadSchema, async execute(_id, params: { evidenceId: string; offset?: number; limit?: number }) { if (!activation) throw new Error("Doctor is not activated"); const details = activation.store.read(params.evidenceId, params.offset, params.limit); return { content: [{ type: "text" as const, text: JSON.stringify(details) }], details }; } });
15
- pi.registerTool({ name: DOCTOR_OUTPUT_TOOL_NAME, label: "Doctor Output", description: DOCTOR_OUTPUT_TOOL_DESCRIPTION, parameters: doctorSubmissionSchema, async execute(id, params, signal, _update, ctx): Promise<AgentToolResult<unknown>> { if (!activation) throw new Error("Doctor is not activated"); singleton(id, ctx); const testimony = validateDoctorOutput(params, activation.patient, activation.store); try { appendActiveSessionCustomEntry(ctx, DOCTOR_CANDIDATE_ENTRY_TYPE, { version: 1, testimony, readRecord: activation.store.readRecord(), patientIdentity: activation.patient.identity }, { unavailable: "doctor candidate retention is unavailable", failed: "doctor candidate retention failed" }); } catch (error) { host.failInfrastructure(error, ctx, id); } let audit: ComplianceDecision; try { audit = await dependencies.auditCompliance(signal === undefined ? { context: ctx } : { context: ctx, signal }); } catch (error) { host.failInfrastructure(error, ctx, id); } const details = testimony.status === "completed" ? { ...testimony, cost: activation.patient.cost } : testimony; return disposeComplianceDecision<AgentToolResult<unknown>>(audit, { pass: (usage) => ({ content: [{ type: "text" as const, text: "Doctor output accepted" }], details, terminate: true as const, ...(usage === undefined ? {} : { usage }) }), revise: (violations) => { throw new Error(`Doctor output violates its soul: ${violations.join("; ")}`); }, escalate: (result) => result, auditIncomplete: (result) => result }, details); } });
15
+ pi.registerTool({ name: DOCTOR_OUTPUT_TOOL_NAME, label: "Doctor Output", description: DOCTOR_OUTPUT_TOOL_DESCRIPTION, parameters: doctorSubmissionSchema, async execute(id, params, signal, _update, ctx): Promise<AgentToolResult<unknown>> { if (!activation) throw new Error("Doctor is not activated"); singleton(id, ctx); const testimony = validateDoctorOutput(params, activation.patient, activation.store); try { appendActiveSessionCustomEntry(ctx, DOCTOR_CANDIDATE_ENTRY_TYPE, { version: 1, testimony, readRecord: activation.store.readRecord(), patientIdentity: activation.patient.identity }, { unavailable: "doctor candidate retention is unavailable", failed: "doctor candidate retention failed" }); } catch (error) { host.failInfrastructure(error, ctx, id); } let audit: ComplianceDecision; try { audit = await dependencies.auditCompliance(signal === undefined ? { context: ctx } : { context: ctx, signal }); } catch (error) { host.failInfrastructure(error, ctx, id); } const details = testimony.status === "completed" ? { ...testimony, cost: activation.patient.cost } : testimony; return disposeComplianceDecision<AgentToolResult<unknown>>(audit, { pass: (usage) => ({ content: [{ type: "text" as const, text: "Doctor output accepted" }], details, terminate: true as const, ...(usage === undefined ? {} : { usage }) }), noReceipt: (auditNoReceipt) => ({ content: [{ type: "text" as const, text: "Doctor output accepted; compliance audit produced no receipt" }], details: { ...details, auditNoReceipt }, terminate: true as const }), revise: (violations) => { throw new Error(`Doctor output violates its soul: ${violations.join("; ")}`); }, escalate: (result) => result, auditIncomplete: (result) => result }, details); } });
16
16
  pi.on("before_agent_start", (event) => { if (!activation) throw new Error("Doctor is not activated"); const catalog = { version: activation.patient.version, identity: activation.patient.identity, admittedMetrics: { provenance: "runtime-derived from retained session bytes and sealed into the accepted receipt", cost: activation.patient.cost }, lawfulTargetKeys: ["case", ...activation.patient.cost.invocations.sources], evidence: activation.patient.evidence.map(({ id, kind, sha256, byteLength, contentLength }) => ({ id, kind, sha256, byteLength, contentLength })) }; return { systemPrompt: `${event.systemPrompt}\n\n<doctor_soul>\n${activation.soul}\n</doctor_soul>\n\n<doctor_case>\n${JSON.stringify(catalog)}\n</doctor_case>` }; }); }
17
17
  const required = [DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME]; const names = pi.getAllTools().map((tool) => tool.name); for (const name of required) if (names.filter((item) => item === name).length !== 1) throw new Error(`Doctor required tool collision or missing: ${name}`); pi.setActiveTools(required); const active = pi.getActiveTools?.() ?? required; if (active.length !== 2 || !required.every((name) => active.includes(name))) throw new Error("Doctor active tool narrowing failed"); } };
18
18
  }