@akagilnc/pi-workflow-roles 0.1.5245 → 0.1.5252

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.
@@ -10894,11 +10894,6 @@ async function appendRunAttemptHistory(source, outcome) {
10894
10894
  } catch {
10895
10895
  }
10896
10896
  }
10897
- function navigatorAttendanceCorrelatedWithBoundMarker(details, attendanceIndex, terminalIndex, marker) {
10898
- if (attendanceIndex <= terminalIndex) return false;
10899
- if (details.invocationId !== marker.invocationId) return false;
10900
- return true;
10901
- }
10902
10897
  function parseNavigatorAttendanceDetails(details) {
10903
10898
  const disposition = details.disposition;
10904
10899
  const advisoryDiagnostic = typeof details.routePlaybookReadFailure === "string" ? { advisoryDiagnostic: details.routePlaybookReadFailure } : {};
@@ -11049,54 +11044,12 @@ function extractNavigatorAttendanceFact(entries) {
11049
11044
  reason: "Navigator attendance has no durable packaged role terminal"
11050
11045
  };
11051
11046
  }
11052
- let markerIndex = -1;
11053
- for (let i = terminal.index - 1; i >= 0; i -= 1) {
11047
+ for (let i = terminal.index; i < entries.length; i += 1) {
11054
11048
  const entry = entries[i];
11055
- if (entry?.type === "custom" && entry.customType === NAVIGATOR_INVOCATION_ENTRY) {
11056
- markerIndex = i;
11057
- break;
11058
- }
11059
- }
11060
- if (markerIndex < 0) {
11061
- return {
11062
- disposition: "unavailable",
11063
- source: "unknown",
11064
- reason: "Navigator attendance is uncorrelated with session invocation facts"
11065
- };
11066
- }
11067
- const marker = parseInvocationMarkerIdentity(entries[markerIndex]?.data);
11068
- if (marker === void 0) {
11069
- return {
11070
- disposition: "unavailable",
11071
- source: "unknown",
11072
- reason: "Navigator attendance is uncorrelated with session invocation facts"
11073
- };
11074
- }
11075
- for (let i = entries.length - 1; i >= 0; i -= 1) {
11076
- const entry = entries[i];
11077
- if (entry?.type === "custom_message" && entry.customType === "ak-navigator-attendance") {
11078
- const details = entry.message?.details ?? entry.details;
11079
- if (!isRecord10(details)) {
11080
- return {
11081
- disposition: "unavailable",
11082
- source: "unknown",
11083
- reason: "Navigator attendance is unparseable"
11084
- };
11085
- }
11086
- if (!navigatorAttendanceCorrelatedWithBoundMarker(
11087
- details,
11088
- i,
11089
- terminal.index,
11090
- marker
11091
- )) {
11092
- return {
11093
- disposition: "unavailable",
11094
- source: "unknown",
11095
- reason: "Navigator attendance is uncorrelated with session invocation facts"
11096
- };
11097
- }
11098
- return parseNavigatorAttendanceDetails(details);
11099
- }
11049
+ if (entry?.type !== "custom" || entry.customType !== "ak-role-submission-closure") continue;
11050
+ const details = isRecord10(entry.data) ? entry.data.navigator : void 0;
11051
+ if (!isRecord10(details)) break;
11052
+ return parseNavigatorAttendanceDetails(details);
11100
11053
  }
11101
11054
  return {
11102
11055
  disposition: "unavailable",
@@ -24028,8 +23981,15 @@ async function projectClosedSubmissionLifecycle(closed, context, phase, recordAc
24028
23981
  isError: false,
24029
23982
  details: closed.accepted
24030
23983
  };
24031
- context.sessionManager.appendCustomEntry?.("ak-role-submission-closure", closure);
24032
- await settle(publicNavigatorSettlement(closed.role, phase, closure));
23984
+ let navigator;
23985
+ try {
23986
+ navigator = await settle(publicNavigatorSettlement(closed.role, phase, closure));
23987
+ } finally {
23988
+ context.sessionManager.appendCustomEntry?.("ak-role-submission-closure", {
23989
+ ...closure,
23990
+ ...navigator === void 0 ? {} : { navigator }
23991
+ });
23992
+ }
24033
23993
  }
24034
23994
  function createFiledOfficerRuntime(roleHost, spec, dependencies) {
24035
23995
  let soul;
@@ -24388,6 +24348,8 @@ function createRoleRuntimeExtension(dependencies) {
24388
24348
  let activeReviewerParent;
24389
24349
  let navigatorAttendance;
24390
24350
  let pendingNavigatorPresentation;
24351
+ let navigatorActivation = 0;
24352
+ let navigatorDeliveryClosed = false;
24391
24353
  let pendingNavigatorSettlement;
24392
24354
  let navigatorWorkContext;
24393
24355
  let navigatorSessionParent;
@@ -24440,6 +24402,7 @@ function createRoleRuntimeExtension(dependencies) {
24440
24402
  void settlePromise.catch(() => void 0);
24441
24403
  const raced = await raceNavigatorGrace(settlePromise, NAVIGATOR_POST_ROLE_GRACE_MS);
24442
24404
  if (raced.status !== "timeout") return;
24405
+ navigatorDeliveryClosed = true;
24443
24406
  if (pendingNavigatorPresentation === void 0) {
24444
24407
  const report = {
24445
24408
  disposition: "unavailable",
@@ -24464,6 +24427,7 @@ function createRoleRuntimeExtension(dependencies) {
24464
24427
  })();
24465
24428
  pendingNavigatorSettlement = pending;
24466
24429
  await pending;
24430
+ return pendingNavigatorPresentation?.event;
24467
24431
  };
24468
24432
  projectClosedSubmission = async (closed, context) => projectClosedSubmissionLifecycle(
24469
24433
  closed,
@@ -24943,6 +24907,8 @@ function createRoleRuntimeExtension(dependencies) {
24943
24907
  receiptDelivery = createReceiptDeliveryPolicy();
24944
24908
  noReceiptRecorded = false;
24945
24909
  pendingNavigatorPresentation = void 0;
24910
+ navigatorActivation += 1;
24911
+ navigatorDeliveryClosed = false;
24946
24912
  pendingNavigatorSettlement = void 0;
24947
24913
  pendingInfrastructureFailures.clear();
24948
24914
  pendingSubmissionNonPassByToolCallId.clear();
@@ -25038,6 +25004,7 @@ function createRoleRuntimeExtension(dependencies) {
25038
25004
  } catch {
25039
25005
  }
25040
25006
  }
25007
+ const activation = navigatorActivation;
25041
25008
  navigatorAttendance = await dependencies.createNavigatorAttendance({
25042
25009
  context: ctx,
25043
25010
  role: entry.role,
@@ -25048,7 +25015,9 @@ function createRoleRuntimeExtension(dependencies) {
25048
25015
  invocationId,
25049
25016
  ...contextError === void 0 ? {} : { contextError },
25050
25017
  onEvent: (navigatorEvent, report) => {
25051
- pendingNavigatorPresentation = { event: navigatorEvent, report };
25018
+ if (activation === navigatorActivation && !navigatorDeliveryClosed) {
25019
+ pendingNavigatorPresentation = { event: navigatorEvent, report };
25020
+ }
25052
25021
  }
25053
25022
  });
25054
25023
  if (navigatorWorkContext.contextError === void 0 && navigatorWorkContext.subjectProvenance !== "placeholder") {
@@ -11314,11 +11314,6 @@ async function appendRunAttemptHistory(source, outcome) {
11314
11314
  } catch {
11315
11315
  }
11316
11316
  }
11317
- function navigatorAttendanceCorrelatedWithBoundMarker(details, attendanceIndex, terminalIndex, marker) {
11318
- if (attendanceIndex <= terminalIndex) return false;
11319
- if (details.invocationId !== marker.invocationId) return false;
11320
- return true;
11321
- }
11322
11317
  function parseNavigatorAttendanceDetails(details) {
11323
11318
  const disposition = details.disposition;
11324
11319
  const advisoryDiagnostic = typeof details.routePlaybookReadFailure === "string" ? { advisoryDiagnostic: details.routePlaybookReadFailure } : {};
@@ -11469,54 +11464,12 @@ function extractNavigatorAttendanceFact(entries) {
11469
11464
  reason: "Navigator attendance has no durable packaged role terminal"
11470
11465
  };
11471
11466
  }
11472
- let markerIndex = -1;
11473
- for (let i = terminal.index - 1; i >= 0; i -= 1) {
11467
+ for (let i = terminal.index; i < entries.length; i += 1) {
11474
11468
  const entry = entries[i];
11475
- if (entry?.type === "custom" && entry.customType === NAVIGATOR_INVOCATION_ENTRY) {
11476
- markerIndex = i;
11477
- break;
11478
- }
11479
- }
11480
- if (markerIndex < 0) {
11481
- return {
11482
- disposition: "unavailable",
11483
- source: "unknown",
11484
- reason: "Navigator attendance is uncorrelated with session invocation facts"
11485
- };
11486
- }
11487
- const marker = parseInvocationMarkerIdentity(entries[markerIndex]?.data);
11488
- if (marker === void 0) {
11489
- return {
11490
- disposition: "unavailable",
11491
- source: "unknown",
11492
- reason: "Navigator attendance is uncorrelated with session invocation facts"
11493
- };
11494
- }
11495
- for (let i = entries.length - 1; i >= 0; i -= 1) {
11496
- const entry = entries[i];
11497
- if (entry?.type === "custom_message" && entry.customType === "ak-navigator-attendance") {
11498
- const details = entry.message?.details ?? entry.details;
11499
- if (!isRecord10(details)) {
11500
- return {
11501
- disposition: "unavailable",
11502
- source: "unknown",
11503
- reason: "Navigator attendance is unparseable"
11504
- };
11505
- }
11506
- if (!navigatorAttendanceCorrelatedWithBoundMarker(
11507
- details,
11508
- i,
11509
- terminal.index,
11510
- marker
11511
- )) {
11512
- return {
11513
- disposition: "unavailable",
11514
- source: "unknown",
11515
- reason: "Navigator attendance is uncorrelated with session invocation facts"
11516
- };
11517
- }
11518
- return parseNavigatorAttendanceDetails(details);
11519
- }
11469
+ if (entry?.type !== "custom" || entry.customType !== "ak-role-submission-closure") continue;
11470
+ const details = isRecord10(entry.data) ? entry.data.navigator : void 0;
11471
+ if (!isRecord10(details)) break;
11472
+ return parseNavigatorAttendanceDetails(details);
11520
11473
  }
11521
11474
  return {
11522
11475
  disposition: "unavailable",
@@ -24121,8 +24074,15 @@ async function projectClosedSubmissionLifecycle(closed, context, phase, recordAc
24121
24074
  isError: false,
24122
24075
  details: closed.accepted
24123
24076
  };
24124
- context.sessionManager.appendCustomEntry?.("ak-role-submission-closure", closure);
24125
- await settle(publicNavigatorSettlement(closed.role, phase, closure));
24077
+ let navigator;
24078
+ try {
24079
+ navigator = await settle(publicNavigatorSettlement(closed.role, phase, closure));
24080
+ } finally {
24081
+ context.sessionManager.appendCustomEntry?.("ak-role-submission-closure", {
24082
+ ...closure,
24083
+ ...navigator === void 0 ? {} : { navigator }
24084
+ });
24085
+ }
24126
24086
  }
24127
24087
  function createFiledOfficerRuntime(roleHost, spec, dependencies) {
24128
24088
  let soul;
@@ -24481,6 +24441,8 @@ function createRoleRuntimeExtension(dependencies) {
24481
24441
  let activeReviewerParent;
24482
24442
  let navigatorAttendance;
24483
24443
  let pendingNavigatorPresentation;
24444
+ let navigatorActivation = 0;
24445
+ let navigatorDeliveryClosed = false;
24484
24446
  let pendingNavigatorSettlement;
24485
24447
  let navigatorWorkContext;
24486
24448
  let navigatorSessionParent;
@@ -24533,6 +24495,7 @@ function createRoleRuntimeExtension(dependencies) {
24533
24495
  void settlePromise.catch(() => void 0);
24534
24496
  const raced = await raceNavigatorGrace(settlePromise, NAVIGATOR_POST_ROLE_GRACE_MS);
24535
24497
  if (raced.status !== "timeout") return;
24498
+ navigatorDeliveryClosed = true;
24536
24499
  if (pendingNavigatorPresentation === void 0) {
24537
24500
  const report = {
24538
24501
  disposition: "unavailable",
@@ -24557,6 +24520,7 @@ function createRoleRuntimeExtension(dependencies) {
24557
24520
  })();
24558
24521
  pendingNavigatorSettlement = pending;
24559
24522
  await pending;
24523
+ return pendingNavigatorPresentation?.event;
24560
24524
  };
24561
24525
  projectClosedSubmission = async (closed, context) => projectClosedSubmissionLifecycle(
24562
24526
  closed,
@@ -25036,6 +25000,8 @@ function createRoleRuntimeExtension(dependencies) {
25036
25000
  receiptDelivery = createReceiptDeliveryPolicy();
25037
25001
  noReceiptRecorded = false;
25038
25002
  pendingNavigatorPresentation = void 0;
25003
+ navigatorActivation += 1;
25004
+ navigatorDeliveryClosed = false;
25039
25005
  pendingNavigatorSettlement = void 0;
25040
25006
  pendingInfrastructureFailures.clear();
25041
25007
  pendingSubmissionNonPassByToolCallId.clear();
@@ -25131,6 +25097,7 @@ function createRoleRuntimeExtension(dependencies) {
25131
25097
  } catch {
25132
25098
  }
25133
25099
  }
25100
+ const activation = navigatorActivation;
25134
25101
  navigatorAttendance = await dependencies.createNavigatorAttendance({
25135
25102
  context: ctx,
25136
25103
  role: entry.role,
@@ -25141,7 +25108,9 @@ function createRoleRuntimeExtension(dependencies) {
25141
25108
  invocationId,
25142
25109
  ...contextError === void 0 ? {} : { contextError },
25143
25110
  onEvent: (navigatorEvent, report) => {
25144
- pendingNavigatorPresentation = { event: navigatorEvent, report };
25111
+ if (activation === navigatorActivation && !navigatorDeliveryClosed) {
25112
+ pendingNavigatorPresentation = { event: navigatorEvent, report };
25113
+ }
25145
25114
  }
25146
25115
  });
25147
25116
  if (navigatorWorkContext.contextError === void 0 && navigatorWorkContext.subjectProvenance !== "placeholder") {
@@ -146,47 +146,6 @@ function findLatestDurablePackagedRoleTerminal(entries) {
146
146
  }
147
147
  return void 0;
148
148
  }
149
- function bindCurrentDurableTerminalToMarker(entries) {
150
- const terminal = findLatestDurablePackagedRoleTerminal(entries);
151
- if (terminal === void 0) return { kind: "absent" };
152
- let markerIndex = -1;
153
- for (let i = terminal.index - 1; i >= 0; i -= 1) {
154
- if (isInvocationMarkerEntry(entries[i])) {
155
- markerIndex = i;
156
- break;
157
- }
158
- }
159
- if (markerIndex < 0) {
160
- return { kind: "unbound", terminal };
161
- }
162
- const marker = parseInvocationMarkerIdentity(entries[markerIndex]?.data);
163
- if (marker === void 0) {
164
- return { kind: "unbound", terminal };
165
- }
166
- let windowEnd = entries.length;
167
- for (let i = markerIndex + 1; i < entries.length; i += 1) {
168
- if (isInvocationMarkerEntry(entries[i])) {
169
- windowEnd = i;
170
- break;
171
- }
172
- }
173
- let durableCount = 0;
174
- for (let i = markerIndex + 1; i < windowEnd; i += 1) {
175
- if (durableTerminalAt(entries, i) !== void 0) durableCount += 1;
176
- }
177
- if (durableCount !== 1) return { kind: "ambiguous" };
178
- if (terminal.index <= markerIndex || terminal.index >= windowEnd) {
179
- return { kind: "ambiguous" };
180
- }
181
- return {
182
- kind: "bound",
183
- terminal,
184
- marker: { ...marker, index: markerIndex }
185
- };
186
- }
187
- function isReceiptSettlementBindingClear(entries) {
188
- return bindCurrentDurableTerminalToMarker(entries).kind !== "ambiguous";
189
- }
190
149
  function resolveLifecycleInvocationPrincipal(entries, expected) {
191
150
  const markerIndex = latestInvocationMarkerIndex(entries);
192
151
  if (markerIndex < 0) {
@@ -206,35 +165,19 @@ function resolveLifecycleInvocationPrincipal(entries, expected) {
206
165
  }
207
166
  return { invocationId: marker.invocationId, resume: true };
208
167
  }
209
- function currentInvocationPrincipalFromSession(entries, beforeIndex = entries.length) {
210
- return currentInvocationMarkerFromSession(entries, beforeIndex)?.invocationId;
211
- }
212
- function currentInvocationMarkerFromSession(entries, beforeIndex = entries.length) {
213
- const limit = Math.min(Math.max(beforeIndex, 0), entries.length);
214
- for (let i = limit - 1; i >= 0; i -= 1) {
215
- const entry = entries[i];
216
- if (!isInvocationMarkerEntry(entry)) continue;
217
- return parseInvocationMarkerIdentity(entry?.data);
218
- }
219
- return void 0;
220
- }
221
168
  export {
222
169
  NAVIGATOR_INFRASTRUCTURE_FAILURE_EVIDENCE_KEYS,
223
170
  NAVIGATOR_INFRASTRUCTURE_FAILURE_KIND,
224
171
  NAVIGATOR_INVOCATION_ENTRY,
225
172
  NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY,
226
- bindCurrentDurableTerminalToMarker,
227
173
  buildNavigatorInfrastructureFailureFact,
228
174
  classifyPackagedRoleTerminalResult,
229
- currentInvocationMarkerFromSession,
230
- currentInvocationPrincipalFromSession,
231
175
  extractInfrastructureFailureEvidence,
232
176
  findLatestDurablePackagedRoleTerminal,
233
177
  hasNavigatorInfrastructureFailureBase,
234
178
  isAcceptedPackagedRoleTerminalResult,
235
179
  isDurablePackagedRoleTerminalResult,
236
180
  isNavigatorInfrastructureFailureFact,
237
- isReceiptSettlementBindingClear,
238
181
  markerMatchesExpectedIdentity,
239
182
  mintNavigatorInvocationId,
240
183
  parseInvocationMarkerIdentity,
@@ -23840,11 +23840,6 @@ async function appendRunAttemptHistory(source, outcome) {
23840
23840
  } catch {
23841
23841
  }
23842
23842
  }
23843
- function navigatorAttendanceCorrelatedWithBoundMarker(details, attendanceIndex, terminalIndex, marker) {
23844
- if (attendanceIndex <= terminalIndex) return false;
23845
- if (details.invocationId !== marker.invocationId) return false;
23846
- return true;
23847
- }
23848
23843
  function parseNavigatorAttendanceDetails(details) {
23849
23844
  const disposition = details.disposition;
23850
23845
  const advisoryDiagnostic = typeof details.routePlaybookReadFailure === "string" ? { advisoryDiagnostic: details.routePlaybookReadFailure } : {};
@@ -23995,54 +23990,12 @@ function extractNavigatorAttendanceFact(entries) {
23995
23990
  reason: "Navigator attendance has no durable packaged role terminal"
23996
23991
  };
23997
23992
  }
23998
- let markerIndex = -1;
23999
- for (let i = terminal.index - 1; i >= 0; i -= 1) {
24000
- const entry = entries[i];
24001
- if (entry?.type === "custom" && entry.customType === NAVIGATOR_INVOCATION_ENTRY) {
24002
- markerIndex = i;
24003
- break;
24004
- }
24005
- }
24006
- if (markerIndex < 0) {
24007
- return {
24008
- disposition: "unavailable",
24009
- source: "unknown",
24010
- reason: "Navigator attendance is uncorrelated with session invocation facts"
24011
- };
24012
- }
24013
- const marker = parseInvocationMarkerIdentity(entries[markerIndex]?.data);
24014
- if (marker === void 0) {
24015
- return {
24016
- disposition: "unavailable",
24017
- source: "unknown",
24018
- reason: "Navigator attendance is uncorrelated with session invocation facts"
24019
- };
24020
- }
24021
- for (let i = entries.length - 1; i >= 0; i -= 1) {
23993
+ for (let i = terminal.index; i < entries.length; i += 1) {
24022
23994
  const entry = entries[i];
24023
- if (entry?.type === "custom_message" && entry.customType === "ak-navigator-attendance") {
24024
- const details = entry.message?.details ?? entry.details;
24025
- if (!isRecord8(details)) {
24026
- return {
24027
- disposition: "unavailable",
24028
- source: "unknown",
24029
- reason: "Navigator attendance is unparseable"
24030
- };
24031
- }
24032
- if (!navigatorAttendanceCorrelatedWithBoundMarker(
24033
- details,
24034
- i,
24035
- terminal.index,
24036
- marker
24037
- )) {
24038
- return {
24039
- disposition: "unavailable",
24040
- source: "unknown",
24041
- reason: "Navigator attendance is uncorrelated with session invocation facts"
24042
- };
24043
- }
24044
- return parseNavigatorAttendanceDetails(details);
24045
- }
23995
+ if (entry?.type !== "custom" || entry.customType !== "ak-role-submission-closure") continue;
23996
+ const details = isRecord8(entry.data) ? entry.data.navigator : void 0;
23997
+ if (!isRecord8(details)) break;
23998
+ return parseNavigatorAttendanceDetails(details);
24046
23999
  }
24047
24000
  return {
24048
24001
  disposition: "unavailable",
@@ -26,7 +26,7 @@ import { DOCTOR_CANDIDATE_ENTRY_TYPE } from "../dossier-resolution.js";
26
26
  import { REVIEWER_OUTPUT_TOOL_NAME, } from "../package-contracts/reviewer-output.js";
27
27
  import { packagedAuditToolName, packagedDurableOfficerEntry, packagedRoleAcceptedOutputTool, packagedRoleMetadata, packagedSkipsGateOnInfrastructureStage, } from "../packaged-role-registry.js";
28
28
  import { SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY, SECRETARIAT_GATE_OFFICER_ENTRY_TYPE, } from "../secretariat-contracts.js";
29
- import { classifyPackagedRoleTerminalResult, findLatestDurablePackagedRoleTerminal, hasNavigatorInfrastructureFailureBase, isAcceptedPackagedRoleTerminalResult, isReceiptSettlementBindingClear, NAVIGATOR_INVOCATION_ENTRY, NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY, parseInvocationMarkerIdentity, } from "../navigator-invocation-identity.js";
29
+ import { classifyPackagedRoleTerminalResult, findLatestDurablePackagedRoleTerminal, hasNavigatorInfrastructureFailureBase, isAcceptedPackagedRoleTerminalResult, NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY, } from "../navigator-invocation-identity.js";
30
30
  import { NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_TURN_LIMIT, noReceiptLifecycleFacts, parseNoReceiptLifecycleFacts, } from "../receipt-delivery-policy.js";
31
31
  import { roleRunArtifactsDirectory } from "../role-run-placement.js";
32
32
  import { rewriteRunDirectoryPathValue } from "../role-run-relocation.js";
@@ -1353,19 +1353,6 @@ export async function appendRunAttemptHistory(source, outcome) {
1353
1353
  }
1354
1354
  catch { }
1355
1355
  }
1356
- /**
1357
- * Minimal attendance provenance against the bound marker (ADR 0043).
1358
- * Keep only invocationId + post-terminal ordering. Runtime-self-produced
1359
- * role/phase/subject/version are not re-reconciled here (ADR 0042).
1360
- */
1361
- function navigatorAttendanceCorrelatedWithBoundMarker(details, attendanceIndex, terminalIndex, marker) {
1362
- if (attendanceIndex <= terminalIndex)
1363
- return false;
1364
- // Exact current invocation token is the bound marker principal.
1365
- if (details.invocationId !== marker.invocationId)
1366
- return false;
1367
- return true;
1368
- }
1369
1356
  function parseNavigatorAttendanceDetails(details) {
1370
1357
  const disposition = details.disposition;
1371
1358
  const advisoryDiagnostic = typeof details.routePlaybookReadFailure === "string"
@@ -1586,10 +1573,9 @@ export function extractNavigatorFact(entries) {
1586
1573
  return message === undefined ? fact : { ...fact, advisoryDiagnostic: message };
1587
1574
  }
1588
1575
  function extractNavigatorAttendanceFact(entries) {
1589
- // Affirmative attendance only. Missing / uncorrelated / unparseable is never no-advice.
1590
- // Minimal provenance: latest durable terminal + nearest preceding marker +
1591
- // invocationId + post-terminal order. Marker↔terminal cardinality belongs to
1592
- // receipt settlement (isReceiptSettlementBindingClear), not attendance extraction.
1576
+ // Affirmative attendance only. Missing / unparseable is never no-advice.
1577
+ // The shared lifecycle places the settled event on this submission's closure;
1578
+ // an unrelated attendance message cannot supply another call's result.
1593
1579
  const terminal = findLatestDurablePackagedRoleTerminal(entries);
1594
1580
  if (terminal === undefined) {
1595
1581
  return {
@@ -1598,49 +1584,14 @@ function extractNavigatorAttendanceFact(entries) {
1598
1584
  reason: "Navigator attendance has no durable packaged role terminal",
1599
1585
  };
1600
1586
  }
1601
- let markerIndex = -1;
1602
- for (let i = terminal.index - 1; i >= 0; i -= 1) {
1587
+ for (let i = terminal.index; i < entries.length; i += 1) {
1603
1588
  const entry = entries[i];
1604
- if (entry?.type === "custom" && entry.customType === NAVIGATOR_INVOCATION_ENTRY) {
1605
- markerIndex = i;
1589
+ if (entry?.type !== "custom" || entry.customType !== "ak-role-submission-closure")
1590
+ continue;
1591
+ const details = isRecord(entry.data) ? entry.data.navigator : undefined;
1592
+ if (!isRecord(details))
1606
1593
  break;
1607
- }
1608
- }
1609
- if (markerIndex < 0) {
1610
- return {
1611
- disposition: "unavailable",
1612
- source: "unknown",
1613
- reason: "Navigator attendance is uncorrelated with session invocation facts",
1614
- };
1615
- }
1616
- const marker = parseInvocationMarkerIdentity(entries[markerIndex]?.data);
1617
- if (marker === undefined) {
1618
- return {
1619
- disposition: "unavailable",
1620
- source: "unknown",
1621
- reason: "Navigator attendance is uncorrelated with session invocation facts",
1622
- };
1623
- }
1624
- for (let i = entries.length - 1; i >= 0; i -= 1) {
1625
- const entry = entries[i];
1626
- if (entry?.type === "custom_message" && entry.customType === "ak-navigator-attendance") {
1627
- const details = entry.message?.details ?? entry.details;
1628
- if (!isRecord(details)) {
1629
- return {
1630
- disposition: "unavailable",
1631
- source: "unknown",
1632
- reason: "Navigator attendance is unparseable",
1633
- };
1634
- }
1635
- if (!navigatorAttendanceCorrelatedWithBoundMarker(details, i, terminal.index, marker)) {
1636
- return {
1637
- disposition: "unavailable",
1638
- source: "unknown",
1639
- reason: "Navigator attendance is uncorrelated with session invocation facts",
1640
- };
1641
- }
1642
- return parseNavigatorAttendanceDetails(details);
1643
- }
1594
+ return parseNavigatorAttendanceDetails(details);
1644
1595
  }
1645
1596
  // Absence is not successful no-advice — require affirmative typed attendance.
1646
1597
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.5245",
3
+ "version": "0.1.5252",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Exact current-invocation principal for Navigator attendance correlation.
2
+ * Exact current-invocation principal for shared role lifecycle (#1087).
3
3
  * Shared role lifecycle owns one opaque uuidv7 per Role invocation and persists
4
4
  * it on the role session via Pi's guaranteed `pi.appendEntry` boundary.
5
5
  *
@@ -7,21 +7,14 @@
7
7
  * unfinished exact-session resume reuses the latest valid marker only when its
8
8
  * role/phase/subjectKey still match the expected identity; a packaged role
9
9
  * terminal completing that marker starts the next invocation (fresh mint).
10
- * Terminal settlement binds the nearest independent marker strictly before the
11
- * current durable packaged role terminal and compares equality — never attendance
12
- * self-shape, markers after the terminal, or stale older markers behind a
13
- * malformed nearest. Non-UUIDv7 principals are never accepted.
10
+ * Public settlement does not re-correlate attendance against this principal
11
+ * (ADR 0042); lifecycle dispose/grace owns call-boundary isolation (ADR 0084).
14
12
  *
15
13
  * One shared typed terminal classifier owns durable completion for lifecycle,
16
14
  * publicNavigatorSettlement, and every public CLI Receipt extractor:
17
15
  * - accepted/human: isError exactly false and no infrastructure-failure fact
18
16
  * - infrastructure: isError exactly true plus base kind/source/reasonCode identity
19
17
  * - retryable/missing/nonboolean/contradictory/malformed: nonterminal
20
- *
21
- * One truth table also owns marker↔terminal cardinality:
22
- * - a marker binds exactly one durable packaged role terminal
23
- * - multiple durable terminals after the same marker → ambiguous / fail-closed
24
- * - marker role/phase/subjectKey must match the terminal + independent expected
25
18
  */
26
19
 
27
20
  import { LEGACY_REVIEW_OUTPUT_ROLES, PACKAGED_ROLE_REGISTRY } from "./packaged-role-registry.ts";
@@ -147,7 +140,7 @@ export type InvocationMarkerIdentity = {
147
140
  readonly subjectKey: string;
148
141
  };
149
142
 
150
- /** Independently admitted / registry / cwd expected identity for resume and correlation. */
143
+ /** Independently admitted / registry / cwd expected identity for lifecycle resume. */
151
144
  export type ExpectedInvocationIdentity = {
152
145
  readonly role: string;
153
146
  readonly phase?: InvocationPhase;
@@ -342,83 +335,6 @@ export function findLatestDurablePackagedRoleTerminal(
342
335
  return undefined;
343
336
  }
344
337
 
345
- /**
346
- * Truth-table binding of the current durable packaged role terminal to its
347
- * owning invocation marker. Singleton cardinality: multiple durable terminals
348
- * after the same marker are ambiguous and fail closed.
349
- */
350
- export type DurableTerminalMarkerBinding =
351
- | {
352
- readonly kind: "bound";
353
- readonly terminal: DurablePackagedRoleTerminalRef;
354
- readonly marker: InvocationMarkerIdentity & { readonly index: number };
355
- }
356
- | {
357
- readonly kind: "unbound";
358
- readonly terminal: DurablePackagedRoleTerminalRef;
359
- }
360
- | { readonly kind: "absent" }
361
- | { readonly kind: "ambiguous" };
362
-
363
- export function bindCurrentDurableTerminalToMarker(
364
- entries: readonly NavigatorInvocationEntryLike[],
365
- ): DurableTerminalMarkerBinding {
366
- const terminal = findLatestDurablePackagedRoleTerminal(entries);
367
- if (terminal === undefined) return { kind: "absent" };
368
-
369
- let markerIndex = -1;
370
- for (let i = terminal.index - 1; i >= 0; i -= 1) {
371
- if (isInvocationMarkerEntry(entries[i])) {
372
- markerIndex = i;
373
- break;
374
- }
375
- }
376
- if (markerIndex < 0) {
377
- return { kind: "unbound", terminal };
378
- }
379
-
380
- const marker = parseInvocationMarkerIdentity(entries[markerIndex]?.data);
381
- if (marker === undefined) {
382
- // Malformed nearest marker: no stale fallback; terminal exists but is unbound.
383
- return { kind: "unbound", terminal };
384
- }
385
-
386
- let windowEnd = entries.length;
387
- for (let i = markerIndex + 1; i < entries.length; i += 1) {
388
- if (isInvocationMarkerEntry(entries[i])) {
389
- windowEnd = i;
390
- break;
391
- }
392
- }
393
-
394
- let durableCount = 0;
395
- for (let i = markerIndex + 1; i < windowEnd; i += 1) {
396
- if (durableTerminalAt(entries, i) !== undefined) durableCount += 1;
397
- }
398
- if (durableCount !== 1) return { kind: "ambiguous" };
399
- // Current terminal must be the singleton inside this marker window.
400
- if (terminal.index <= markerIndex || terminal.index >= windowEnd) {
401
- return { kind: "ambiguous" };
402
- }
403
-
404
- return {
405
- kind: "bound",
406
- terminal,
407
- marker: { ...marker, index: markerIndex },
408
- };
409
- }
410
-
411
- /**
412
- * Whether public Receipt settlement may proceed for the current durable terminal.
413
- * Only ambiguous multi-terminal bindings fail closed; attendance mismatch does not
414
- * pollute an otherwise legal single Receipt.
415
- */
416
- export function isReceiptSettlementBindingClear(
417
- entries: readonly NavigatorInvocationEntryLike[],
418
- ): boolean {
419
- return bindCurrentDurableTerminalToMarker(entries).kind !== "ambiguous";
420
- }
421
-
422
338
  export type LifecycleInvocationPrincipal = {
423
339
  readonly invocationId: string;
424
340
  /** True when the principal is already on the admitted session (resume; do not re-append). */
@@ -463,36 +379,3 @@ export function resolveLifecycleInvocationPrincipal(
463
379
 
464
380
  return { invocationId: marker.invocationId, resume: true };
465
381
  }
466
-
467
- /**
468
- * Exact current invocation principal already present on the role session.
469
- * `beforeIndex` is the current packaged role terminal index: only the nearest
470
- * marker strictly before that bound is applicable. A malformed nearest marker
471
- * fails closed (undefined) — never falls back to a stale older valid marker.
472
- * Markers at/after the terminal (future prepare/event) are never considered.
473
- * Non-UUIDv7 values are rejected.
474
- */
475
- export function currentInvocationPrincipalFromSession(
476
- entries: readonly NavigatorInvocationEntryLike[],
477
- beforeIndex: number = entries.length,
478
- ): string | undefined {
479
- return currentInvocationMarkerFromSession(entries, beforeIndex)?.invocationId;
480
- }
481
-
482
- /**
483
- * Full marker identity nearest before `beforeIndex` (current durable terminal).
484
- * Malformed nearest fails closed — no stale older fallback.
485
- */
486
- export function currentInvocationMarkerFromSession(
487
- entries: readonly NavigatorInvocationEntryLike[],
488
- beforeIndex: number = entries.length,
489
- ): InvocationMarkerIdentity | undefined {
490
- const limit = Math.min(Math.max(beforeIndex, 0), entries.length);
491
- for (let i = limit - 1; i >= 0; i -= 1) {
492
- const entry = entries[i];
493
- if (!isInvocationMarkerEntry(entry)) continue;
494
- // Nearest marker only — parse full identity or fail closed.
495
- return parseInvocationMarkerIdentity(entry?.data);
496
- }
497
- return undefined;
498
- }
@@ -88,11 +88,7 @@ import {
88
88
  findLatestDurablePackagedRoleTerminal,
89
89
  hasNavigatorInfrastructureFailureBase,
90
90
  isAcceptedPackagedRoleTerminalResult,
91
- isReceiptSettlementBindingClear,
92
- NAVIGATOR_INVOCATION_ENTRY,
93
91
  NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY,
94
- parseInvocationMarkerIdentity,
95
- type InvocationMarkerIdentity,
96
92
  } from "../navigator-invocation-identity.ts";
97
93
  import {
98
94
  NO_RECEIPT_LIFECYCLE_ENTRY_TYPE,
@@ -1921,23 +1917,6 @@ export async function appendRunAttemptHistory(
1921
1917
  } catch {}
1922
1918
  }
1923
1919
 
1924
- /**
1925
- * Minimal attendance provenance against the bound marker (ADR 0043).
1926
- * Keep only invocationId + post-terminal ordering. Runtime-self-produced
1927
- * role/phase/subject/version are not re-reconciled here (ADR 0042).
1928
- */
1929
- function navigatorAttendanceCorrelatedWithBoundMarker(
1930
- details: Record<string, unknown>,
1931
- attendanceIndex: number,
1932
- terminalIndex: number,
1933
- marker: InvocationMarkerIdentity,
1934
- ): boolean {
1935
- if (attendanceIndex <= terminalIndex) return false;
1936
- // Exact current invocation token is the bound marker principal.
1937
- if (details.invocationId !== marker.invocationId) return false;
1938
- return true;
1939
- }
1940
-
1941
1920
  function parseNavigatorAttendanceDetails(
1942
1921
  details: Record<string, unknown>,
1943
1922
  ): TerminalNavigatorFact {
@@ -2216,10 +2195,9 @@ export function extractNavigatorFact(
2216
2195
  function extractNavigatorAttendanceFact(
2217
2196
  entries: readonly SessionEntry[],
2218
2197
  ): TerminalNavigatorFact {
2219
- // Affirmative attendance only. Missing / uncorrelated / unparseable is never no-advice.
2220
- // Minimal provenance: latest durable terminal + nearest preceding marker +
2221
- // invocationId + post-terminal order. Marker↔terminal cardinality belongs to
2222
- // receipt settlement (isReceiptSettlementBindingClear), not attendance extraction.
2198
+ // Affirmative attendance only. Missing / unparseable is never no-advice.
2199
+ // The shared lifecycle places the settled event on this submission's closure;
2200
+ // an unrelated attendance message cannot supply another call's result.
2223
2201
  const terminal = findLatestDurablePackagedRoleTerminal(entries);
2224
2202
  if (terminal === undefined) {
2225
2203
  return {
@@ -2229,57 +2207,12 @@ function extractNavigatorAttendanceFact(
2229
2207
  };
2230
2208
  }
2231
2209
 
2232
- let markerIndex = -1;
2233
- for (let i = terminal.index - 1; i >= 0; i -= 1) {
2234
- const entry = entries[i];
2235
- if (entry?.type === "custom" && entry.customType === NAVIGATOR_INVOCATION_ENTRY) {
2236
- markerIndex = i;
2237
- break;
2238
- }
2239
- }
2240
- if (markerIndex < 0) {
2241
- return {
2242
- disposition: "unavailable",
2243
- source: "unknown",
2244
- reason: "Navigator attendance is uncorrelated with session invocation facts",
2245
- };
2246
- }
2247
- const marker = parseInvocationMarkerIdentity(entries[markerIndex]?.data);
2248
- if (marker === undefined) {
2249
- return {
2250
- disposition: "unavailable",
2251
- source: "unknown",
2252
- reason: "Navigator attendance is uncorrelated with session invocation facts",
2253
- };
2254
- }
2255
-
2256
- for (let i = entries.length - 1; i >= 0; i -= 1) {
2210
+ for (let i = terminal.index; i < entries.length; i += 1) {
2257
2211
  const entry = entries[i];
2258
- if (entry?.type === "custom_message" && entry.customType === "ak-navigator-attendance") {
2259
- const details = entry.message?.details ?? (entry as { details?: unknown }).details;
2260
- if (!isRecord(details)) {
2261
- return {
2262
- disposition: "unavailable",
2263
- source: "unknown",
2264
- reason: "Navigator attendance is unparseable",
2265
- };
2266
- }
2267
- if (
2268
- !navigatorAttendanceCorrelatedWithBoundMarker(
2269
- details,
2270
- i,
2271
- terminal.index,
2272
- marker,
2273
- )
2274
- ) {
2275
- return {
2276
- disposition: "unavailable",
2277
- source: "unknown",
2278
- reason: "Navigator attendance is uncorrelated with session invocation facts",
2279
- };
2280
- }
2281
- return parseNavigatorAttendanceDetails(details);
2282
- }
2212
+ if (entry?.type !== "custom" || entry.customType !== "ak-role-submission-closure") continue;
2213
+ const details = isRecord(entry.data) ? entry.data.navigator : undefined;
2214
+ if (!isRecord(details)) break;
2215
+ return parseNavigatorAttendanceDetails(details);
2283
2216
  }
2284
2217
  // Absence is not successful no-advice — require affirmative typed attendance.
2285
2218
  return {
@@ -523,7 +523,7 @@ export async function projectClosedSubmissionLifecycle(
523
523
  context: HostContext,
524
524
  phase: NavigatorPhase,
525
525
  recordAccepted: () => void,
526
- settle: (settlement: NavigatorSettlement | undefined) => Promise<void>,
526
+ settle: (settlement: NavigatorSettlement | undefined) => Promise<NavigatorEvent | undefined>,
527
527
  ): Promise<void> {
528
528
  recordAccepted();
529
529
  const closure = {
@@ -531,8 +531,17 @@ export async function projectClosedSubmissionLifecycle(
531
531
  isError: false,
532
532
  details: closed.accepted,
533
533
  };
534
- context.sessionManager.appendCustomEntry?.("ak-role-submission-closure", closure);
535
- await settle(publicNavigatorSettlement(closed.role, phase, closure));
534
+ let navigator: NavigatorEvent | undefined;
535
+ try {
536
+ navigator = await settle(publicNavigatorSettlement(closed.role, phase, closure));
537
+ } finally {
538
+ // A Navigator failure cannot erase an already accepted role closure.
539
+ // Missing attendance remains a typed unavailable at public settlement.
540
+ context.sessionManager.appendCustomEntry?.("ak-role-submission-closure", {
541
+ ...closure,
542
+ ...(navigator === undefined ? {} : { navigator }),
543
+ });
544
+ }
536
545
  }
537
546
 
538
547
  /**
@@ -1032,6 +1041,8 @@ export function createRoleRuntimeExtension(
1032
1041
  let navigatorAttendance: NavigatorAttendanceDependency | undefined;
1033
1042
  // #351: session-lifecycle owner for periodic OAuth refresh (orthogonal to role admission).
1034
1043
  let pendingNavigatorPresentation: { event: import("./navigator-attendance.ts").NavigatorEvent; report: import("./navigator-attendance.ts").NavigatorReport } | undefined;
1044
+ let navigatorActivation = 0;
1045
+ let navigatorDeliveryClosed = false;
1035
1046
  let pendingNavigatorSettlement: Promise<void> | undefined;
1036
1047
  let navigatorWorkContext: NavigatorWorkContext | undefined;
1037
1048
  let navigatorSessionParent: string | undefined;
@@ -1085,7 +1096,7 @@ export function createRoleRuntimeExtension(
1085
1096
  }
1086
1097
  void Promise.resolve(pending).then(undefined, recordDisposeFailure);
1087
1098
  };
1088
- const settleNavigatorProjection = async (settlement: NavigatorSettlement | undefined) => {
1099
+ const settleNavigatorProjection = async (settlement: NavigatorSettlement | undefined): Promise<NavigatorEvent | undefined> => {
1089
1100
  const attendance = navigatorAttendance;
1090
1101
  if (settlement === undefined || attendance === undefined) return;
1091
1102
  const workContext = navigatorWorkContext;
@@ -1099,6 +1110,7 @@ export function createRoleRuntimeExtension(
1099
1110
  void settlePromise.catch(() => undefined);
1100
1111
  const raced = await raceNavigatorGrace(settlePromise, NAVIGATOR_POST_ROLE_GRACE_MS);
1101
1112
  if (raced.status !== "timeout") return;
1113
+ navigatorDeliveryClosed = true;
1102
1114
  if (pendingNavigatorPresentation === undefined) {
1103
1115
  const report: NavigatorReport = {
1104
1116
  disposition: "unavailable",
@@ -1124,6 +1136,7 @@ export function createRoleRuntimeExtension(
1124
1136
  })();
1125
1137
  pendingNavigatorSettlement = pending;
1126
1138
  await pending;
1139
+ return pendingNavigatorPresentation?.event;
1127
1140
  };
1128
1141
  projectClosedSubmission = async (closed, context) => projectClosedSubmissionLifecycle(
1129
1142
  closed,
@@ -1695,6 +1708,8 @@ export function createRoleRuntimeExtension(
1695
1708
  receiptDelivery = createReceiptDeliveryPolicy();
1696
1709
  noReceiptRecorded = false;
1697
1710
  pendingNavigatorPresentation = undefined;
1711
+ navigatorActivation += 1;
1712
+ navigatorDeliveryClosed = false;
1698
1713
  pendingNavigatorSettlement = undefined;
1699
1714
  pendingInfrastructureFailures.clear();
1700
1715
  pendingSubmissionNonPassByToolCallId.clear();
@@ -1811,6 +1826,7 @@ export function createRoleRuntimeExtension(
1811
1826
  });
1812
1827
  } catch {}
1813
1828
  }
1829
+ const activation = navigatorActivation;
1814
1830
  navigatorAttendance = await dependencies.createNavigatorAttendance({
1815
1831
  context: ctx,
1816
1832
  role: entry.role,
@@ -1821,7 +1837,9 @@ export function createRoleRuntimeExtension(
1821
1837
  invocationId,
1822
1838
  ...(contextError === undefined ? {} : { contextError }),
1823
1839
  onEvent: (navigatorEvent, report) => {
1824
- pendingNavigatorPresentation = { event: navigatorEvent, report };
1840
+ if (activation === navigatorActivation && !navigatorDeliveryClosed) {
1841
+ pendingNavigatorPresentation = { event: navigatorEvent, report };
1842
+ }
1825
1843
  },
1826
1844
  });
1827
1845
  // Concrete work context starts standby attendance (record only, no model).