@sema-agent/client-core 0.112.1 → 0.113.0

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.
Files changed (178) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +36 -23
  3. package/dist/adapt/arms.js +7 -1
  4. package/dist/adapt/textStream.d.ts +2 -0
  5. package/dist/adapt/textStream.js +27 -1
  6. package/dist/adapter/activeRunSelfHeal.d.ts +7 -5
  7. package/dist/adapter/activeRunSelfHeal.js +38 -66
  8. package/dist/adapter/downstream/eventToSdkMessage.js +4 -0
  9. package/dist/adapter/downstream/terminalToSdkResult.d.ts +8 -0
  10. package/dist/adapter/downstream/terminalToSdkResult.js +36 -1
  11. package/dist/adapter/runStream.d.ts +2 -12
  12. package/dist/adapter/runStream.js +46 -19
  13. package/dist/agentSession/engineAgentRegistryRead.js +2 -1
  14. package/dist/agentSession/engineSessionBackgroundStop.js +2 -2
  15. package/dist/attachmentOutcome.d.ts +35 -0
  16. package/dist/attachmentOutcome.js +192 -0
  17. package/dist/attachmentPathPlan.d.ts +67 -0
  18. package/dist/attachmentPathPlan.js +203 -0
  19. package/dist/attachmentRoute.d.ts +9 -1
  20. package/dist/attachmentRoute.js +23 -2
  21. package/dist/declared/supportFloorDeclared.d.ts +1 -1
  22. package/dist/declared/supportFloorDeclared.js +1 -0
  23. package/dist/deviceRebind.js +2 -3
  24. package/dist/displayUntrusted.js +126 -19
  25. package/dist/engineAddress.d.ts +1 -1
  26. package/dist/engineAddress.js +9 -6
  27. package/dist/engineErrorCodes.d.ts +3 -0
  28. package/dist/engineErrorCodes.js +4 -0
  29. package/dist/engineNoticeCodes.d.ts +34 -4
  30. package/dist/engineNoticeCodes.js +133 -6
  31. package/dist/engineUnavailable.d.ts +4 -2
  32. package/dist/engineUnavailable.js +99 -17
  33. package/dist/fleet/fleetLedger.d.ts +9 -3
  34. package/dist/fleet/fleetLedger.js +112 -16
  35. package/dist/fleet/fleetProjection.d.ts +5 -0
  36. package/dist/fleet/fleetProjection.js +9 -1
  37. package/dist/gateVocabulary.d.ts +7 -0
  38. package/dist/gateVocabulary.js +40 -14
  39. package/dist/generated/engineFactTables.d.ts +4 -2
  40. package/dist/generated/engineFactTables.js +198 -2
  41. package/dist/generated/engineNoticeTables.js +56 -8
  42. package/dist/generated/settingsSchemaFns.d.ts +7 -0
  43. package/dist/generated/settingsSchemaFns.js +11 -0
  44. package/dist/generated/settingsSchemaHash.d.ts +1 -0
  45. package/dist/generated/settingsSchemaHash.js +58 -0
  46. package/dist/generated/settingsSchemaOwnKeyRead.js +6 -1
  47. package/dist/generated/settingsSchemaTables.d.ts +3 -2
  48. package/dist/generated/settingsSchemaTables.js +14 -0
  49. package/dist/generated/settingsSchemaTightenVerdict.d.ts +1 -0
  50. package/dist/generated/settingsSchemaTightenVerdict.js +125 -0
  51. package/dist/headlessPermissionModeWire.d.ts +7 -2
  52. package/dist/headlessPermissionModeWire.js +77 -8
  53. package/dist/hitl/approvalDecisionDoor.d.ts +24 -0
  54. package/dist/hitl/approvalDecisionDoor.js +77 -0
  55. package/dist/hitl/approvalDelivery.d.ts +6 -0
  56. package/dist/hitl/approvalDelivery.js +57 -7
  57. package/dist/hitl/approvalResolution.d.ts +6 -3
  58. package/dist/hitl/approvalResolution.js +24 -2
  59. package/dist/hitl/approvalsFeed.js +1 -1
  60. package/dist/hitl/automaticAnswerVerdict.d.ts +1 -1
  61. package/dist/hitl/decisionSend.d.ts +7 -4
  62. package/dist/hitl/decisionSend.js +90 -41
  63. package/dist/hitl/frameRouter.d.ts +3 -3
  64. package/dist/hitl/frameRouter.js +5 -0
  65. package/dist/hitl/hitlHostSurface.d.ts +3 -2
  66. package/dist/hitl/hitlHostSurface.js +5 -3
  67. package/dist/hitl/legacyApprovalShapes.d.ts +142 -0
  68. package/dist/hitl/legacyApprovalShapes.js +1 -0
  69. package/dist/hitl/livePendingAsk.d.ts +4 -4
  70. package/dist/hitl/livePendingAsk.js +5 -3
  71. package/dist/hitl/parkResolver.js +1 -1
  72. package/dist/hitl/persistedRulesWire.js +2 -2
  73. package/dist/hitl/planReviewCard.d.ts +2 -1
  74. package/dist/hitl/planReviewCard.js +64 -1
  75. package/dist/hitl/planReviewWire.d.ts +30 -0
  76. package/dist/hitl/planReviewWire.js +204 -65
  77. package/dist/hitl/sessionPolicyWire.js +16 -7
  78. package/dist/hitl/subagentSource.d.ts +1 -0
  79. package/dist/hitl/subagentSource.js +11 -0
  80. package/dist/hitl/toolApprovalWire.d.ts +34 -10
  81. package/dist/hitl/toolApprovalWire.js +182 -33
  82. package/dist/hookMatcherVerdict.d.ts +2 -1
  83. package/dist/hookMatcherVerdict.js +3 -2
  84. package/dist/hooksReaderGenerations.d.ts +8 -0
  85. package/dist/hooksReaderGenerations.js +49 -0
  86. package/dist/hooksWireCaps.js +43 -18
  87. package/dist/hostSettingsSource.d.ts +1 -1
  88. package/dist/hostSettingsSource.js +2 -2
  89. package/dist/hostTurnSettings.d.ts +2 -2
  90. package/dist/hostTurnSettings.js +87 -47
  91. package/dist/humanTurn.d.ts +2 -1
  92. package/dist/humanTurn.js +44 -13
  93. package/dist/humanTurnRead.d.ts +7 -0
  94. package/dist/humanTurnRead.js +23 -14
  95. package/dist/humanTurnSentences.js +3 -0
  96. package/dist/i18n/messages.en.d.ts +91 -5
  97. package/dist/i18n/messages.en.js +98 -12
  98. package/dist/i18n/messages.zh.js +108 -15
  99. package/dist/index.d.ts +29 -15
  100. package/dist/index.js +17 -9
  101. package/dist/liveModelCatalog.js +1 -0
  102. package/dist/liveQuestionStore.d.ts +2 -0
  103. package/dist/liveQuestionStore.js +7 -2
  104. package/dist/localConfig/kernel.d.ts +2 -1
  105. package/dist/localConfig/kernel.js +12 -18
  106. package/dist/localConfig/mcp/edit.js +25 -27
  107. package/dist/localConfig/mcp/servers.js +5 -8
  108. package/dist/localConfig/modelPool.d.ts +6 -1
  109. package/dist/localConfig/modelPool.js +104 -27
  110. package/dist/localConfig/modelPoolWords.d.ts +2 -0
  111. package/dist/localConfig/modelPoolWords.js +1 -0
  112. package/dist/localConfig/settingsWrite.js +1 -14
  113. package/dist/localConfig/skills.js +9 -16
  114. package/dist/localEngineSpawn.d.ts +29 -1
  115. package/dist/localEngineSpawn.js +62 -1
  116. package/dist/mcpLiveness.d.ts +1 -1
  117. package/dist/mcpProbeWire.js +2 -2
  118. package/dist/mcpReconnect.js +2 -2
  119. package/dist/model/catalogLoader.js +3 -17
  120. package/dist/model/onboardJudgement.d.ts +8 -0
  121. package/dist/model/onboardJudgement.js +8 -3
  122. package/dist/model/onboardSteps.d.ts +19 -0
  123. package/dist/model/onboardSteps.js +76 -0
  124. package/dist/notifications.js +6 -0
  125. package/dist/ownKey.d.ts +4 -0
  126. package/dist/ownKey.js +61 -0
  127. package/dist/pathShape.d.ts +14 -0
  128. package/dist/pathShape.js +44 -0
  129. package/dist/planReviewCardFacts.d.ts +3 -0
  130. package/dist/planReviewCardFacts.js +20 -3
  131. package/dist/pluginHooksWire.d.ts +20 -8
  132. package/dist/pluginHooksWire.js +204 -86
  133. package/dist/prepareRefusal.d.ts +2 -0
  134. package/dist/prepareRefusal.js +11 -1
  135. package/dist/request/taskRequest.js +3 -1
  136. package/dist/resultFrameKeys.d.ts +1 -0
  137. package/dist/resultFrameKeys.js +3 -0
  138. package/dist/resumeRefusalCopy.js +3 -1
  139. package/dist/retryStatus.d.ts +11 -1
  140. package/dist/retryStatus.js +15 -4
  141. package/dist/runTerminal.d.ts +1 -0
  142. package/dist/runTerminal.js +15 -1
  143. package/dist/sdkWireTransit.d.ts +1 -1
  144. package/dist/seatContract.d.ts +12 -3
  145. package/dist/seatContract.js +24 -13
  146. package/dist/sessionEngine.js +1 -1
  147. package/dist/sessionHooksRoster.js +8 -6
  148. package/dist/sessionIdSegment.d.ts +0 -1
  149. package/dist/sessionIdSegment.js +1 -3
  150. package/dist/sessionMemoryErase.js +2 -2
  151. package/dist/sessionMemoryStatus.js +2 -1
  152. package/dist/sessionOccupant.d.ts +29 -0
  153. package/dist/sessionOccupant.js +78 -0
  154. package/dist/settingsHooksSalvage.d.ts +4 -1
  155. package/dist/settingsHooksSalvage.js +22 -9
  156. package/dist/skillOverrideChain.d.ts +3 -0
  157. package/dist/skillOverrideChain.js +31 -4
  158. package/dist/skillsWireCaps.d.ts +16 -2
  159. package/dist/skillsWireCaps.js +47 -20
  160. package/dist/subagent/agentStateDetail.js +32 -8
  161. package/dist/subagent/engineCompactWire.js +48 -36
  162. package/dist/subagent/engineDelegatedPrompt.d.ts +3 -2
  163. package/dist/subagent/engineDelegatedPrompt.js +65 -28
  164. package/dist/subagentEditOutcomes.d.ts +9 -0
  165. package/dist/subagentEditOutcomes.js +43 -0
  166. package/dist/supportFloor.d.ts +2 -1
  167. package/dist/supportFloor.js +1 -1
  168. package/dist/toolResult.js +4 -2
  169. package/dist/toolRoster.d.ts +1 -0
  170. package/dist/toolRoster.js +1 -0
  171. package/dist/usageAnalytics.js +2 -1
  172. package/dist/userAttachmentEnvelope.d.ts +11 -0
  173. package/dist/userAttachmentEnvelope.js +7 -2
  174. package/dist/userAttachmentSeat.d.ts +7 -1
  175. package/dist/userAttachmentSeat.js +44 -7
  176. package/dist/wiringInstructionsAttribution.js +8 -2
  177. package/docs/INTEGRATION-CLIENTS.md +1588 -16
  178. package/package.json +5 -5
@@ -1,5 +1,6 @@
1
- import { publishQuestionFrame, publishQuestionFrameFor, registerLocalQuestionResponder, unregisterLocalQuestionResponder, hasLocalQuestionResponder, hasQuestionOverlayFor, } from '../liveQuestionStore.js';
1
+ import { deliverQuestionFrameFor, publishQuestionFrame, publishQuestionFrameFor, registerLocalQuestionResponder, unregisterLocalQuestionResponder, hasLocalQuestionResponder, hasQuestionOverlayFor, } from '../liveQuestionStore.js';
2
2
  import { hostLog } from '../host.js';
3
+ import { displayUntrusted } from '../displayUntrusted.js';
3
4
  import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
4
5
  import { abortableSleep, settleWithinDeadline } from '../abortableSleep.js';
5
6
  import { clientHasVerbs, engineWireFor, noAnswerKindOf, sendOnce } from '../engineWireFor.js';
@@ -8,15 +9,16 @@ import { enqueuePlanReviewOutcome } from '../notifications.js';
8
9
  import { corruptStoredRowContent, corruptStoredRowWhere } from '../wireErrorTriage.js';
9
10
  import { decideFailureNote } from '../decideFailureNote.js';
10
11
  import { readThrownExtra, wireFailureTextsOf } from '../wireFailureShape.js';
11
- import { appendOwnElement, readOwnDataNonEmptyString, readOwnDataValue } from '../ownKey.js';
12
+ import { appendOwnElement, guardedRead, hostPresenceOf, readOwnDataNonEmptyString, readOwnDataValue } from '../ownKey.js';
12
13
  import { checkpointUnsupportedVersionContent, checkpointUnsupportedVersionFromError, resumeContextUnavailableContent, resumeContextUnavailableFromError } from '../resumeRefusalCopy.js';
13
14
  import { APPROVAL_STALE, REQUEST_FIELD_CONFLICT } from '../engineErrorCodes.js';
14
- import { refusalDeliveryOf } from './decisionRefusal.js';
15
+ import { readDecisionRefusalReading, refusalDeliveryOf } from './decisionRefusal.js';
16
+ import { decisionRefusalWayOutContent } from '../checkpointRowVerdict.js';
15
17
  import { directFailureVerdictOf, directSafetyStopOf } from './approvalResolution.js';
16
18
  import { __resetDecisionLedgerForTests, decisionInFlightOf, decisionKeyInFlight, decisionLedgerKeysOf, decisionOpsWithKey, decisionReadMark, dropDecisionOp, latestDecisionHandedMark, markLatestDecisionNotApplied, openDecisionOp, planDecisionKey, readMarkOf, settleDecisionOp, settleDecisionOpsOnGateLeft, } from './decisionLedger.js';
17
19
  import { denyReasonForWire, isPlanReviewModeAfter } from './hitlBridge.js';
18
20
  import { freshPlanReviewQuestionId, planReviewQuestionId, REOPEN_ID_TAIL, taskIdFromPlanReviewQuestionId } from './gateIdentity.js';
19
- import { isPlanReviewPark, mintPlanReviewCardFacts, notePlanReviewCardIssued, pausedCheckpointIdOf, planReviewCardFactsFrom, planReviewFactsCurrent, registeredPlanReviewCardFacts, samePlanReviewCardFacts, unknownBody, _resetPlanReviewRunsForTest, } from '../planReviewCardFacts.js';
21
+ import { isPlanReviewPark, mintPlanReviewCardFacts, noteCardGateHandedOver, notePlanReviewCardIssued, pausedCheckpointIdOf, planReviewCardFactsFrom, planReviewCardReplacesEarlier, planReviewFactsCurrent, registeredPlanReviewCardFacts, samePlanReviewCardFacts, unknownBody, _resetPlanReviewRunsForTest, } from '../planReviewCardFacts.js';
20
22
  import { planReviewAskAgainKey, planReviewCardNoteRef, planReviewDecisionReasonRef, planReviewUnreadableNoteRef, takePlanReviewFacts, _resetPlanReviewFactsFlightsForTest, } from './planReviewCard.js';
21
23
  import { planReviewOutcomeCauseOf } from '../planReviewOutcomeCause.js';
22
24
  import { EN_MESSAGES } from '../i18n/messages.en.js';
@@ -133,6 +135,14 @@ function cardShapeOf(facts, submittedInPlanMode) {
133
135
  }
134
136
  return submittedInPlanMode ? 'three_choice' : 'two_choice';
135
137
  }
138
+ const PLAN_REVIEW_HEADING_GATE_MAX = 120;
139
+ function planReviewHeadingOf(facts, replaces, lang) {
140
+ const gate = facts?.checkpointId === undefined ? '' : displayUntrusted(facts.checkpointId, { max: PLAN_REVIEW_HEADING_GATE_MAX });
141
+ if (gate === '')
142
+ return renderMessage(msg('approval.planReview.heading.plain'), lang);
143
+ const head = msg('approval.planReview.heading.gate', { gate });
144
+ return renderMessage(replaces ? msg('approval.planReview.heading.replaces', { head }) : head, lang);
145
+ }
136
146
  export function planReviewCardViewOf(frame, lang) {
137
147
  try {
138
148
  const questionId = readOwnDataNonEmptyString(frame ?? undefined, 'questionId');
@@ -141,14 +151,20 @@ export function planReviewCardViewOf(frame, lang) {
141
151
  return undefined;
142
152
  const facts = planReviewCardFactsFrom(readOwnDataValue(frame ?? undefined, '_sema_planReview'));
143
153
  if (facts === undefined || facts.taskId !== taskId) {
144
- return Object.freeze({ options: OPTIONS_BY_SHAPE.withheld, note: renderMessage(planReviewUnreadableNoteRef(), lang) });
154
+ return Object.freeze({ options: OPTIONS_BY_SHAPE.withheld, note: renderMessage(planReviewUnreadableNoteRef(), lang), heading: planReviewHeadingOf(undefined, false, lang) });
145
155
  }
146
156
  const shape = cardShapeOf(facts, frame !== null && frame !== undefined && offeredModeFromLabels(frame) === 'three_choice');
147
157
  const noteRef = planReviewCardNoteRef(facts);
148
- return Object.freeze({ facts, options: OPTIONS_BY_SHAPE[shape], ...(noteRef !== undefined ? { note: renderMessage(noteRef, lang) } : {}) });
158
+ const replaces = readOwnDataValue(frame ?? undefined, '_sema_planReviewReplaces') === true;
159
+ return Object.freeze({
160
+ facts,
161
+ options: OPTIONS_BY_SHAPE[shape],
162
+ ...(noteRef !== undefined ? { note: renderMessage(noteRef, lang) } : {}),
163
+ heading: planReviewHeadingOf(facts, replaces, lang),
164
+ });
149
165
  }
150
166
  catch {
151
- return Object.freeze({ options: OPTIONS_BY_SHAPE.withheld, note: renderMessage(planReviewUnreadableNoteRef(), lang) });
167
+ return Object.freeze({ options: OPTIONS_BY_SHAPE.withheld, note: renderMessage(planReviewUnreadableNoteRef(), lang), heading: planReviewHeadingOf(undefined, false, lang) });
152
168
  }
153
169
  }
154
170
  export function planReviewEvidenceMovedOf(shown, latest) {
@@ -305,7 +321,7 @@ function planReviewQuestions(question, shape) {
305
321
  },
306
322
  ];
307
323
  }
308
- function planReviewQuestionFrame(questionId, question, shape, facts) {
324
+ function planReviewQuestionFrame(questionId, question, shape, facts, sessionKey = DEFAULT_SESSION_KEY) {
309
325
  offeredModeByQuestionId.delete(questionId);
310
326
  offeredModeByQuestionId.set(questionId, shape);
311
327
  while (offeredModeByQuestionId.size > OFFERED_MODE_LEDGER_MAX) {
@@ -319,10 +335,17 @@ function planReviewQuestionFrame(questionId, question, shape, facts) {
319
335
  questionId,
320
336
  questions: planReviewQuestions(question, shape),
321
337
  ...(facts !== undefined ? { _sema_planReview: facts } : {}),
338
+ ...(facts !== undefined && planReviewCardReplacesEarlier(sessionKey, facts) ? { _sema_planReviewReplaces: true } : {}),
322
339
  };
323
340
  }
341
+ function deliverPlanReviewCard(sessionKey, frame, facts) {
342
+ if (deliverQuestionFrameFor(sessionKey, frame) !== 'delivered')
343
+ return;
344
+ if (facts !== undefined)
345
+ noteCardGateHandedOver(sessionKey, facts.taskId, facts.checkpointId);
346
+ }
324
347
  function publishArmedPlanReviewCard(inst) {
325
- publishQuestionFrame(planReviewQuestionFrame(inst.questionId, ARM_QUESTION, inst.shape, inst.facts));
348
+ deliverPlanReviewCard(DEFAULT_SESSION_KEY, planReviewQuestionFrame(inst.questionId, ARM_QUESTION, inst.shape, inst.facts, DEFAULT_SESSION_KEY), inst.facts);
326
349
  }
327
350
  function retireArmedPlanReview(taskId, inst) {
328
351
  if (armedPlanReviews.get(taskId) === inst)
@@ -446,9 +469,7 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
446
469
  else if (!engineWireTarget()) {
447
470
  return false;
448
471
  }
449
- const delivery = wire !== undefined || opts?.onOutcome !== undefined
450
- ? { ...(wire !== undefined ? { wire } : {}), ...(opts?.onOutcome !== undefined ? { onOutcome: opts.onOutcome } : {}) }
451
- : undefined;
472
+ const delivery = planReviewDeliveryFrom(wire, opts?.onOutcome, opts?.onAccepted);
452
473
  const { taskId } = result;
453
474
  const gateBefore = pausedCheckpointIdOf(result);
454
475
  if (gateBefore === undefined) {
@@ -464,12 +485,12 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
464
485
  void taking.pending.then((taken) => {
465
486
  try {
466
487
  if (taken !== undefined && planReviewFactsCurrent(taken)) {
467
- presentArmedPlanReview(taken.facts, delivery);
488
+ presentAfterAwait(taken.facts, delivery);
468
489
  return;
469
490
  }
470
491
  const current = registeredPlanReviewCardFacts(DEFAULT_SESSION_KEY, taskId, gateBefore);
471
492
  if (current !== undefined) {
472
- presentArmedPlanReview(current, delivery);
493
+ presentAfterAwait(current, delivery);
473
494
  return;
474
495
  }
475
496
  hostLog('debug', `planReviewWire: arm-superseded task=${taskId}`);
@@ -488,7 +509,17 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
488
509
  hostLog('debug', `planReviewWire: arm failed (fail-soft): ${String(e)}`);
489
510
  return false;
490
511
  }
491
- function presentArmedPlanReview(facts, delivery) {
512
+ function presentAfterAwait(facts, delivery) {
513
+ if (!isPlanReviewPark(result))
514
+ return;
515
+ const presence = publishPresenceOf(opts);
516
+ if (presence !== 'here') {
517
+ hostLog('debug', `planReviewWire: arm-withheld-${presence} task=${result.taskId}`);
518
+ return;
519
+ }
520
+ presentArmedPlanReview(facts, delivery, () => publishPresenceOf(opts) === 'here');
521
+ }
522
+ function presentArmedPlanReview(facts, delivery, recheckHere) {
492
523
  if (!isPlanReviewPark(result))
493
524
  return;
494
525
  const { taskId } = result;
@@ -502,6 +533,10 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
502
533
  prev.checkpointId !== undefined &&
503
534
  prev.checkpointId === gateBefore &&
504
535
  samePlanReviewCardFacts(prev.facts, facts)) {
536
+ if (recheckHere !== undefined && !recheckHere()) {
537
+ hostLog('debug', `planReviewWire: arm-withheld-after-retire task=${taskId}`);
538
+ return;
539
+ }
505
540
  hostLog('debug', `planReviewWire: arm replayed — re-presenting the still-armed card for task ${taskId}`);
506
541
  publishArmedPlanReviewCard(prev);
507
542
  return;
@@ -509,6 +544,10 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
509
544
  hostLog('debug', `planReviewWire: arm-retire task=${taskId} card=${prev.questionId}`);
510
545
  retireArmedPlanReview(taskId, prev);
511
546
  }
547
+ if (recheckHere !== undefined && !recheckHere()) {
548
+ hostLog('debug', `planReviewWire: arm-withheld-after-retire task=${taskId}`);
549
+ return;
550
+ }
512
551
  const issuedBefore = notePlanReviewCardIssued(DEFAULT_SESSION_KEY, taskId);
513
552
  const questionId = issuedBefore || hadArmedInstance ? freshPlanReviewQuestionId(taskId, reopenIdSuffix()) : planReviewQuestionId(taskId);
514
553
  const instance = Object.freeze({ questionId, ...(gateBefore !== undefined ? { checkpointId: gateBefore } : {}), facts, shape });
@@ -570,6 +609,11 @@ export function planReviewOutcomeTail(taskId, decision, effect, cause) {
570
609
  return `Do not state the result as fact: tell the user the ${decision} was submitted but its effect could not be confirmed; the background task list or the next notification of task ${JSON.stringify(taskId)} shows where it stands.`;
571
610
  }
572
611
  }
612
+ function planReviewDeliveryFrom(wire, onOutcome, onAccepted) {
613
+ if (wire === undefined && onOutcome === undefined && onAccepted === undefined)
614
+ return undefined;
615
+ return { ...(wire !== undefined ? { wire } : {}), ...(onOutcome !== undefined ? { onOutcome } : {}), ...(onAccepted !== undefined ? { onAccepted } : {}) };
616
+ }
573
617
  function planReviewDecisionTimedOutOutcome(decision) {
574
618
  return (`The plan_review ${decision} was sent, but no answer came back from the engine within the time limit this host allows for it — ` +
575
619
  `it may have taken effect (${decision === 'approve' ? 'an approval keeps the engine busy until the resumed work finishes' : 'the engine may still be applying it'}).`);
@@ -747,13 +791,43 @@ function bodyWordOf(body, key) {
747
791
  return undefined;
748
792
  }
749
793
  }
750
- function bodySaysReparked(body) {
751
- try {
752
- return body !== null && typeof body === 'object' && body.retriable === true;
753
- }
754
- catch {
755
- return false;
756
- }
794
+ function planReviewAcceptanceOf(body) {
795
+ const obj = body !== null && typeof body === 'object' ? body : undefined;
796
+ const str = (key) => (obj === undefined ? undefined : guardedRead(() => readOwnDataNonEmptyString(obj, key)));
797
+ const isTrue = (key) => obj !== undefined && guardedRead(() => readOwnDataValue(obj, key)) === true;
798
+ const taskId = str('taskId');
799
+ const sessionId = str('sessionId');
800
+ const status = str('status');
801
+ const checkpointId = str('checkpointId');
802
+ const engine = Object.freeze({
803
+ ...(taskId !== undefined ? { taskId } : {}),
804
+ ...(sessionId !== undefined ? { sessionId } : {}),
805
+ ...(status !== undefined ? { status } : {}),
806
+ ...(isTrue('decisionCommitted') ? { decisionCommitted: true } : {}),
807
+ ...(checkpointId !== undefined ? { checkpointId } : {}),
808
+ });
809
+ const errorCode = str('errorCode');
810
+ return Object.freeze({ engine, reparked: isTrue('retriable'), ...(errorCode !== undefined ? { errorCode } : {}) });
811
+ }
812
+ function acceptanceSettled(a) {
813
+ return a !== undefined && a.engine.decisionCommitted === true && !a.reparked;
814
+ }
815
+ function planReviewAcceptedPayloadOf(taskId, dispatchNo, semanticDecision, cardId, a, wireGate, card) {
816
+ const body = card?.body;
817
+ const decided = acceptanceSettled(a) && semanticDecision !== 'ask_again' && wireGate !== undefined && body !== undefined && body.kind === 'shown'
818
+ ? Object.freeze({ verdict: semanticDecision === 'approve' ? 'approved' : 'rejected', plan: body.markdown })
819
+ : undefined;
820
+ return Object.freeze({
821
+ taskId,
822
+ dispatchNo,
823
+ decision: semanticDecision,
824
+ ...(cardId !== undefined ? { questionId: cardId } : {}),
825
+ engine: a.engine,
826
+ ...(decided !== undefined ? { decided } : {}),
827
+ });
828
+ }
829
+ function planReviewDeliveryOf(a, semanticDecision) {
830
+ return acceptanceSettled(a) && semanticDecision !== 'approve' ? 'follow' : 'submit';
757
831
  }
758
832
  function askAgainReasonOf(card, opts) {
759
833
  try {
@@ -831,6 +905,8 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
831
905
  let outcome;
832
906
  let cause;
833
907
  let unsentProven = false;
908
+ let acceptance;
909
+ let wayOut;
834
910
  const gateBefore = card?.checkpointId;
835
911
  const approvalWithheld = decision !== 'approve' ? undefined : card === undefined ? 'card_missing' : card.body.kind !== 'shown' || card.checkpointId === undefined ? 'card_withholds' : undefined;
836
912
  let effect = 'unconfirmed';
@@ -913,7 +989,14 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
913
989
  finally {
914
990
  releaseLatch();
915
991
  }
916
- hostLog('debug', `planReviewWire: ${decision} → ok (dispatch #${dispatchNo}) ${JSON.stringify(body).slice(0, 200)}`);
992
+ const accepted = planReviewAcceptanceOf(body);
993
+ acceptance = accepted;
994
+ hostLog('debug', `planReviewWire: ${decision} → ok (dispatch #${dispatchNo}) ${JSON.stringify(accepted).slice(0, 200)}`);
995
+ const onAccepted = opts?.onAccepted;
996
+ if (onAccepted !== undefined) {
997
+ const payload = planReviewAcceptedPayloadOf(taskId, dispatchNo, semanticDecision, cardId, accepted, wireGate, card);
998
+ invokeHostCallbackIsolated(`planReviewWire: onAccepted task=${taskId}`, () => onAccepted(payload));
999
+ }
917
1000
  let postStatus;
918
1001
  let gateAfter;
919
1002
  let gateKindAfter;
@@ -928,12 +1011,12 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
928
1011
  catch (e) {
929
1012
  hostLog('debug', `planReviewWire: post-decide status re-pull failed (fail-soft): ${String(e)}`);
930
1013
  }
931
- const bodyStatus = bodyWordOf(body, 'status');
1014
+ const bodyStatus = accepted.engine.status;
932
1015
  const effective = postStatus ?? bodyStatus;
933
1016
  statusSeen = effective;
934
- effect = planReviewSettledVerdict({ decision, postStatus, bodyStatus, reparked: bodySaysReparked(body), gateBefore, gateAfter, gateKindAfter });
935
- const code = bodyWordOf(body, 'errorCode');
936
- if (effect === 'still_parked' && postStatus === 'needs_review' && !bodySaysReparked(body)) {
1017
+ effect = planReviewSettledVerdict({ decision, postStatus, bodyStatus, reparked: accepted.reparked, gateBefore, gateAfter, gateKindAfter });
1018
+ const code = accepted.errorCode;
1019
+ if (effect === 'still_parked' && postStatus === 'needs_review' && !accepted.reparked) {
937
1020
  outcome =
938
1021
  `The plan_review ${decision} returned HTTP 200 but the session is STILL locked on the same review gate ` +
939
1022
  `(post-decide status: needs_review). The decision did NOT take effect (known engine issue, RB-471 family). ` +
@@ -994,33 +1077,27 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
994
1077
  'This is NOT a rejection of the plan and NOT a failure of the task; the answer just given simply had nothing left to act on.';
995
1078
  }
996
1079
  else if (status === 409 && refusedCode === APPROVAL_STALE) {
997
- let untrusted = false;
998
- if (wireGate !== undefined) {
999
- const bag = readThrownExtra(e);
1000
- const noCurrentPending = bag.state === 'object' &&
1001
- (() => {
1002
- try {
1003
- return Object.getOwnPropertyDescriptor(bag.value, 'currentPending') === undefined;
1004
- }
1005
- catch {
1006
- return false;
1007
- }
1008
- })();
1009
- if (noCurrentPending) {
1010
- try {
1011
- const row = await repullRow();
1012
- untrusted = row !== undefined && pausedCheckpointIdOf(row?.result) === wireGate;
1013
- }
1014
- catch {
1015
- untrusted = false;
1016
- }
1080
+ let verdict = 'gate_moved';
1081
+ if (wireGate !== undefined && planReviewCurrentGateReadingOf(readThrownExtra(e)) === 'absent') {
1082
+ try {
1083
+ const row = await repullRow();
1084
+ const rowGate = pausedCheckpointIdOf(row?.result);
1085
+ verdict =
1086
+ row !== undefined && rowGate === wireGate
1087
+ ? 'gate_untrusted'
1088
+ : row !== undefined && rowGate === undefined && bodyWordOf(row, 'status') === 'needs_review'
1089
+ ? 'unreadable'
1090
+ : 'gate_moved';
1091
+ }
1092
+ catch {
1093
+ verdict = 'gate_moved';
1017
1094
  }
1018
1095
  }
1019
- if (untrusted && wireGate !== undefined)
1096
+ if (verdict === 'gate_untrusted' && wireGate !== undefined)
1020
1097
  noteEngineRefusedGate(wireGate);
1021
- hostLog('debug', `planReviewWire: approval-stale decision=${decision} reason=${untrusted ? 'gate_untrusted' : 'gate_moved'}`);
1098
+ hostLog('debug', `planReviewWire: approval-stale decision=${decision} reason=${verdict}`);
1022
1099
  effect = 'not_applied';
1023
- cause = untrusted ? 'gate_untrusted' : 'gate_moved';
1100
+ cause = verdict === 'unreadable' ? undefined : verdict;
1024
1101
  outcome = renderMessage(msg('approval.planReview.decision.refused', { decision }), DATA_FACE_LANGUAGE);
1025
1102
  }
1026
1103
  else if (checkpointUnsupportedVersionFromError(e) !== null) {
@@ -1046,7 +1123,11 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
1046
1123
  const failureTexts = wireFailureTextsOf(e);
1047
1124
  hostLog('debug', `planReviewWire: ${decision} → ${status} ${failureTexts.log.slice(0, 200)}`);
1048
1125
  effect = refusalDeliveryOf(e, { route: 'plan_review' }).delivery === 'refused' ? 'not_applied' : 'unconfirmed';
1049
- outcome = `${`The plan_review decision failed: HTTP ${status} ${failureTexts.reason}`.trim()}${decideFailureNote(e)}`;
1126
+ const engineWayOut = status === 409 ? readDecisionRefusalReading(e, { route: 'plan_review' }).wayOut : undefined;
1127
+ const wayOutClause = engineWayOut === undefined ? undefined : decisionRefusalWayOutContent(engineWayOut);
1128
+ if (wayOutClause !== undefined)
1129
+ wayOut = engineWayOut;
1130
+ outcome = `${`The plan_review decision failed: HTTP ${status} ${failureTexts.reason}`.trim()}${decideFailureNote(e)}${wayOutClause === undefined ? '' : ` — ${wayOutClause}.`}`;
1050
1131
  }
1051
1132
  else if (noAnswer === 'time-limit' || noAnswer === 'deadline') {
1052
1133
  hostLog('debug', `planReviewWire: ${decision} sent but no answer within the request time limit (task ${taskId})`);
@@ -1066,13 +1147,25 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
1066
1147
  }
1067
1148
  }
1068
1149
  const outcomeCause = planReviewOutcomeCauseOf(effect, cause);
1069
- outcome = `${outcome}${planReviewDecisionReasonSegment(outcomeCause, card)}${gateBefore !== undefined && wireGate === undefined && effect !== 'not_sent' && !unsentProven ? planReviewSentWithoutGateSegment() : ''}`;
1150
+ const sentWithoutGate = gateBefore !== undefined && wireGate === undefined && effect !== 'not_sent' && !unsentProven;
1151
+ outcome = `${outcome}${planReviewDecisionReasonSegment(outcomeCause, card)}${sentWithoutGate ? planReviewSentWithoutGateSegment() : ''}`;
1070
1152
  const tail = planReviewOutcomeTail(taskId, decision, effect, outcomeCause);
1071
1153
  const prompt = `<plan-review-outcome>\n${outcome}\n${tail}\n</plan-review-outcome>`;
1072
1154
  const onOutcome = opts?.onOutcome;
1155
+ const meta = {
1156
+ taskId,
1157
+ dispatchNo,
1158
+ decision: semanticDecision,
1159
+ effect,
1160
+ ...(outcomeCause !== undefined ? { cause: outcomeCause } : {}),
1161
+ ...(cardId !== undefined ? { questionId: cardId } : {}),
1162
+ delivery: planReviewDeliveryOf(acceptance, semanticDecision),
1163
+ ...(sentWithoutGate ? { gateIdOmitted: true } : {}),
1164
+ ...(wayOut !== undefined ? { wayOut } : {}),
1165
+ };
1073
1166
  settle(effect === 'not_sent' || effect === 'still_parked');
1074
1167
  try {
1075
- const delivered = enqueuePlanReviewOutcome(prompt, { taskId, dispatchNo, decision: semanticDecision, effect, ...(outcomeCause !== undefined ? { cause: outcomeCause } : {}), ...(cardId !== undefined ? { questionId: cardId } : {}) });
1168
+ const delivered = enqueuePlanReviewOutcome(prompt, meta);
1076
1169
  if (!delivered) {
1077
1170
  if (onOutcome !== undefined) {
1078
1171
  hostLog('debug', `planReviewWire: outcome queue port not installed for task ${taskId} — outcome handed to the host's onOutcome callback only`);
@@ -1087,12 +1180,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
1087
1180
  }
1088
1181
  if (onOutcome !== undefined) {
1089
1182
  invokeHostCallbackIsolated(`planReviewWire: onOutcome callback (task ${taskId})`, () => onOutcome({
1090
- taskId,
1091
- dispatchNo,
1092
- decision: semanticDecision,
1093
- effect,
1094
- ...(outcomeCause !== undefined ? { cause: outcomeCause } : {}),
1095
- ...(cardId !== undefined ? { questionId: cardId } : {}),
1183
+ ...meta,
1096
1184
  detail: outcome,
1097
1185
  tail,
1098
1186
  prompt,
@@ -1174,6 +1262,9 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs, taken) {
1174
1262
  hostLog('debug', `planReviewWire: reopen-superseded task=${taskId}`);
1175
1263
  return Promise.resolve(refusedReopen('not_presented'));
1176
1264
  }
1265
+ const early = reopenPresenceRefusal(taskId, opts, 'before');
1266
+ if (early !== undefined)
1267
+ return Promise.resolve(early);
1177
1268
  const facts = taken.facts;
1178
1269
  const markBefore = dismissalEntryFor(sessionKey, taskId);
1179
1270
  const settleOnReceipt = (receipt, firstSight) => receipt.then((presented) => {
@@ -1195,8 +1286,11 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs, taken) {
1195
1286
  samePlanReviewCardFacts(armed.facts, facts)) {
1196
1287
  hostLog('debug', `planReviewWire: reopen for task ${taskId} reusing the canonical responder (path=reopen-canonical)`);
1197
1288
  retireActiveReopen(sessionKey, canonicalId);
1289
+ const lateReuse = reopenPresenceRefusal(taskId, opts, 'after-retire');
1290
+ if (lateReuse !== undefined)
1291
+ return Promise.resolve(lateReuse);
1198
1292
  const receipt = waitForGateArmedFor(sessionKey, [armed.questionId], receiptMs);
1199
- publishQuestionFrameFor(sessionKey, planReviewQuestionFrame(armed.questionId, ARM_QUESTION, armed.shape, armed.facts));
1293
+ deliverPlanReviewCard(sessionKey, planReviewQuestionFrame(armed.questionId, ARM_QUESTION, armed.shape, armed.facts, sessionKey), armed.facts);
1200
1294
  return settleOnReceipt(receipt, firstSight);
1201
1295
  }
1202
1296
  retireActiveReopen(sessionKey, canonicalId);
@@ -1205,11 +1299,12 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs, taken) {
1205
1299
  hostLog('debug', `planReviewWire: reopen-retire-armed task=${taskId} card=${armedId}`);
1206
1300
  }
1207
1301
  publishQuestionFrameFor(sessionKey, { type: 'question_complete', questionId: armedId });
1302
+ const lateFresh = reopenPresenceRefusal(taskId, opts, 'after-retire');
1303
+ if (lateFresh !== undefined)
1304
+ return Promise.resolve(lateFresh);
1208
1305
  const questionId = freshPlanReviewQuestionId(taskId, reopenIdSuffix());
1209
1306
  hostLog('debug', `planReviewWire: path=reopen-fresh task=${taskId} card=${questionId}`);
1210
- const delivery = opts?.wire !== undefined || opts?.onOutcome !== undefined
1211
- ? { ...(opts.wire !== undefined ? { wire: opts.wire } : {}), ...(opts.onOutcome !== undefined ? { onOutcome: opts.onOutcome } : {}) }
1212
- : undefined;
1307
+ const delivery = planReviewDeliveryFrom(opts?.wire, opts?.onOutcome, opts?.onAccepted);
1213
1308
  const deliver = opts?.deliverDecision ??
1214
1309
  ((tid, decision, reason, card) => {
1215
1310
  hostLog('debug', `planReviewWire: reopen responder delivering ${decision} for task ${tid} (path=reopen-default)`);
@@ -1257,12 +1352,32 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs, taken) {
1257
1352
  unregister = registerLocalQuestionResponder(questionId, responder, onWithdraw);
1258
1353
  activeReopenResponders.set(activeKey, { questionId, unregister: () => unregister(), shape });
1259
1354
  const receipt = waitForGateArmedFor(sessionKey, [questionId], receiptMs);
1260
- publishQuestionFrameFor(sessionKey, planReviewQuestionFrame(questionId, REOPEN_QUESTION, shape, facts));
1355
+ deliverPlanReviewCard(sessionKey, planReviewQuestionFrame(questionId, REOPEN_QUESTION, shape, facts, sessionKey), facts);
1261
1356
  return settleOnReceipt(receipt, firstSight);
1262
1357
  }
1263
1358
  function refusedReopen(cause) {
1264
1359
  return { reopened: false, _sema_reopenRefusal: cause };
1265
1360
  }
1361
+ function publishPresenceOf(opts) {
1362
+ return hostPresenceOf(opts, (ask) => {
1363
+ const got = { answered: false };
1364
+ invokeHostCallbackIsolated('planReviewWire: stillHere', () => {
1365
+ got.answer = ask();
1366
+ got.answered = true;
1367
+ return got.answer;
1368
+ });
1369
+ if (!got.answered)
1370
+ throw new Error('stillHere did not answer');
1371
+ return got.answer;
1372
+ });
1373
+ }
1374
+ function reopenPresenceRefusal(taskId, opts, stage) {
1375
+ const presence = publishPresenceOf(opts);
1376
+ if (presence === 'here')
1377
+ return undefined;
1378
+ hostLog('debug', `planReviewWire: reopen-withheld-${presence} task=${taskId} at=${stage}`);
1379
+ return refusedReopen(presence === 'left' ? 'conversation_left' : 'conversation_unconfirmed');
1380
+ }
1266
1381
  function noPresentationSurfaceVerdict(taskId, sessionKey) {
1267
1382
  const verdict = { reopened: false, _sema_noPresentationSurface: true };
1268
1383
  hostLog('debug', `planReviewWire: reopen for task ${taskId} refused — nothing is subscribed to question frames for session ` +
@@ -1370,7 +1485,8 @@ export function reopenPlanReviewCard(taskId, opts) {
1370
1485
  return Promise.resolve(refusedReopen('invalid_call'));
1371
1486
  }
1372
1487
  const injected = opts;
1373
- if (opts?.mintFreshQuestionId === false && (injected?.deliverDecision !== undefined || injected?.wire !== undefined || injected?.onOutcome !== undefined)) {
1488
+ if (opts?.mintFreshQuestionId === false &&
1489
+ (injected?.deliverDecision !== undefined || injected?.wire !== undefined || injected?.onOutcome !== undefined || injected?.onAccepted !== undefined)) {
1374
1490
  hostLog('error', `planReviewWire: plan_review reopen for task ${taskId} refused — mintFreshQuestionId:false reuses the armed card's responder and its delivery, ` +
1375
1491
  'so deliverDecision / wire / onOutcome cannot be given with it; drop mintFreshQuestionId:false to deliver through the injected path');
1376
1492
  return Promise.resolve(refusedReopen('invalid_call'));
@@ -1501,3 +1617,26 @@ export async function readPlanReviewCardFacts(taskId, opts) {
1501
1617
  return mintPlanReviewCardFacts(taskId, checkpointId, unknownBody('unreadable'));
1502
1618
  }
1503
1619
  }
1620
+ function planReviewCurrentGateReadingOf(bag) {
1621
+ if (bag.state !== 'object')
1622
+ return 'malformed';
1623
+ try {
1624
+ const d = Object.getOwnPropertyDescriptor(bag.value, 'currentPending');
1625
+ if (d === undefined)
1626
+ return 'absent';
1627
+ if (!Object.hasOwn(d, 'value'))
1628
+ return 'malformed';
1629
+ const slot = d.value;
1630
+ if (typeof slot !== 'object' || slot === null || Array.isArray(slot))
1631
+ return 'malformed';
1632
+ const checkpointId = readOwnDataNonEmptyString(slot, 'checkpointId');
1633
+ return checkpointId === undefined ? 'malformed' : { checkpointId };
1634
+ }
1635
+ catch {
1636
+ return 'malformed';
1637
+ }
1638
+ }
1639
+ export function readPlanReviewCurrentGate(e) {
1640
+ const reading = planReviewCurrentGateReadingOf(readThrownExtra(e));
1641
+ return typeof reading === 'object' ? reading.checkpointId : undefined;
1642
+ }
@@ -6,7 +6,7 @@ import { ruleToolNameVerdictOf } from '../protocolSegment.js';
6
6
  import { CORRUPT_ROW_ERROR_CODE, RULES_INERT_ENTRY, RULES_INVALID_SHAPE, isRulesRefusalCode } from '../engineErrorCodes.js';
7
7
  import { deniedEntryToolName } from '../engineToolDirectory.js';
8
8
  import { rosterMountedNames } from '../rosterMountedNames.js';
9
- import { CORE_ALLOW_ENTRY_INERT_REASONS, CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
9
+ import { CORE_ALLOW_ENTRY_INERT_REASONS, CORE_RULE_TOOL_NAME_VERDICTS, CORE_SESSION_RULES_GRAMMAR } from '../generated/engineFactTables.js';
10
10
  const EARLIER_GRAMMAR_REASON = 'earlier_grammar';
11
11
  const _inertReasonPin = true;
12
12
  void _inertReasonPin;
@@ -234,7 +234,7 @@ function receiptMismatch(sent, receipt, sentRev, receiptRev, sentGrammar, receip
234
234
  if (receiptRev <= sentRev) {
235
235
  return `policy write receipt did not advance the version stamp (sent with ${sentRev}, receipt says ${receiptRev})`;
236
236
  }
237
- if (sentGrammar !== undefined && (receiptGrammar === undefined || receiptGrammar < sentGrammar)) {
237
+ if (receiptGrammar === undefined || receiptGrammar < sentGrammar) {
238
238
  return `policy write receipt does not carry the rule-grammar stamp that was sent (sent ${sentGrammar}, receipt ${receiptGrammar === undefined ? 'carries none' : `says ${receiptGrammar}`}) — the stored entries may now read differently`;
239
239
  }
240
240
  for (const field of SESSION_POLICY_RULE_FIELDS) {
@@ -342,7 +342,14 @@ function earlierGrammarOutcome(failure) {
342
342
  };
343
343
  }
344
344
  function rulesWithStamp(rules, grammar) {
345
- return grammar === undefined ? rules : { ...rules, grammar };
345
+ return { ...rules, grammar };
346
+ }
347
+ const GRAMMAR_BEFORE_STAMP = 0;
348
+ function writeGrammarOf(read) {
349
+ const stamp = grammarStampOf(read);
350
+ if (stamp !== undefined)
351
+ return stamp;
352
+ return read.rev >= 1 ? GRAMMAR_BEFORE_STAMP : CORE_SESSION_RULES_GRAMMAR;
346
353
  }
347
354
  function grammarStampOf(read) {
348
355
  return Object.hasOwn(read, 'grammar') ? read.grammar : undefined;
@@ -361,9 +368,10 @@ async function attemptTighten(facade, req, opts) {
361
368
  if (earlier.length > 0)
362
369
  return { done: { kind: 'refused', why: 'earlier_grammar_rows', entries: earlier } };
363
370
  }
371
+ const declared = writeGrammarOf(read);
364
372
  let receipt;
365
373
  try {
366
- receipt = await facade.putPolicy(req.sessionId, { rules: rulesWithStamp(rules, stamp), expectedRev: read.rev }, opts);
374
+ receipt = await facade.putPolicy(req.sessionId, { rules: rulesWithStamp(rules, declared), expectedRev: read.rev }, opts);
367
375
  }
368
376
  catch (e) {
369
377
  const failure = classifySessionPolicyFailure(e);
@@ -389,7 +397,7 @@ async function attemptTighten(facade, req, opts) {
389
397
  if (!narrowed.ok) {
390
398
  return { done: { kind: 'unknown', why: 'write_receipt_unreadable', message: narrowed.detail } };
391
399
  }
392
- const mismatch = receiptMismatch(rules, narrowed.rules, read.rev, narrowed.rev, stamp, narrowed.grammar);
400
+ const mismatch = receiptMismatch(rules, narrowed.rules, read.rev, narrowed.rev, declared, narrowed.grammar);
393
401
  if (mismatch !== null)
394
402
  return { done: { kind: 'unknown', why: 'write_receipt_mismatch', message: mismatch } };
395
403
  return { done: { kind: 'written', rev: narrowed.rev } };
@@ -626,9 +634,10 @@ async function attemptRemoval(facade, sessionId, mounted, opts) {
626
634
  if (earlier.length > 0)
627
635
  return { done: { kind: 'refused', why: 'earlier_grammar_rows', entries: earlier } };
628
636
  }
637
+ const declared = writeGrammarOf(read);
629
638
  let receipt;
630
639
  try {
631
- receipt = await facade.putPolicy(sessionId, { rules: rulesWithStamp(next, stamp), expectedRev: read.rev }, opts);
640
+ receipt = await facade.putPolicy(sessionId, { rules: rulesWithStamp(next, declared), expectedRev: read.rev }, opts);
632
641
  }
633
642
  catch (e) {
634
643
  const failure = classifySessionPolicyFailure(e);
@@ -651,7 +660,7 @@ async function attemptRemoval(facade, sessionId, mounted, opts) {
651
660
  }
652
661
  if (!narrowed.ok)
653
662
  return { done: { kind: 'write_unconfirmed', why: 'write_receipt_unreadable', message: narrowed.detail } };
654
- const mismatch = receiptMismatch(next, narrowed.rules, read.rev, narrowed.rev, stamp, narrowed.grammar) ??
663
+ const mismatch = receiptMismatch(next, narrowed.rules, read.rev, narrowed.rev, declared, narrowed.grammar) ??
655
664
  (removed.some((e) => {
656
665
  const r = Object.hasOwn(narrowed.rules, e.field) ? narrowed.rules[e.field] : undefined;
657
666
  return r !== undefined && r.includes(e.rule);
@@ -1 +1,2 @@
1
1
  export declare function subagentSourceSessionOf(carrier: object | null | undefined): string | undefined;
2
+ export declare function livePendingRowSourceSessionOf(row: object | null | undefined): string | undefined;
@@ -11,3 +11,14 @@ export function subagentSourceSessionOf(carrier) {
11
11
  const retired = slotReadOf(carrier, 'sourceTaskId');
12
12
  return retired.presence === 'present' ? nonEmpty(retired.value) : undefined;
13
13
  }
14
+ export function livePendingRowSourceSessionOf(row) {
15
+ if (typeof row !== 'object' || row === null)
16
+ return undefined;
17
+ const current = slotReadOf(row, 'sourceSessionId');
18
+ if (current.presence === 'present')
19
+ return nonEmpty(current.value);
20
+ if (current.presence !== 'absent')
21
+ return undefined;
22
+ const retired = slotReadOf(row, 'originTaskId');
23
+ return retired.presence === 'present' ? nonEmpty(retired.value) : undefined;
24
+ }