@sema-agent/client-core 0.91.0 → 0.93.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 (195) hide show
  1. package/CHANGELOG.md +351 -0
  2. package/README.md +51 -34
  3. package/dist/adapt/arms.js +6 -12
  4. package/dist/adapt/panelTasks.d.ts +0 -2
  5. package/dist/adapt/panelTasks.js +6 -26
  6. package/dist/adapt/toolCards.js +6 -4
  7. package/dist/adapt.d.ts +1 -1
  8. package/dist/adapt.js +3 -3
  9. package/dist/adapter/activeRunSelfHeal.d.ts +0 -2
  10. package/dist/adapter/activeRunSelfHeal.js +5 -27
  11. package/dist/adapter/downstream/terminalToSdkResult.js +16 -7
  12. package/dist/adapter/runStream.js +6 -2
  13. package/dist/agentsWireCaps.js +1 -1
  14. package/dist/approvalDecidedByCapability.d.ts +16 -0
  15. package/dist/approvalDecidedByCapability.js +47 -0
  16. package/dist/approvalsStreamLiveCapability.js +3 -3
  17. package/dist/askSurvivesStandingAnswer.d.ts +4 -1
  18. package/dist/askSurvivesStandingAnswer.js +13 -0
  19. package/dist/autoModeUnavailable.js +3 -2
  20. package/dist/centerWiringCapability.js +1 -1
  21. package/dist/cloudConfigWireCaps.js +10 -9
  22. package/dist/compensations.js +7 -7
  23. package/dist/controlRouter.d.ts +23 -23
  24. package/dist/controlRouter.js +114 -49
  25. package/dist/coreValuePorts.d.ts +4 -2
  26. package/dist/coreValuePorts.js +5 -0
  27. package/dist/decideFailureNote.js +4 -2
  28. package/dist/decideReceipt.d.ts +11 -0
  29. package/dist/decideReceipt.js +95 -9
  30. package/dist/detachWire.d.ts +2 -14
  31. package/dist/detachWire.js +1 -47
  32. package/dist/deviceExecutorManagementCapability.d.ts +6 -2
  33. package/dist/deviceExecutorManagementCapability.js +61 -15
  34. package/dist/displayUntrusted.js +2 -1
  35. package/dist/effectiveFacts.d.ts +14 -4
  36. package/dist/effectiveFacts.js +45 -5
  37. package/dist/engineAgentAbsence.js +2 -1
  38. package/dist/engineCapReader.js +16 -10
  39. package/dist/engineCapsCache.d.ts +2 -0
  40. package/dist/engineCapsCache.js +13 -2
  41. package/dist/engineConfigDrift.d.ts +20 -0
  42. package/dist/engineConfigDrift.js +54 -0
  43. package/dist/engineErrorCodes.d.ts +17 -0
  44. package/dist/engineErrorCodes.js +45 -0
  45. package/dist/engineInlineTaskStats.d.ts +1 -0
  46. package/dist/engineNoticeCodes.d.ts +16 -2
  47. package/dist/engineNoticeCodes.js +71 -0
  48. package/dist/engineWireFor.d.ts +22 -0
  49. package/dist/engineWireFor.js +154 -9
  50. package/dist/engineWireSdk.d.ts +0 -1
  51. package/dist/engineWireSdk.js +0 -3
  52. package/dist/executionLaneCapability.js +4 -2
  53. package/dist/fileHistoryCaptureCapability.d.ts +1 -1
  54. package/dist/fileHistoryCaptureCapability.js +1 -6
  55. package/dist/fileHistoryLegNotice.d.ts +1 -1
  56. package/dist/fileHistoryLegNotice.js +1 -6
  57. package/dist/gateOutcome.js +2 -17
  58. package/dist/gateVocabulary.d.ts +2 -8
  59. package/dist/gateVocabulary.js +6 -32
  60. package/dist/generated/engineFactTables.d.ts +17 -1
  61. package/dist/generated/engineFactTables.js +91 -0
  62. package/dist/generated/engineNoticeTables.d.ts +1 -1
  63. package/dist/generated/engineNoticeTables.js +4 -1
  64. package/dist/generated/settingsSchemaTables.d.ts +8 -0
  65. package/dist/generated/settingsSchemaTables.js +40 -0
  66. package/dist/goalStopHook.d.ts +1 -8
  67. package/dist/goalStopHook.js +2 -58
  68. package/dist/headlessPermissionModeWire.d.ts +3 -2
  69. package/dist/headlessPermissionModeWire.js +46 -5
  70. package/dist/hitl/allowRuleShadow.d.ts +21 -0
  71. package/dist/hitl/allowRuleShadow.js +167 -0
  72. package/dist/hitl/askClassPair.d.ts +14 -0
  73. package/dist/hitl/askClassPair.js +42 -0
  74. package/dist/hitl/askGateWire.js +1 -0
  75. package/dist/hitl/askParkRowRouting.js +2 -2
  76. package/dist/hitl/askSurvivesPosture.d.ts +5 -0
  77. package/dist/hitl/askSurvivesPosture.js +17 -0
  78. package/dist/hitl/automaticAnswerVerdict.d.ts +21 -0
  79. package/dist/hitl/automaticAnswerVerdict.js +143 -0
  80. package/dist/hitl/crashConverged.d.ts +1 -0
  81. package/dist/hitl/crashConverged.js +1 -1
  82. package/dist/hitl/frameRouter.d.ts +2 -0
  83. package/dist/hitl/frameRouter.js +73 -16
  84. package/dist/hitl/gateLedger.d.ts +7 -0
  85. package/dist/hitl/gateLedger.js +37 -5
  86. package/dist/hitl/hitlBridge.d.ts +1 -0
  87. package/dist/hitl/hitlBridge.js +6 -1
  88. package/dist/hitl/hitlHostSurface.d.ts +5 -2
  89. package/dist/hitl/hitlHostSurface.js +25 -3
  90. package/dist/hitl/livePendingAsk.js +24 -5
  91. package/dist/hitl/parkResolver.js +24 -4
  92. package/dist/hitl/persistedRulesWire.d.ts +4 -1
  93. package/dist/hitl/persistedRulesWire.js +38 -19
  94. package/dist/hitl/planReviewWire.d.ts +0 -2
  95. package/dist/hitl/planReviewWire.js +3 -20
  96. package/dist/hitl/replayedApproval.d.ts +21 -0
  97. package/dist/hitl/replayedApproval.js +89 -0
  98. package/dist/hitl/sessionPolicyDeliverable.js +20 -2
  99. package/dist/hitl/sessionPolicyWire.d.ts +10 -0
  100. package/dist/hitl/sessionPolicyWire.js +19 -6
  101. package/dist/hitl/toolApprovalWire.d.ts +27 -7
  102. package/dist/hitl/toolApprovalWire.js +153 -94
  103. package/dist/hookMatcherVerdict.js +2 -1
  104. package/dist/hooksWireCaps.d.ts +1 -1
  105. package/dist/hooksWireCaps.js +1 -1
  106. package/dist/humanTurn.js +3 -4
  107. package/dist/index.d.ts +21 -6
  108. package/dist/index.js +17 -4
  109. package/dist/inheritEnvWire.d.ts +1 -4
  110. package/dist/inheritEnvWire.js +3 -22
  111. package/dist/limitsWire.d.ts +0 -6
  112. package/dist/limitsWire.js +0 -38
  113. package/dist/mcpLiveness.js +2 -1
  114. package/dist/mcpProbeCapability.js +1 -1
  115. package/dist/mcpProbeWire.js +10 -12
  116. package/dist/mcpReconnect.d.ts +5 -0
  117. package/dist/mcpReconnect.js +28 -0
  118. package/dist/memoryComplianceCapability.js +2 -2
  119. package/dist/memoryOriginCapability.js +2 -2
  120. package/dist/memoryVerbsWire.d.ts +1 -1
  121. package/dist/memoryVerbsWire.js +17 -13
  122. package/dist/model/catalog.js +2 -1
  123. package/dist/model/providerPresets.d.ts +3 -1
  124. package/dist/model/tierVocabulary.d.ts +2 -1
  125. package/dist/model/tierVocabulary.js +3 -8
  126. package/dist/ownKey.d.ts +2 -0
  127. package/dist/ownKey.js +15 -0
  128. package/dist/peerFrames.js +2 -2
  129. package/dist/peerLaneCapability.js +2 -2
  130. package/dist/permissionModesCapability.d.ts +16 -0
  131. package/dist/permissionModesCapability.js +71 -0
  132. package/dist/permissionRuleIssue.js +10 -7
  133. package/dist/permissionRulesWriteCapability.js +2 -2
  134. package/dist/permissionWireCaps.d.ts +2 -0
  135. package/dist/permissionWireCaps.js +8 -1
  136. package/dist/permissionsAllowGrantCapability.js +7 -2
  137. package/dist/permissionsDeclaration.d.ts +7 -1
  138. package/dist/permissionsDeclaration.js +29 -20
  139. package/dist/pluginHooksWire.js +2 -11
  140. package/dist/postureKnob.js +1 -1
  141. package/dist/promptProfileWireCaps.d.ts +2 -1
  142. package/dist/protocolSegment.d.ts +11 -0
  143. package/dist/protocolSegment.js +31 -0
  144. package/dist/readFacePosture.js +1 -1
  145. package/dist/request/taskRequest.js +1 -15
  146. package/dist/resumeRefusalCopy.d.ts +1 -0
  147. package/dist/resumeRefusalCopy.js +26 -3
  148. package/dist/retryStatus.js +2 -1
  149. package/dist/rewindArchiveCapability.js +2 -2
  150. package/dist/ruleCompileVerdict.d.ts +2 -0
  151. package/dist/ruleCompileVerdict.js +13 -0
  152. package/dist/ruleRemovalConsequence.d.ts +2 -1
  153. package/dist/runCancelContext.d.ts +12 -0
  154. package/dist/runCancelContext.js +83 -1
  155. package/dist/runTerminal.d.ts +16 -0
  156. package/dist/runTerminal.js +32 -3
  157. package/dist/seam.d.ts +1 -0
  158. package/dist/seam.js +1 -1
  159. package/dist/seatContract.d.ts +2 -1
  160. package/dist/sessionMemoryStatus.d.ts +2 -4
  161. package/dist/sessionPolicyCapability.js +1 -1
  162. package/dist/sessionSyncFileHistoryCapability.d.ts +18 -0
  163. package/dist/sessionSyncFileHistoryCapability.js +50 -0
  164. package/dist/skillToolListsCapability.d.ts +18 -0
  165. package/dist/skillToolListsCapability.js +50 -0
  166. package/dist/sqlEngineCapability.js +2 -9
  167. package/dist/steerPriorityCapability.d.ts +18 -0
  168. package/dist/steerPriorityCapability.js +80 -0
  169. package/dist/steerReceipt.d.ts +9 -0
  170. package/dist/steerReceipt.js +25 -0
  171. package/dist/storePostureCapability.js +2 -2
  172. package/dist/subagent/engineSubagentSteer.js +2 -9
  173. package/dist/supportFloor.d.ts +1 -0
  174. package/dist/supportFloor.js +15 -5
  175. package/dist/taskRequestWords.d.ts +4 -2
  176. package/dist/taskRequestWords.js +1 -1
  177. package/dist/toolHistoryBadTurn.js +1 -2
  178. package/dist/toolResult.d.ts +10 -1
  179. package/dist/toolResult.js +80 -6
  180. package/dist/types/engineState.d.ts +2 -12
  181. package/dist/userInvokedSkillsCapability.d.ts +18 -0
  182. package/dist/userInvokedSkillsCapability.js +50 -0
  183. package/dist/webSearchBackendCapability.js +2 -2
  184. package/dist/webSearchWireCaps.d.ts +1 -1
  185. package/dist/webSearchWireCaps.js +40 -2
  186. package/dist/wireFailureShape.d.ts +2 -0
  187. package/dist/wireFailureShape.js +6 -2
  188. package/dist/wireRefusalCopy.d.ts +9 -3
  189. package/dist/wireRefusalCopy.js +41 -4
  190. package/dist/workflowsJournalCapability.d.ts +18 -0
  191. package/dist/workflowsJournalCapability.js +50 -0
  192. package/dist/writeProtectionCapability.d.ts +2 -4
  193. package/dist/writeProtectionCapability.js +1 -1
  194. package/docs/INTEGRATION-CLIENTS.md +1516 -18
  195. package/package.json +2 -2
@@ -1,6 +1,6 @@
1
- import { putOwnKey } from '../ownKey.js';
1
+ import { putOwnKey, readOwnDataValue } from '../ownKey.js';
2
2
  import { DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, NO_QUESTION_OVERLAY_REASON, findPendingForTask, isNoDecisionSurfaceReason, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
3
- import { CHECKPOINT_UNSUPPORTED_VERSION, PARKED_RESUME_STARTUP_FAILED, RESUME_POLICY_FOLD_CODE, isDecideParkMovedCode, refusalSaysStillPending } from '../engineErrorCodes.js';
3
+ import { ASK_GONE, CHECKPOINT_UNSUPPORTED_VERSION, DECIDE_WORKFLOW_PARK_FORKED, DECIDE_WORKFLOW_PARKS_UNREADABLE, PARKED_RESUME_STARTUP_FAILED, RESUME_POLICY_FOLD_CODE, isDecideParkMovedCode, refusalSaysStillPending } from '../engineErrorCodes.js';
4
4
  import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
5
5
  import { hostLog } from '../host.js';
6
6
  import { surfaceFsApprovalAndDecide } from './toolApprovalWire.js';
@@ -58,10 +58,16 @@ function alreadyResolvedFailureVerdict(outcome) {
58
58
  return false;
59
59
  if ('errorCode' in outcome && isDecideParkMovedCode(outcome.errorCode))
60
60
  return true;
61
+ if ('errorCode' in outcome && outcome.errorCode === ASK_GONE)
62
+ return true;
61
63
  return isAlreadyResolvedGateReason(outcome.reason);
62
64
  }
63
65
  export function isGateStandingErrorCode(code, retriable) {
64
- return code === RESUME_POLICY_FOLD_CODE || code === 'corrupt' || (code === CHECKPOINT_UNSUPPORTED_VERSION && retriable === true);
66
+ return (code === RESUME_POLICY_FOLD_CODE ||
67
+ code === 'corrupt' ||
68
+ (code === CHECKPOINT_UNSUPPORTED_VERSION && retriable === true) ||
69
+ code === DECIDE_WORKFLOW_PARKS_UNREADABLE ||
70
+ code === DECIDE_WORKFLOW_PARK_FORKED);
65
71
  }
66
72
  export function isCodeClassifiedGateFailure(code, retriable) {
67
73
  return isGateStandingErrorCode(code, retriable) || code === PARKED_RESUME_STARTUP_FAILED;
@@ -190,10 +196,18 @@ function isFetchStepNoPending(outcome) {
190
196
  async function surfaceParkGate(park, ctx, gatedCallId, onPresented) {
191
197
  const { deps, led, taskId } = ctx;
192
198
  if (park.gate === 'fs') {
199
+ let decidedByReading;
200
+ try {
201
+ decidedByReading = readOwnDataValue(deps.approvalLane, 'approvalDecidedBy');
202
+ }
203
+ catch {
204
+ decidedByReading = undefined;
205
+ }
193
206
  const outcome = await surfaceFsApprovalAndDecide({
194
207
  client: deps.client,
195
208
  ...(onPresented ? { onPresented } : {}),
196
209
  ...(deps.onDecideOperation ? { onDecideOperation: deps.onDecideOperation } : {}),
210
+ ...(decidedByReading !== undefined ? { approvalDecidedBy: decidedByReading } : {}),
197
211
  }, taskId, argsByCallOf(led), ctx.signal, gatedCallId);
198
212
  const outcomeDenied = 'denied' in outcome ? outcome.denied : undefined;
199
213
  if (outcome.kind === 'decided' && outcomeDenied === true && outcome.gatedCallId) {
@@ -208,6 +222,12 @@ async function surfaceParkGate(park, ctx, gatedCallId, onPresented) {
208
222
  }
209
223
  return surfaceGateAndDecide(deps, taskId, argsByCallOf(led), ctx.signal, gatedCallId, onPresented);
210
224
  }
225
+ function decidedIdentityCandidate(led, park) {
226
+ const id = park.gatedCallId;
227
+ if (id !== undefined)
228
+ return id;
229
+ return led.lastFsOrShellGatedCallId();
230
+ }
211
231
  export async function resolvePark(park, ctx) {
212
232
  const { led, taskId } = ctx;
213
233
  let outcome;
@@ -243,7 +263,7 @@ export async function resolvePark(park, ctx) {
243
263
  }
244
264
  else {
245
265
  if (park.gate === 'fs')
246
- candidateGatedCallId = led.lastFsOrShellGatedCallId();
266
+ candidateGatedCallId = decidedIdentityCandidate(led, park);
247
267
  outcome = await surfaceParkGate(park, ctx, park.gatedCallId, witness);
248
268
  }
249
269
  const alreadyDecidedById = outcome.kind === 'failed' &&
@@ -50,6 +50,9 @@ export type RulesFailure = {
50
50
  } | {
51
51
  kind: 'forbidden';
52
52
  message: string;
53
+ } | {
54
+ kind: 'operators-unconfigured';
55
+ message: string;
53
56
  } | {
54
57
  kind: 'body-shape';
55
58
  message: string;
@@ -100,7 +103,7 @@ export type PersistedRuleWriteBehavior = (typeof PERSISTED_RULE_WRITE_BEHAVIORS)
100
103
  export type PersistedRuleWriteRequest = RuleWriteRequest;
101
104
  export type PersistedRuleWriteWireResult = RuleWriteResult;
102
105
  export type PersistedRuleLiveness = RemovalLiveness;
103
- export declare const PERSISTED_RULE_WRITE_REFUSAL_CAUSES: readonly ["unwritable_behavior", "unreadable_identity", "unusable_principal", "client_too_old", "field_refused", "body_shape", "operator_only", "store_absent", "lane_too_old", "route_absent", "not_committed"];
106
+ export declare const PERSISTED_RULE_WRITE_REFUSAL_CAUSES: readonly ["unwritable_behavior", "unreadable_identity", "unusable_principal", "client_too_old", "field_refused", "body_shape", "operator_only", "operators_unconfigured", "store_absent", "lane_too_old", "route_absent", "not_committed"];
104
107
  export type PersistedRuleWriteRefusalCause = (typeof PERSISTED_RULE_WRITE_REFUSAL_CAUSES)[number];
105
108
  export declare const PERSISTED_RULE_WRITE_UNKNOWN_REASONS: readonly ["indeterminate", "malformed_result", "identity_mismatch", "unattributed_method_refusal", "unclassified"];
106
109
  export type PersistedRuleWriteUnknownReason = (typeof PERSISTED_RULE_WRITE_UNKNOWN_REASONS)[number];
@@ -1,5 +1,7 @@
1
1
  import { engineCapTrue } from '../engineCapsCache.js';
2
+ import { CORE_RULE_BEHAVIORS } from '../generated/engineFactTables.js';
2
3
  import { wireFailureShapeOf } from '../wireFailureShape.js';
4
+ import { CAPABILITY_OPERATOR_PRINCIPALS_REQUIRED } from '../engineErrorCodes.js';
3
5
  import { hostLog } from '../host.js';
4
6
  import { observedPermissionRulesWrite, permissionRulesWriteAvailable } from '../permissionRulesWriteCapability.js';
5
7
  import { readToolApprovalRespondAck } from './toolApprovalWire.js';
@@ -7,7 +9,7 @@ export function persistedRulesLaneAvailable(baseUrl) {
7
9
  return engineCapTrue(baseUrl, 'permissionRules');
8
10
  }
9
11
  export function persistedRulesGovernanceAvailable(baseUrl) {
10
- return engineCapTrue(baseUrl, 'permissionRulesRevoke') && engineCapTrue(baseUrl, 'permissionRules');
12
+ return engineCapTrue(baseUrl, 'permissionRules');
11
13
  }
12
14
  export function persistedRulesWriteAvailable(baseUrl) {
13
15
  return permissionRulesWriteAvailable(observedPermissionRulesWrite(baseUrl ?? ''));
@@ -66,6 +68,8 @@ export function classifyRulesFailure(e) {
66
68
  return { kind: 'too-many-candidates', message };
67
69
  if (status === 403 && errorCode === 'auth.operator_only')
68
70
  return { kind: 'forbidden', message };
71
+ if (errorCode === CAPABILITY_OPERATOR_PRINCIPALS_REQUIRED)
72
+ return { kind: 'operators-unconfigured', message };
69
73
  if (status === 405) {
70
74
  return errorCode === 'method_not_allowed'
71
75
  ? { kind: 'method-unsupported', message }
@@ -73,7 +77,7 @@ export function classifyRulesFailure(e) {
73
77
  }
74
78
  return { kind: 'error', message };
75
79
  }
76
- export const PERSISTED_RULE_BEHAVIORS = Object.freeze(['deny', 'ask', 'allow']);
80
+ export const PERSISTED_RULE_BEHAVIORS = CORE_RULE_BEHAVIORS;
77
81
  const BEHAVIOR_SET = new Set(PERSISTED_RULE_BEHAVIORS);
78
82
  function rowStr(row, key) {
79
83
  if (typeof row !== 'object' || row === null || Array.isArray(row))
@@ -84,6 +88,10 @@ function rowStr(row, key) {
84
88
  function persistedRuleIdentityRowReadable(row) {
85
89
  return rowStr(row, 'rule') !== undefined && rowStr(row, 'scope') !== undefined;
86
90
  }
91
+ function persistedRuleIdentityRowWhy(row) {
92
+ const bad = ['rule', 'scope'].filter((k) => rowStr(row, k) === undefined);
93
+ return `${bad.join(' and ')} ${bad.length === 1 ? 'is' : 'are'} missing or not a non-empty string`;
94
+ }
87
95
  export function persistedRuleBehaviorOf(rule) {
88
96
  const b = rowStr(rule, 'behavior');
89
97
  return b !== undefined && BEHAVIOR_SET.has(b) ? b : undefined;
@@ -144,16 +152,22 @@ export async function listAllPersistedRules(facade, params = {}, opts) {
144
152
  }
145
153
  return { ok: false, failure };
146
154
  }
147
- if (typeof res !== 'object' ||
148
- res === null ||
149
- !Array.isArray(res.rules) ||
150
- typeof res.rev !== 'number' ||
151
- !Number.isFinite(res.rev)) {
155
+ const pageProblems = [];
156
+ if (typeof res !== 'object' || res === null) {
157
+ pageProblems.push('the page is not an object');
158
+ }
159
+ else {
160
+ if (!Array.isArray(res.rules))
161
+ pageProblems.push('rules is missing or not an array');
162
+ if (typeof res.rev !== 'number' || !Number.isFinite(res.rev))
163
+ pageProblems.push('rev is missing or not a finite number');
164
+ }
165
+ if (pageProblems.length > 0) {
152
166
  return {
153
167
  ok: false,
154
168
  failure: {
155
169
  kind: 'error',
156
- message: 'rule list page was malformed (missing/ill-typed rules array or rev on a 2xx) — refusing to certify it as a complete governance list',
170
+ message: `rule list page was malformed (${pageProblems.join('; ')}) on a 2xx — refusing to certify it as a complete governance list`,
157
171
  },
158
172
  };
159
173
  }
@@ -172,7 +186,7 @@ export async function listAllPersistedRules(facade, params = {}, opts) {
172
186
  ok: false,
173
187
  failure: {
174
188
  kind: 'error',
175
- message: 'rule list page carried a malformed rule row (missing/ill-typed rule or scope) — refusing to present a list that hides or garbles live rules',
189
+ message: `rule list page carried a malformed rule row (${persistedRuleIdentityRowWhy(r)}) — refusing to present a list that hides or garbles live rules`,
176
190
  },
177
191
  };
178
192
  }
@@ -221,6 +235,7 @@ export const PERSISTED_RULE_WRITE_REFUSAL_CAUSES = Object.freeze([
221
235
  'field_refused',
222
236
  'body_shape',
223
237
  'operator_only',
238
+ 'operators_unconfigured',
224
239
  'store_absent',
225
240
  'lane_too_old',
226
241
  'route_absent',
@@ -242,6 +257,8 @@ function writeOutcomeFromFailure(failure) {
242
257
  return { status: 'refused', cause: 'body_shape', message };
243
258
  case 'forbidden':
244
259
  return { status: 'refused', cause: 'operator_only', message };
260
+ case 'operators-unconfigured':
261
+ return { status: 'refused', cause: 'operators_unconfigured', message };
245
262
  case 'lane-unavailable':
246
263
  return { status: 'refused', cause: 'store_absent', message };
247
264
  case 'method-unsupported':
@@ -341,24 +358,26 @@ export async function writePersistedRule(facade, input, opts) {
341
358
  catch (e) {
342
359
  return writeOutcomeFromFailure(classifyRulesFailure(e));
343
360
  }
344
- const malformed = () => ({
361
+ const malformed = (what) => ({
345
362
  status: 'unknown',
346
363
  why: 'malformed_result',
347
- message: 'the rule write answered 2xx with a body this client cannot read (status, rev, liveness or the written row) — refusing to certify it as a standing rule; re-list the rules to reconcile',
364
+ message: `the rule write answered 2xx with a body this client cannot read (${what}) — refusing to certify it as a standing rule; re-list the rules to reconcile`,
348
365
  });
349
366
  if (res === null || typeof res !== 'object' || Array.isArray(res))
350
- return malformed();
367
+ return malformed('the body is not an object');
351
368
  const snap = certificationSnapshot(res);
352
369
  if (snap === null)
353
- return malformed();
370
+ return malformed('a field of the body is an inherited or accessor property, or threw when read');
354
371
  const { status, rev, hasLiveness, liveness, hasRow, row } = snap;
355
- if ((status !== 'persisted' && status !== 'no-op') || typeof rev !== 'number' || !Number.isFinite(rev))
356
- return malformed();
372
+ const statusOk = status === 'persisted' || status === 'no-op';
373
+ const revOk = typeof rev === 'number' && Number.isFinite(rev);
374
+ if (!statusOk || !revOk)
375
+ return malformed([...(statusOk ? [] : ['status is not persisted or no-op']), ...(revOk ? [] : ['rev is not a finite number'])].join('; '));
357
376
  const wrote = status === 'persisted' ? 'persisted' : 'no_op';
358
377
  if (hasLiveness) {
359
378
  if (liveness === 'no') {
360
379
  if (hasRow)
361
- return malformed();
380
+ return malformed('liveness and the written row contradict each other');
362
381
  return {
363
382
  status: 'revoked',
364
383
  wrote,
@@ -368,7 +387,7 @@ export async function writePersistedRule(facade, input, opts) {
368
387
  }
369
388
  if (liveness === 'unknown') {
370
389
  if (hasRow)
371
- return malformed();
390
+ return malformed('liveness and the written row contradict each other');
372
391
  return {
373
392
  status: 'unverified',
374
393
  wrote,
@@ -377,10 +396,10 @@ export async function writePersistedRule(facade, input, opts) {
377
396
  };
378
397
  }
379
398
  if (liveness !== 'yes')
380
- return malformed();
399
+ return malformed('liveness is not one of yes, no or unknown');
381
400
  }
382
401
  if (!persistedRuleIdentityRowReadable(row))
383
- return malformed();
402
+ return malformed(`the written row is unreadable (${persistedRuleIdentityRowWhy(row)})`);
384
403
  const landed = row;
385
404
  if (landed.behavior !== behavior || landed.rule !== rule || landed.scope !== scope) {
386
405
  return {
@@ -6,8 +6,6 @@ export declare const PLAN_REVIEW_APPROVE_LABEL = "Yes, approve and run the plan"
6
6
  export declare const PLAN_REVIEW_REJECT_LABEL = "No, reject it (keep planning)";
7
7
  export declare const PLAN_REVIEW_APPROVE_AUTO_LABEL = "Yes, and auto-accept edits";
8
8
  export declare const PLAN_REVIEW_APPROVE_MANUAL_LABEL = "Yes, and manually approve edits";
9
- export declare const PLAN_REVIEW_MODE_AFTER_MIN_ENGINE: readonly [number, number, number];
10
- export declare function versionSupportsPlanReviewModeAfter(v: string | undefined): 'supported' | 'unsupported' | 'unknown';
11
9
  export declare function planReviewDecisionFromAnswer(answer: QuestionAnswer | null | undefined, shownLabels?: readonly string[]): 'approve' | 'reject' | 'dismissed';
12
10
  export type PlanReviewChoice = {
13
11
  decision: 'approve';
@@ -7,9 +7,8 @@ import { enqueuePlanReviewOutcome } from '../notifications.js';
7
7
  import { corruptStoredRowContent, corruptStoredRowWhere } from '../wireErrorTriage.js';
8
8
  import { decideFailureNote } from '../decideFailureNote.js';
9
9
  import { checkpointUnsupportedVersionContent, checkpointUnsupportedVersionFromError } from '../resumeRefusalCopy.js';
10
- import { REQUEST_FIELD_CONFLICT } from '../engineErrorCodes.js';
10
+ import { REQUEST_FIELD_CONFLICT, isUnavailableFaceCode } from '../engineErrorCodes.js';
11
11
  import { denyReasonForWire, isPlanReviewModeAfter } from './hitlBridge.js';
12
- import { engineCapString } from '../engineCapsCache.js';
13
12
  import { planReviewQuestionId, REOPEN_ID_TAIL, taskIdFromPlanReviewQuestionId } from './gateIdentity.js';
14
13
  import { notePlanReviewAnswered, notePlanReviewAnsweredFor, planReviewArmedKeyFor, registerArmedGateFor, waitForGateArmedFor, wasGateArmedFor, } from './armedGateRegistry.js';
15
14
  import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
@@ -20,20 +19,6 @@ export const PLAN_REVIEW_APPROVE_LABEL = 'Yes, approve and run the plan';
20
19
  export const PLAN_REVIEW_REJECT_LABEL = 'No, reject it (keep planning)';
21
20
  export const PLAN_REVIEW_APPROVE_AUTO_LABEL = 'Yes, and auto-accept edits';
22
21
  export const PLAN_REVIEW_APPROVE_MANUAL_LABEL = 'Yes, and manually approve edits';
23
- export const PLAN_REVIEW_MODE_AFTER_MIN_ENGINE = Object.freeze([7, 86, 0]);
24
- export function versionSupportsPlanReviewModeAfter(v) {
25
- const mm = v ? /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(v) : null;
26
- if (!mm)
27
- return 'unknown';
28
- const got = [Number(mm[1]), Number(mm[2]), Number(mm[3])];
29
- for (let i = 0; i < 3; i++) {
30
- const g = got[i];
31
- const need = PLAN_REVIEW_MODE_AFTER_MIN_ENGINE[i];
32
- if (g !== need)
33
- return g > need ? 'supported' : 'unsupported';
34
- }
35
- return 'supported';
36
- }
37
22
  const APPROVE_LABEL = PLAN_REVIEW_APPROVE_LABEL;
38
23
  const REJECT_LABEL = PLAN_REVIEW_REJECT_LABEL;
39
24
  const LEGACY_TWO_CHOICE_LABELS = Object.freeze([PLAN_REVIEW_APPROVE_LABEL, PLAN_REVIEW_REJECT_LABEL]);
@@ -266,9 +251,7 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
266
251
  }
267
252
  armedPlanReviewTaskIds.add(taskId);
268
253
  armedTaskId = taskId;
269
- const capsBaseUrl = wire !== undefined ? wire.capsBaseUrl : engineWireTarget()?.baseUrl;
270
- const offerModeAfter = opts?.submittedInPlanMode === true &&
271
- versionSupportsPlanReviewModeAfter(engineCapString(capsBaseUrl, 'version')) === 'supported';
254
+ const offerModeAfter = opts?.submittedInPlanMode === true;
272
255
  if (offerModeAfter)
273
256
  offerModeAfterByTask.set(taskId, true);
274
257
  else
@@ -648,7 +631,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
648
631
  else if (typeof status === 'number') {
649
632
  const msg = e instanceof Error ? e.message : String(e);
650
633
  hostLog('debug', `planReviewWire: ${decision} → ${status} ${msg.slice(0, 200)}`);
651
- effect = status >= 400 && status < 500 ? 'not_applied' : 'unconfirmed';
634
+ effect = (status >= 400 && status < 500) || (typeof refusedCode === 'string' && isUnavailableFaceCode(refusedCode)) ? 'not_applied' : 'unconfirmed';
652
635
  outcome = `${`The plan_review decision failed: HTTP ${status} ${msg}`.trim()}${decideFailureNote(e)}`;
653
636
  }
654
637
  else if (noAnswer === 'time-limit' || noAnswer === 'deadline') {
@@ -0,0 +1,21 @@
1
+ import { type ApprovalsListEnvelope, type CrashConvergedRow } from './crashConverged.js';
2
+ export type ReplayedApprovalDisposition = {
3
+ readonly kind: 'live';
4
+ readonly basis: 'live_leg' | 'preamble' | 'pending_list';
5
+ } | {
6
+ readonly kind: 'decided';
7
+ readonly cause?: CrashConvergedRow['cause'];
8
+ } | {
9
+ readonly kind: 'unknown';
10
+ readonly why: 'unreadable_frame' | 'no_approval_id' | 'not_read' | 'list_unreadable' | 'list_not_reported' | 'list_incomplete';
11
+ };
12
+ export interface ReplayPendingBasis {
13
+ readonly preamble?: ReadonlySet<string>;
14
+ readonly approvals?: {
15
+ readonly read: 'ok';
16
+ readonly body: ApprovalsListEnvelope;
17
+ } | {
18
+ readonly read: 'failed';
19
+ };
20
+ }
21
+ export declare function replayedApprovalDisposition(frame: object, basis: ReplayPendingBasis): ReplayedApprovalDisposition;
@@ -0,0 +1,89 @@
1
+ import { hasLedgerPositionKey } from './gateLedger.js';
2
+ import { readLivePendingRows } from './livePendingAsk.js';
3
+ import { projectCrashConverged } from './crashConverged.js';
4
+ const live = (basis) => Object.freeze({ kind: 'live', basis });
5
+ const unknown = (why) => Object.freeze({ kind: 'unknown', why });
6
+ const decided = (cause) => Object.freeze(cause !== undefined ? { kind: 'decided', cause } : { kind: 'decided' });
7
+ function approvalIdOf(frame) {
8
+ const v = frame.approvalId;
9
+ return typeof v === 'string' && v !== '' ? v : undefined;
10
+ }
11
+ function crashConvergedCauseOf(body, approvalId) {
12
+ const projection = projectCrashConverged(body);
13
+ if (projection === undefined)
14
+ return undefined;
15
+ for (const row of [...projection.resumeSafe, ...projection.needsHuman]) {
16
+ if (row.approvalId === approvalId)
17
+ return row.cause;
18
+ }
19
+ return undefined;
20
+ }
21
+ export function replayedApprovalDisposition(frame, basis) {
22
+ let approvalId;
23
+ try {
24
+ if (typeof frame !== 'object' || frame === null)
25
+ return unknown('unreadable_frame');
26
+ if (!hasLedgerPositionKey(frame))
27
+ return live('live_leg');
28
+ approvalId = approvalIdOf(frame);
29
+ }
30
+ catch {
31
+ return unknown('unreadable_frame');
32
+ }
33
+ if (approvalId === undefined)
34
+ return unknown('no_approval_id');
35
+ let b;
36
+ let inPreamble = false;
37
+ try {
38
+ b = typeof basis === 'object' && basis !== null ? basis : undefined;
39
+ inPreamble = b?.preamble?.has(approvalId) === true;
40
+ }
41
+ catch {
42
+ inPreamble = false;
43
+ }
44
+ if (inPreamble)
45
+ return live('preamble');
46
+ let approvals;
47
+ try {
48
+ approvals = b?.approvals;
49
+ }
50
+ catch {
51
+ return unknown('not_read');
52
+ }
53
+ if (approvals === undefined || approvals === null)
54
+ return unknown('not_read');
55
+ let body;
56
+ try {
57
+ if (approvals.read !== 'ok')
58
+ return unknown('list_unreadable');
59
+ body = approvals.body;
60
+ }
61
+ catch {
62
+ return unknown('list_unreadable');
63
+ }
64
+ let reading;
65
+ try {
66
+ reading = readLivePendingRows(body);
67
+ }
68
+ catch {
69
+ return unknown('list_unreadable');
70
+ }
71
+ if (reading.kind === 'present' && reading.rows.some((r) => r.approvalId === approvalId))
72
+ return live('pending_list');
73
+ let cause;
74
+ try {
75
+ cause = crashConvergedCauseOf(body, approvalId);
76
+ }
77
+ catch {
78
+ cause = undefined;
79
+ }
80
+ if (cause !== undefined)
81
+ return decided(cause);
82
+ if (reading.kind === 'not_reported')
83
+ return unknown('list_not_reported');
84
+ if (reading.kind === 'malformed')
85
+ return unknown('list_unreadable');
86
+ if (reading.dropped > 0)
87
+ return unknown('list_incomplete');
88
+ return decided();
89
+ }
@@ -1,5 +1,7 @@
1
1
  import { CONFIG_LEGACY_TOOL_NAME, RULES_LEGACY_TOOL_NAME } from '../engineErrorCodes.js';
2
2
  import { rosterMountedNames } from './rosterMountedNames.js';
3
+ import { ruleCompileVerdict } from '../ruleCompileVerdict.js';
4
+ import { protocolSegmentOf } from '../protocolSegment.js';
3
5
  import { PREFIXES_IN_PROSE, REFUSED_NAME_CLASSES, REFUSED_NAME_FIX, RULES_LEGACY_TOOL_NAME_WRITE_NOTE } from './refusedToolNameProse.js';
4
6
  import { CORE_PROTOCOL_PREFIXES, CORE_RETIRED_TOOL_CURRENT_NAMES } from '../generated/toolNameTables.js';
5
7
  const COVERING_NAMESPACE_PREFIXES = CORE_PROTOCOL_PREFIXES;
@@ -9,7 +11,7 @@ const WITHHELD_NOTICE = {
9
11
  qualified_deny: 'Not written to this session’s rule record: that record holds whole tool names and command names only, and this rule has a part in parentheses it cannot carry — recording just the name could block more than this rule does.',
10
12
  peer_wide: 'Not written to this session’s rule record: this rule names a server or agent peer without naming one of its tools, and an engine that matches that record by exact tool name would block none of that peer’s tools.',
11
13
  wildcard: 'Not written to this session’s rule record: this rule uses a wildcard (*), and an engine that matches that record by exact tool name would block none of the tools it covers.',
12
- not_a_tool_name: 'Not written to this session’s rule record: this entry is not exactly one tool name — it is empty, not text, or has spaces around the name.',
14
+ not_a_tool_name: 'Not written to this session’s rule record: this entry is not exactly one tool name — it is empty, not text, has spaces around the name, is not a single tool name in rule syntax (for example it contains a backslash or a parenthesis, or is longer than a rule can be), or spells its server or agent peer part in a way the engine never uses when naming tools (for example with a dot or a space), so no tool would ever match it.',
13
15
  legacy_tool_name: `Not written to this session’s rule record: unless a tool by exactly this name is available, the engine refuses it — it is a retired tool name, or it contains "__" but does not start with ${PREFIXES_IN_PROSE} — and with it in that record every later run of this session would fail at startup until that entry is removed from the record. ${REFUSED_NAME_FIX}`,
14
16
  not_in_roster: 'Not written to this session’s rule record: the tool roster supplied for this check has no tool by exactly this name, as a name or an alias, with letter case counted — it may be misspelled, or name a tool this deployment does not provide — so recorded as it is it would block nothing here.',
15
17
  };
@@ -97,7 +99,23 @@ export function sessionPolicyDeliverable(behavior, rules, opts) {
97
99
  withheld.push({ rule: '', why: 'not_a_tool_name' });
98
100
  continue;
99
101
  }
100
- const why = withheldWhyOf(r) ?? (mounted !== undefined && !mounted.has(r) ? 'not_in_roster' : undefined);
102
+ let why = withheldWhyOf(r);
103
+ if (why === undefined) {
104
+ const grammar = ruleCompileVerdict(r, 'deny');
105
+ if (grammar === 'unknown')
106
+ return unreadable();
107
+ if (grammar === 'refused')
108
+ why = 'not_a_tool_name';
109
+ }
110
+ if (why === undefined && COVERING_NAMESPACE_PREFIXES.some((p) => r.startsWith(p))) {
111
+ const segment = protocolSegmentOf(r);
112
+ if (segment.kind === 'empty' || segment.kind === 'unminted')
113
+ why = 'not_a_tool_name';
114
+ else if (segment.kind === 'unknown')
115
+ return unreadable();
116
+ }
117
+ if (why === undefined && mounted !== undefined && !mounted.has(r))
118
+ why = 'not_in_roster';
101
119
  if (why === undefined)
102
120
  exact.push(r);
103
121
  else {
@@ -1,6 +1,8 @@
1
1
  import type { SessionPermissionRules, StoredSessionRules } from '@sema-agent/sdk';
2
2
  import type { SessionPolicyReading } from '../sessionPolicyCapability.js';
3
3
  import type { ToolRosterView } from '../toolRoster.js';
4
+ import { CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
5
+ export type LegacyToolNameVerdict = Exclude<(typeof CORE_RULE_TOOL_NAME_VERDICTS)[number], 'current'>;
4
6
  export interface SessionPolicyFacade {
5
7
  getPolicy(sessionId: string, opts?: {
6
8
  signal?: AbortSignal;
@@ -43,6 +45,8 @@ export type SessionPolicyFailure = {
43
45
  message: string;
44
46
  field?: SessionPolicyRemovedEntry['field'];
45
47
  name?: string;
48
+ verdict?: LegacyToolNameVerdict;
49
+ currentName?: string;
46
50
  } | {
47
51
  kind: 'unauthorized';
48
52
  message: string;
@@ -95,6 +99,8 @@ export type SessionPolicyTightenOutcome = {
95
99
  message: string;
96
100
  field?: SessionPolicyRemovedEntry['field'];
97
101
  name?: string;
102
+ verdict?: LegacyToolNameVerdict;
103
+ currentName?: string;
98
104
  alreadyInRecord?: boolean;
99
105
  } | {
100
106
  kind: 'unknown';
@@ -109,6 +115,8 @@ export declare function sessionPolicyReadNotice(outcome: Exclude<SessionPolicyRe
109
115
  kind: 'present';
110
116
  }> | {
111
117
  kind: 'no_target';
118
+ } | {
119
+ kind: 'not_started';
112
120
  }): string;
113
121
  export interface SessionPolicyRemovedEntry {
114
122
  readonly field: 'toolDeny' | 'toolAllow';
@@ -148,6 +156,8 @@ export type SessionPolicyRemovalOutcome = {
148
156
  message: string;
149
157
  field?: SessionPolicyRemovedEntry['field'];
150
158
  name?: string;
159
+ verdict?: LegacyToolNameVerdict;
160
+ currentName?: string;
151
161
  };
152
162
  export type SessionPolicyRosterRequiredWhy = 'absent' | 'unreadable';
153
163
  export declare function removeRefusedSessionRules(facade: SessionPolicyFacade, sessionId: string, roster: ToolRosterView | undefined, opts?: {
@@ -4,6 +4,10 @@ import { isEngineRefusedToolName } from './sessionPolicyDeliverable.js';
4
4
  import { RULES_LEGACY_TOOL_NAME_WRITE_NOTE } from './refusedToolNameProse.js';
5
5
  import { RULES_LEGACY_TOOL_NAME, isRulesRefusalCode } from '../engineErrorCodes.js';
6
6
  import { rosterMountedNames } from './rosterMountedNames.js';
7
+ import { CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
8
+ function isLegacyToolNameVerdict(v) {
9
+ return typeof v === 'string' && v !== 'current' && CORE_RULE_TOOL_NAME_VERDICTS.includes(v);
10
+ }
7
11
  const RULE_FIELD_PRESENCE = {
8
12
  toolAllow: true,
9
13
  toolDeny: true,
@@ -14,7 +18,7 @@ const RULE_FIELD_PRESENCE = {
14
18
  export const SESSION_POLICY_RULE_FIELDS = Object.freeze(Object.keys(RULE_FIELD_PRESENCE));
15
19
  const MAX_RULE_ENTRIES = 10_000;
16
20
  export function classifySessionPolicyFailure(e) {
17
- const { status, errorCode, message, bodyField, bodyName } = wireFailureShapeOf(e);
21
+ const { status, errorCode, message, bodyField, bodyName, bodyVerdict, bodyCurrentName } = wireFailureShapeOf(e);
18
22
  if (errorCode === undefined || errorCode === '')
19
23
  return { kind: 'no-verdict', message };
20
24
  if (errorCode === RULES_LEGACY_TOOL_NAME) {
@@ -23,6 +27,8 @@ export function classifySessionPolicyFailure(e) {
23
27
  message,
24
28
  ...(bodyField === 'toolDeny' || bodyField === 'toolAllow' ? { field: bodyField } : {}),
25
29
  ...(bodyName !== undefined && bodyName !== '' ? { name: bodyName } : {}),
30
+ ...(isLegacyToolNameVerdict(bodyVerdict) ? { verdict: bodyVerdict } : {}),
31
+ ...(bodyCurrentName !== undefined && bodyCurrentName !== '' ? { currentName: bodyCurrentName } : {}),
26
32
  };
27
33
  }
28
34
  if (isRulesRefusalCode(errorCode) && status !== undefined && status >= 400 && status < 500)
@@ -227,7 +233,7 @@ function unknownFrom(failure, leg) {
227
233
  }
228
234
  }
229
235
  function legacyToolNameOutcome(failure, prior, add) {
230
- const { field, name } = failure;
236
+ const { field, name, verdict, currentName } = failure;
231
237
  let alreadyInRecord;
232
238
  if (field !== undefined && name !== undefined) {
233
239
  const inPrior = Object.hasOwn(prior, field) ? prior[field] : undefined;
@@ -242,6 +248,8 @@ function legacyToolNameOutcome(failure, prior, add) {
242
248
  message: failure.message,
243
249
  ...(field !== undefined ? { field } : {}),
244
250
  ...(name !== undefined ? { name } : {}),
251
+ ...(verdict !== undefined ? { verdict } : {}),
252
+ ...(currentName !== undefined ? { currentName } : {}),
245
253
  ...(alreadyInRecord !== undefined ? { alreadyInRecord } : {}),
246
254
  };
247
255
  }
@@ -320,7 +328,7 @@ export function sessionPolicyTightenNotice(outcome) {
320
328
  case 'face_unavailable':
321
329
  return 'This deployment does not keep per-session tool rules, so nothing was written.';
322
330
  case 'session_not_found':
323
- return 'This session is not readable from here — it may not exist, or it may belong to someone else; nothing was written.';
331
+ return 'This session is not readable from here — it may not exist, or it may belong to someone else (the engine answers both the same way, so this client cannot tell which); nothing was written.';
324
332
  case 'session_ownerless':
325
333
  return 'This session has no owner, so a rule written for it could never be read back; nothing was written. Attach an identity to the session and try again.';
326
334
  case 'unauthorized':
@@ -346,6 +354,7 @@ export function sessionPolicyTightenNotice(outcome) {
346
354
  }
347
355
  const READ_NOTICE_GENERIC = 'This session’s rule record could not be shown, so what it holds is not known. Try reading it again.';
348
356
  const READ_NOTICE_NO_TARGET = 'There is no engine connection for this session, so its rule record cannot be read and what it holds is not known. Look again once it is connected.';
357
+ const READ_NOTICE_NOT_STARTED = 'This session has not begun on the engine yet — nothing has been sent in it — so it has no rule record to read. Send a first message, then look again.';
349
358
  function readFailureSentence(kind) {
350
359
  switch (kind) {
351
360
  case 'face-unavailable':
@@ -359,7 +368,7 @@ function readFailureSentence(kind) {
359
368
  case 'session-ownerless':
360
369
  return 'This session has no owner, so it has no rule record to read.';
361
370
  case 'session-not-found':
362
- return 'This session is not readable from here — it may not exist, or it may belong to someone else — so its rule record cannot be shown. Check that you are connected to the engine and the session you meant, or continue in a new session.';
371
+ return 'This session is not readable from here — it may not exist, or it may belong to someone else (the engine answers both the same way, so this client cannot tell which) — so its rule record cannot be shown. Check that you are connected to the engine and the session you meant, or continue in a new session.';
363
372
  case 'request-rejected':
364
373
  return 'The engine rejected the request to read this session’s rule record as malformed, so it could not be read. Sending the same request again will not change that; check which session was asked for.';
365
374
  case 'legacy-tool-name':
@@ -401,6 +410,8 @@ export function sessionPolicyReadNotice(outcome) {
401
410
  return readMalformedSentence(ownRead(outcome, 'why')) ?? READ_NOTICE_GENERIC;
402
411
  if (arm === 'no_target')
403
412
  return READ_NOTICE_NO_TARGET;
413
+ if (arm === 'not_started')
414
+ return READ_NOTICE_NOT_STARTED;
404
415
  return READ_NOTICE_GENERIC;
405
416
  }
406
417
  catch {
@@ -488,6 +499,8 @@ async function attemptRemoval(facade, sessionId, mounted, opts) {
488
499
  message: failure.message,
489
500
  ...(failure.field !== undefined ? { field: failure.field } : {}),
490
501
  ...(failure.name !== undefined ? { name: failure.name } : {}),
502
+ ...(failure.verdict !== undefined ? { verdict: failure.verdict } : {}),
503
+ ...(failure.currentName !== undefined ? { currentName: failure.currentName } : {}),
491
504
  },
492
505
  };
493
506
  }
@@ -559,7 +572,7 @@ export function sessionPolicyRemovalNotice(outcome) {
559
572
  case 'face_unavailable':
560
573
  return 'This deployment does not keep per-session tool rules, so there is no session rule record to remove entries from — nothing was changed.';
561
574
  case 'session_not_found':
562
- return 'This session is not readable from here — it may not exist, or it may belong to someone else; nothing was changed.';
575
+ return 'This session is not readable from here — it may not exist, or it may belong to someone else (the engine answers both the same way, so this client cannot tell which); nothing was changed.';
563
576
  case 'session_ownerless':
564
577
  return 'This session has no owner, so it has no session rule record to change; nothing was changed.';
565
578
  case 'unauthorized':
@@ -576,7 +589,7 @@ export function sessionPolicyRemovalNotice(outcome) {
576
589
  case 'store_corrupt':
577
590
  return 'The engine could not establish which version this session’s rules are at and refused to stamp a new one, so nothing was changed. This needs an operator to look at the rule store.';
578
591
  case 'session_not_found':
579
- return 'This session is not writable from here — it may not exist, or it may belong to someone else; nothing was changed.';
592
+ return 'This session is not writable from here — it may not exist, or it may belong to someone else (the engine answers both the same way, so this client cannot tell which); nothing was changed.';
580
593
  case 'unauthorized':
581
594
  return 'This call could not present a verifiable identity, so nothing was changed.';
582
595
  default: