@sema-agent/client-core 0.97.1 → 0.99.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 (353) hide show
  1. package/CHANGELOG.md +607 -0
  2. package/README.md +96 -46
  3. package/dist/adapt/arms.d.ts +1 -0
  4. package/dist/adapt/arms.js +188 -53
  5. package/dist/adapt/ids.d.ts +12 -1
  6. package/dist/adapt/ids.js +59 -8
  7. package/dist/adapt/instanceLedger.d.ts +50 -0
  8. package/dist/adapt/instanceLedger.js +147 -0
  9. package/dist/adapt/panelTasks.d.ts +1 -1
  10. package/dist/adapt/panelTasks.js +2 -2
  11. package/dist/adapt/textStream.d.ts +1 -0
  12. package/dist/adapt/textStream.js +24 -7
  13. package/dist/adapt/toolCards.d.ts +27 -5
  14. package/dist/adapt/toolCards.js +39 -6
  15. package/dist/adapt/wireShapes.js +6 -2
  16. package/dist/adapt.d.ts +2 -0
  17. package/dist/adapt.js +8 -3
  18. package/dist/adapter/activeRunSelfHeal.d.ts +9 -3
  19. package/dist/adapter/activeRunSelfHeal.js +205 -313
  20. package/dist/adapter/downstream/eventToSdkMessage.d.ts +2 -5
  21. package/dist/adapter/downstream/eventToSdkMessage.js +77 -36
  22. package/dist/adapter/downstream/terminalToSdkResult.js +11 -4
  23. package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +5 -2
  24. package/dist/adapter/downstream/turnUsageToModelUsage.js +2 -0
  25. package/dist/adapter/runStream.js +79 -48
  26. package/dist/adapter/types.d.ts +4 -4
  27. package/dist/adapter/types.js +2 -1
  28. package/dist/agentSession/backgroundView.js +2 -1
  29. package/dist/agentSession/engineAgentRegistryRead.d.ts +2 -2
  30. package/dist/agentSession/engineAgentRegistryRead.js +12 -13
  31. package/dist/agentSession/engineSessionBackgroundStop.js +16 -8
  32. package/dist/agentsWireCaps.d.ts +8 -11
  33. package/dist/agentsWireCaps.js +196 -194
  34. package/dist/approvalDecidedByCapability.d.ts +0 -4
  35. package/dist/approvalDecidedByCapability.js +0 -6
  36. package/dist/approvalsStreamLiveCapability.d.ts +0 -4
  37. package/dist/approvalsStreamLiveCapability.js +0 -6
  38. package/dist/assistantInbox.d.ts +34 -0
  39. package/dist/assistantInbox.js +118 -0
  40. package/dist/centerWiringCapability.js +9 -1
  41. package/dist/clientSlice.d.ts +0 -9
  42. package/dist/clientSlice.js +0 -3
  43. package/dist/cloudConfigWireCaps.d.ts +5 -3
  44. package/dist/cloudConfigWireCaps.js +106 -64
  45. package/dist/compensations.js +1 -1
  46. package/dist/connectorStatus.d.ts +21 -0
  47. package/dist/connectorStatus.js +112 -0
  48. package/dist/controlRouter.d.ts +18 -3
  49. package/dist/controlRouter.js +106 -67
  50. package/dist/coreValuePorts.d.ts +29 -4
  51. package/dist/coreValuePorts.js +10 -0
  52. package/dist/corruptSessionCopy.d.ts +2 -1
  53. package/dist/corruptSessionCopy.js +3 -3
  54. package/dist/decideReceipt.d.ts +2 -2
  55. package/dist/decideReceipt.js +5 -2
  56. package/dist/declared/supportFloorDeclared.js +3 -3
  57. package/dist/detachWire.js +3 -2
  58. package/dist/deviceExecutorManagementCapability.d.ts +0 -4
  59. package/dist/deviceExecutorManagementCapability.js +0 -6
  60. package/dist/deviceRebind.d.ts +49 -1
  61. package/dist/deviceRebind.js +221 -3
  62. package/dist/devicesFace.d.ts +34 -0
  63. package/dist/devicesFace.js +90 -0
  64. package/dist/displayUntrusted.d.ts +2 -0
  65. package/dist/displayUntrusted.js +253 -76
  66. package/dist/engineAgentAbsence.d.ts +1 -1
  67. package/dist/engineAgentPanelStore.d.ts +12 -4
  68. package/dist/engineAgentPanelStore.js +16 -11
  69. package/dist/engineCapReader.d.ts +2 -5
  70. package/dist/engineCapReader.js +8 -10
  71. package/dist/engineCapsCache.d.ts +12 -0
  72. package/dist/engineCapsCache.js +76 -18
  73. package/dist/engineCapsChange.d.ts +2 -0
  74. package/dist/engineCapsChange.js +9 -0
  75. package/dist/engineClosedSets.d.ts +1 -0
  76. package/dist/engineClosedSets.js +4 -1
  77. package/dist/engineErrorCodes.d.ts +11 -3
  78. package/dist/engineErrorCodes.js +11 -8
  79. package/dist/engineIdentity.d.ts +1 -0
  80. package/dist/engineIdentity.js +8 -0
  81. package/dist/engineRowRef.d.ts +28 -0
  82. package/dist/engineRowRef.js +114 -0
  83. package/dist/engineSessionMessages.d.ts +68 -0
  84. package/dist/engineSessionMessages.js +370 -0
  85. package/dist/engineTargetSeat.d.ts +27 -0
  86. package/dist/engineTargetSeat.js +199 -0
  87. package/dist/engineToolDirectory.d.ts +0 -9
  88. package/dist/engineToolDirectory.js +1 -94
  89. package/dist/engineUrl.d.ts +18 -0
  90. package/dist/engineUrl.js +52 -0
  91. package/dist/engineWireFor.js +3 -8
  92. package/dist/env/uiLanguage.d.ts +11 -1
  93. package/dist/env/uiLanguage.js +38 -2
  94. package/dist/eventLeg.d.ts +100 -0
  95. package/dist/eventLeg.js +305 -0
  96. package/dist/executionLaneCapability.d.ts +0 -4
  97. package/dist/executionLaneCapability.js +3 -16
  98. package/dist/fileHistoryCaptureCapability.d.ts +2 -5
  99. package/dist/fileHistoryCaptureCapability.js +13 -21
  100. package/dist/fileHistoryLegNotice.d.ts +2 -1
  101. package/dist/fileHistoryLegNotice.js +3 -3
  102. package/dist/fleet/fleetLedger.d.ts +0 -3
  103. package/dist/fleet/fleetLedger.js +21 -36
  104. package/dist/fleet/fleetProjection.d.ts +2 -2
  105. package/dist/fleet/fleetProjection.js +10 -8
  106. package/dist/frameBinding.d.ts +8 -0
  107. package/dist/frameBinding.js +35 -0
  108. package/dist/gateFailureCodes.d.ts +2 -0
  109. package/dist/gateFailureCodes.js +15 -0
  110. package/dist/gateOutcome.d.ts +3 -1
  111. package/dist/gateOutcome.js +41 -8
  112. package/dist/gateVocabulary.d.ts +10 -4
  113. package/dist/gateVocabulary.js +107 -47
  114. package/dist/generated/engineAskFaceTables.js +2 -2
  115. package/dist/generated/engineFactTables.d.ts +22 -3
  116. package/dist/generated/engineFactTables.js +51 -4
  117. package/dist/generated/engineNoticeTables.js +18 -3
  118. package/dist/generated/settingsSchemaFns.d.ts +6 -0
  119. package/dist/generated/settingsSchemaFns.js +57 -0
  120. package/dist/generated/settingsSchemaHooksClassifier.d.ts +2 -0
  121. package/dist/generated/settingsSchemaHooksClassifier.js +254 -0
  122. package/dist/generated/settingsSchemaTables.d.ts +1 -0
  123. package/dist/generated/settingsSchemaTables.js +4 -0
  124. package/dist/generated/settingsSchemaTypes.d.ts +105 -0
  125. package/dist/generated/settingsSchemaTypes.js +1 -0
  126. package/dist/generated/toolNameTables.d.ts +1 -0
  127. package/dist/generated/toolNameTables.js +1 -0
  128. package/dist/hitl/allowRuleShadow.d.ts +8 -2
  129. package/dist/hitl/allowRuleShadow.js +36 -29
  130. package/dist/hitl/approvalCardReading.d.ts +15 -0
  131. package/dist/hitl/approvalCardReading.js +154 -0
  132. package/dist/hitl/approvalCardReasons.d.ts +14 -0
  133. package/dist/hitl/approvalCardReasons.js +119 -0
  134. package/dist/hitl/approvalHolds.d.ts +50 -15
  135. package/dist/hitl/approvalHolds.js +177 -27
  136. package/dist/hitl/approvalResolution.d.ts +51 -26
  137. package/dist/hitl/approvalResolution.js +86 -122
  138. package/dist/hitl/approvalsFeed.js +6 -18
  139. package/dist/hitl/askClassPair.d.ts +2 -0
  140. package/dist/hitl/askClassPair.js +17 -0
  141. package/dist/hitl/askGateWire.d.ts +20 -2
  142. package/dist/hitl/askGateWire.js +50 -1
  143. package/dist/hitl/askParkRowRouting.d.ts +5 -0
  144. package/dist/hitl/askParkRowRouting.js +32 -1
  145. package/dist/hitl/askSettlementLine.d.ts +28 -0
  146. package/dist/hitl/askSettlementLine.js +99 -0
  147. package/dist/hitl/automaticAnswerVerdict.d.ts +3 -2
  148. package/dist/hitl/automaticAnswerVerdict.js +29 -13
  149. package/dist/hitl/crashConverged.d.ts +11 -20
  150. package/dist/hitl/crashConverged.js +56 -0
  151. package/dist/hitl/crashConvergedNotice.d.ts +6 -3
  152. package/dist/hitl/crashConvergedNotice.js +48 -24
  153. package/dist/hitl/decisionConflict.js +3 -10
  154. package/dist/hitl/decisionRefusal.d.ts +40 -1
  155. package/dist/hitl/decisionRefusal.js +199 -42
  156. package/dist/hitl/frameRouter.d.ts +5 -2
  157. package/dist/hitl/frameRouter.js +27 -24
  158. package/dist/hitl/gateLedger.d.ts +2 -0
  159. package/dist/hitl/gateLedger.js +50 -13
  160. package/dist/hitl/hitlBridge.d.ts +2 -2
  161. package/dist/hitl/hitlBridge.js +13 -10
  162. package/dist/hitl/livePendingAsk.d.ts +3 -3
  163. package/dist/hitl/livePendingAsk.js +7 -18
  164. package/dist/hitl/parkResolver.d.ts +2 -24
  165. package/dist/hitl/parkResolver.js +61 -97
  166. package/dist/hitl/persistedRulesWire.d.ts +25 -0
  167. package/dist/hitl/persistedRulesWire.js +104 -2
  168. package/dist/hitl/planReviewWire.d.ts +31 -13
  169. package/dist/hitl/planReviewWire.js +120 -78
  170. package/dist/hitl/refusedToolNameProse.d.ts +0 -1
  171. package/dist/hitl/refusedToolNameProse.js +0 -3
  172. package/dist/hitl/replayedApproval.d.ts +4 -3
  173. package/dist/hitl/replayedApproval.js +7 -4
  174. package/dist/hitl/sessionPolicyDeliverable.d.ts +2 -0
  175. package/dist/hitl/sessionPolicyDeliverable.js +48 -7
  176. package/dist/hitl/sessionPolicyWire.d.ts +34 -21
  177. package/dist/hitl/sessionPolicyWire.js +179 -70
  178. package/dist/hitl/standingRows.d.ts +20 -2
  179. package/dist/hitl/standingRows.js +69 -8
  180. package/dist/hitl/suspendedReopen.d.ts +2 -1
  181. package/dist/hitl/suspendedReopen.js +7 -10
  182. package/dist/hitl/toolApprovalWire.d.ts +103 -156
  183. package/dist/hitl/toolApprovalWire.js +661 -411
  184. package/dist/hookMatcherVerdict.d.ts +6 -1
  185. package/dist/hookMatcherVerdict.js +14 -7
  186. package/dist/hooksWireCaps.d.ts +2 -2
  187. package/dist/hooksWireCaps.js +174 -81
  188. package/dist/host.d.ts +21 -0
  189. package/dist/hostTurnSettings.d.ts +71 -0
  190. package/dist/hostTurnSettings.js +291 -0
  191. package/dist/humanTurn.d.ts +14 -1
  192. package/dist/humanTurn.js +100 -12
  193. package/dist/humanTurnRead.d.ts +2 -0
  194. package/dist/humanTurnRead.js +22 -1
  195. package/dist/i18n/messages.en.d.ts +703 -0
  196. package/dist/i18n/messages.en.js +701 -0
  197. package/dist/i18n/messages.zh.d.ts +3 -0
  198. package/dist/i18n/messages.zh.js +837 -0
  199. package/dist/i18n/translate.d.ts +25 -0
  200. package/dist/i18n/translate.js +127 -0
  201. package/dist/index.d.ts +78 -29
  202. package/dist/index.js +54 -18
  203. package/dist/inheritEnvCapability.d.ts +32 -0
  204. package/dist/inheritEnvCapability.js +104 -0
  205. package/dist/inheritEnvWire.d.ts +3 -2
  206. package/dist/inheritEnvWire.js +12 -14
  207. package/dist/interactiveHalt.js +17 -6
  208. package/dist/liveModelCatalog.d.ts +3 -3
  209. package/dist/liveModelCatalog.js +15 -13
  210. package/dist/liveQuestionStore.d.ts +2 -21
  211. package/dist/mcpLiveness.d.ts +2 -1
  212. package/dist/mcpPanel.d.ts +10 -1
  213. package/dist/mcpPanel.js +40 -13
  214. package/dist/mcpProbeCapability.d.ts +2 -1
  215. package/dist/mcpProbeCapability.js +7 -11
  216. package/dist/mcpProbeWire.d.ts +9 -2
  217. package/dist/mcpProbeWire.js +6 -1
  218. package/dist/mcpWireCaps.d.ts +47 -2
  219. package/dist/mcpWireCaps.js +164 -16
  220. package/dist/memoryEntriesWire.d.ts +4 -9
  221. package/dist/memoryEntriesWire.js +5 -10
  222. package/dist/model/modelSupplyRules.d.ts +0 -2
  223. package/dist/model/modelSupplyRules.js +3 -12
  224. package/dist/model/modelsPolicy.d.ts +0 -5
  225. package/dist/model/modelsPolicy.js +6 -36
  226. package/dist/model/onboardJudgement.d.ts +93 -0
  227. package/dist/model/onboardJudgement.js +415 -0
  228. package/dist/model/subagentModelWord.d.ts +31 -0
  229. package/dist/model/subagentModelWord.js +261 -0
  230. package/dist/model/tierVocabulary.d.ts +2 -0
  231. package/dist/model/tierVocabulary.js +17 -5
  232. package/dist/modelCapabilityProbe.d.ts +64 -0
  233. package/dist/modelCapabilityProbe.js +600 -0
  234. package/dist/modelIdentityResolvability.d.ts +3 -0
  235. package/dist/modelIdentityResolvability.js +26 -17
  236. package/dist/notifications.d.ts +6 -0
  237. package/dist/notifications.js +71 -23
  238. package/dist/outputStyleSeatCapability.d.ts +0 -3
  239. package/dist/outputStyleSeatCapability.js +0 -3
  240. package/dist/ownKey.d.ts +22 -0
  241. package/dist/ownKey.js +94 -3
  242. package/dist/panelReduce.d.ts +21 -10
  243. package/dist/panelReduce.js +728 -550
  244. package/dist/panelStateHolder.d.ts +3 -7
  245. package/dist/panelStateHolder.js +14 -130
  246. package/dist/peerLaneCapability.d.ts +0 -4
  247. package/dist/peerLaneCapability.js +0 -6
  248. package/dist/permissionRuleIssue.js +2 -0
  249. package/dist/permissionRulesWriteCapability.d.ts +2 -5
  250. package/dist/permissionRulesWriteCapability.js +8 -12
  251. package/dist/permissionWireCaps.d.ts +2 -2
  252. package/dist/permissionWireCaps.js +2 -0
  253. package/dist/permissionsAllowGrantCapability.d.ts +0 -4
  254. package/dist/permissionsAllowGrantCapability.js +0 -6
  255. package/dist/permissionsDeclaration.d.ts +0 -11
  256. package/dist/permissionsDeclaration.js +60 -53
  257. package/dist/pluginHooksWire.d.ts +46 -3
  258. package/dist/pluginHooksWire.js +219 -16
  259. package/dist/preemptWire.d.ts +44 -0
  260. package/dist/preemptWire.js +83 -0
  261. package/dist/printModelStatus.d.ts +63 -0
  262. package/dist/printModelStatus.js +155 -0
  263. package/dist/printToolResultFrame.js +15 -4
  264. package/dist/replayBody.d.ts +3 -0
  265. package/dist/replayBody.js +14 -0
  266. package/dist/request/taskRequest.d.ts +11 -6
  267. package/dist/request/taskRequest.js +112 -19
  268. package/dist/resumeRefusalCopy.d.ts +13 -9
  269. package/dist/resumeRefusalCopy.js +81 -118
  270. package/dist/retryStatus.d.ts +45 -0
  271. package/dist/retryStatus.js +185 -1
  272. package/dist/rewindArchiveCapability.d.ts +0 -4
  273. package/dist/rewindArchiveCapability.js +2 -10
  274. package/dist/rewindArchiveReading.d.ts +0 -1
  275. package/dist/rewindArchiveReading.js +0 -3
  276. package/dist/runCancelContext.d.ts +2 -1
  277. package/dist/runCancelContext.js +4 -2
  278. package/dist/runReattachPolicy.d.ts +31 -0
  279. package/dist/runReattachPolicy.js +46 -0
  280. package/dist/runTerminal.d.ts +3 -6
  281. package/dist/runTerminal.js +8 -9
  282. package/dist/sandboxWire.d.ts +3 -1
  283. package/dist/sandboxWire.js +76 -27
  284. package/dist/scenarioRefusal.d.ts +9 -0
  285. package/dist/scenarioRefusal.js +111 -0
  286. package/dist/scenarioWire.js +1 -1
  287. package/dist/seam.d.ts +48 -2
  288. package/dist/seam.js +25 -4
  289. package/dist/seatContract.d.ts +311 -18
  290. package/dist/seatContract.js +824 -100
  291. package/dist/selfOrchestrationDenial.js +7 -19
  292. package/dist/sessionIdSegment.d.ts +6 -0
  293. package/dist/sessionIdSegment.js +24 -0
  294. package/dist/sessionMemoryStatus.js +6 -11
  295. package/dist/sessionPolicyCapability.d.ts +2 -5
  296. package/dist/sessionPolicyCapability.js +8 -12
  297. package/dist/sessionSyncFileHistoryCapability.d.ts +0 -3
  298. package/dist/sessionSyncFileHistoryCapability.js +0 -3
  299. package/dist/settingsHooksSalvage.d.ts +44 -0
  300. package/dist/settingsHooksSalvage.js +211 -0
  301. package/dist/skillToolListsCapability.d.ts +2 -4
  302. package/dist/skillToolListsCapability.js +8 -9
  303. package/dist/sqlEngineCapability.d.ts +0 -4
  304. package/dist/sqlEngineCapability.js +0 -6
  305. package/dist/sseIdleTriage.js +3 -2
  306. package/dist/steerPriorityCapability.d.ts +0 -3
  307. package/dist/steerPriorityCapability.js +0 -3
  308. package/dist/streamDrainingLegs.d.ts +4 -0
  309. package/dist/streamDrainingLegs.js +6 -1
  310. package/dist/subagent/engineCompactWire.d.ts +0 -1
  311. package/dist/subagent/engineCompactWire.js +7 -5
  312. package/dist/subagent/engineSubagentResume.js +15 -9
  313. package/dist/subagent/engineTaskHandleWire.js +4 -7
  314. package/dist/subagentContentStore.d.ts +3 -0
  315. package/dist/subagentContentStore.js +22 -10
  316. package/dist/submitReopen.d.ts +24 -0
  317. package/dist/submitReopen.js +75 -0
  318. package/dist/terminalStatuses.d.ts +3 -0
  319. package/dist/terminalStatuses.js +10 -2
  320. package/dist/toolHistoryMismatch.js +3 -2
  321. package/dist/toolResult.d.ts +10 -1
  322. package/dist/toolResult.js +34 -6
  323. package/dist/toolRoster.d.ts +3 -4
  324. package/dist/toolRoster.js +21 -22
  325. package/dist/turnFailurePresentation.d.ts +39 -0
  326. package/dist/turnFailurePresentation.js +402 -0
  327. package/dist/usageAnalytics.js +6 -11
  328. package/dist/userAttachmentEnvelope.d.ts +1 -0
  329. package/dist/userAttachmentEnvelope.js +37 -7
  330. package/dist/userAttachmentSeat.d.ts +21 -0
  331. package/dist/userAttachmentSeat.js +75 -0
  332. package/dist/userInvokedSkillsCapability.d.ts +11 -4
  333. package/dist/userInvokedSkillsCapability.js +31 -10
  334. package/dist/webSearchBackendCapability.d.ts +0 -4
  335. package/dist/webSearchBackendCapability.js +0 -6
  336. package/dist/wireErrorTriage.d.ts +7 -0
  337. package/dist/wireErrorTriage.js +26 -23
  338. package/dist/wireFailureShape.d.ts +62 -5
  339. package/dist/wireFailureShape.js +106 -5
  340. package/dist/wireRefusalCopy.d.ts +25 -3
  341. package/dist/wireRefusalCopy.js +74 -80
  342. package/dist/workflowsJournalCapability.d.ts +2 -5
  343. package/dist/workflowsJournalCapability.js +8 -12
  344. package/dist/writeProtectionCapability.d.ts +0 -4
  345. package/dist/writeProtectionCapability.js +0 -6
  346. package/docs/INTEGRATION-CLIENTS.md +1775 -56
  347. package/package.json +6 -5
  348. package/dist/attachmentSeatCapability.d.ts +0 -12
  349. package/dist/attachmentSeatCapability.js +0 -23
  350. package/dist/hitl/approvalOutcomeNote.d.ts +0 -10
  351. package/dist/hitl/approvalOutcomeNote.js +0 -29
  352. package/dist/sessionReadRedactionCapability.d.ts +0 -13
  353. package/dist/sessionReadRedactionCapability.js +0 -35
@@ -1,8 +1,10 @@
1
- import { CONFIG_LEGACY_TOOL_NAME, RULES_LEGACY_TOOL_NAME } from '../engineErrorCodes.js';
1
+ import { CONFIG_LEGACY_TOOL_NAME } from '../engineErrorCodes.js';
2
2
  import { rosterMountedNames } from '../rosterMountedNames.js';
3
3
  import { ruleCompileVerdict } from '../ruleCompileVerdict.js';
4
+ import { readRuleEntry } from '../coreValuePorts.js';
5
+ import { readOwnDataValue } from '../ownKey.js';
4
6
  import { ruleToolNameVerdictOf } from '../protocolSegment.js';
5
- import { PREFIXES_IN_PROSE, REFUSED_NAME_CLASSES, REFUSED_NAME_FIX, RULES_LEGACY_TOOL_NAME_WRITE_NOTE } from './refusedToolNameProse.js';
7
+ import { PREFIXES_IN_PROSE, REFUSED_NAME_CLASSES, REFUSED_NAME_FIX } from './refusedToolNameProse.js';
6
8
  import { CORE_PROTOCOL_PREFIXES } from '../generated/toolNameTables.js';
7
9
  const COVERING_NAMESPACE_PREFIXES = CORE_PROTOCOL_PREFIXES;
8
10
  const WITHHELD_NOTICE = {
@@ -90,8 +92,9 @@ export function sessionPolicyDeliverable(behavior, rules, opts) {
90
92
  continue;
91
93
  }
92
94
  let why = withheldWhyOf(r);
95
+ const engineAsked = why === undefined;
93
96
  let currentName;
94
- if (why === undefined) {
97
+ if (engineAsked) {
95
98
  const verdict = ruleToolNameVerdictOf(r);
96
99
  if (verdict.kind === 'unknown')
97
100
  return unreadable();
@@ -103,6 +106,17 @@ export function sessionPolicyDeliverable(behavior, rules, opts) {
103
106
  else if (verdict.kind === 'unmintable_segment')
104
107
  why = 'not_a_tool_name';
105
108
  }
109
+ let inertReason;
110
+ if (engineAsked) {
111
+ const reading = entryReadingOf(r);
112
+ if (reading === 'unknown')
113
+ return unreadable();
114
+ if (reading !== 'row') {
115
+ inertReason = reading.reason;
116
+ if (why === undefined)
117
+ why = reading.why;
118
+ }
119
+ }
106
120
  if (why === undefined) {
107
121
  const grammar = ruleCompileVerdict(r, 'deny').kind;
108
122
  if (grammar === 'unknown')
@@ -115,13 +129,42 @@ export function sessionPolicyDeliverable(behavior, rules, opts) {
115
129
  if (why === undefined)
116
130
  exact.push(r);
117
131
  else {
118
- withheld.push(why === 'legacy_tool_name' && currentName !== undefined ? { rule: r, why, currentName } : { rule: r, why });
132
+ withheld.push({
133
+ rule: r,
134
+ why,
135
+ ...(why === 'legacy_tool_name' && currentName !== undefined ? { currentName } : {}),
136
+ ...(inertReason !== undefined ? { inertReason } : {}),
137
+ });
119
138
  }
120
139
  }
121
- if (withheld.length > 0 || exact.length === 0)
140
+ const blocking = withheld.some((w) => w.inertReason === undefined);
141
+ if (blocking || exact.length === 0)
122
142
  return { deliverable: {}, withheld };
123
143
  return { deliverable: { toolDeny: exact }, withheld };
124
144
  }
145
+ function entryReadingOf(rule) {
146
+ try {
147
+ const reading = readRuleEntry(rule, 'deny');
148
+ if (typeof reading !== 'object' || reading === null)
149
+ return 'unknown';
150
+ const kind = readOwnDataValue(reading, 'kind');
151
+ if (kind === 'row') {
152
+ const compiled = readOwnDataValue(reading, 'compiled');
153
+ return typeof compiled === 'object' && compiled !== null ? 'row' : 'unknown';
154
+ }
155
+ if (kind !== 'inert')
156
+ return 'unknown';
157
+ const reason = readOwnDataValue(reading, 'reason');
158
+ if (reason === 'grammar' || reason === 'content_unsupported')
159
+ return { why: 'not_a_tool_name', reason };
160
+ if (reason === 'wildcard_all' || reason === 'namespace_wildcard')
161
+ return { why: 'wildcard', reason };
162
+ return 'unknown';
163
+ }
164
+ catch {
165
+ return 'unknown';
166
+ }
167
+ }
125
168
  export function sessionPolicyWithheldNotice(why) {
126
169
  return typeof why === 'string' && Object.hasOwn(WITHHELD_NOTICE, why)
127
170
  ? WITHHELD_NOTICE[why]
@@ -132,7 +175,5 @@ const LEGACY_TOOL_NAME_FAILURE_NOTE = `This run did not start: a tool rule appli
132
175
  export function legacyToolNameFailureNoteOf(code) {
133
176
  if (code === CONFIG_LEGACY_TOOL_NAME)
134
177
  return LEGACY_TOOL_NAME_FAILURE_NOTE;
135
- if (code === RULES_LEGACY_TOOL_NAME)
136
- return RULES_LEGACY_TOOL_NAME_WRITE_NOTE;
137
178
  return undefined;
138
179
  }
@@ -1,8 +1,10 @@
1
1
  import type { SessionPermissionRules, StoredSessionRules } from '@sema-agent/sdk';
2
+ import type { UiLanguage } from '../env/uiLanguage.js';
2
3
  import type { SessionPolicyReading } from '../sessionPolicyCapability.js';
3
4
  import type { ToolRosterView } from '../toolRoster.js';
4
- import { CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
5
+ import { CORE_ALLOW_ENTRY_INERT_REASONS, CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
5
6
  export type LegacyToolNameVerdict = Exclude<(typeof CORE_RULE_TOOL_NAME_VERDICTS)[number], 'current'>;
7
+ export type SessionRuleInertReason = (typeof CORE_ALLOW_ENTRY_INERT_REASONS)[number];
6
8
  export interface SessionPolicyFacade {
7
9
  getPolicy(sessionId: string, opts?: {
8
10
  signal?: AbortSignal;
@@ -41,10 +43,16 @@ export type SessionPolicyFailure = {
41
43
  kind: 'request-rejected';
42
44
  message: string;
43
45
  } | {
44
- kind: 'legacy-tool-name';
46
+ kind: 'earlier-grammar';
47
+ message: string;
48
+ field?: 'toolAllow';
49
+ entry?: string;
50
+ } | {
51
+ kind: 'inert-entry';
45
52
  message: string;
46
53
  field?: SessionPolicyRemovedEntry['field'];
47
- name?: string;
54
+ entry?: string;
55
+ reason?: SessionRuleInertReason;
48
56
  verdict?: LegacyToolNameVerdict;
49
57
  currentName?: string;
50
58
  } | {
@@ -58,11 +66,12 @@ export type SessionPolicyFailure = {
58
66
  message: string;
59
67
  };
60
68
  export declare function classifySessionPolicyFailure(e: unknown): SessionPolicyFailure;
61
- export type SessionPolicyMalformedWhy = 'envelope' | 'rev' | 'rule_field';
69
+ export type SessionPolicyMalformedWhy = 'envelope' | 'rev' | 'rule_field' | 'grammar_stamp';
62
70
  export type SessionPolicyReadOutcome = {
63
71
  kind: 'present';
64
72
  rev: number;
65
73
  rules: SessionPermissionRules;
74
+ grammar?: number;
66
75
  } | {
67
76
  kind: 'malformed';
68
77
  why: SessionPolicyMalformedWhy;
@@ -82,6 +91,11 @@ export interface SessionPolicyTightenRequest {
82
91
  }
83
92
  export type SessionPolicyTightenUnknownWhy = 'face_unavailable' | 'session_not_found' | 'session_ownerless' | 'unauthorized' | 'request_rejected' | 'store_corrupt' | 'read_unreadable' | 'read_failed' | 'write_refused' | 'write_indeterminate' | 'write_receipt_unreadable' | 'write_receipt_mismatch';
84
93
  export declare const SESSION_POLICY_TIGHTEN_UNKNOWN_WHY: readonly SessionPolicyTightenUnknownWhy[];
94
+ export type SessionPolicyEarlierGrammarRefusal = {
95
+ kind: 'refused';
96
+ why: 'earlier_grammar_rows';
97
+ entries: readonly SessionPolicyRemovedEntry[];
98
+ };
85
99
  export type SessionPolicyTightenOutcome = {
86
100
  kind: 'written';
87
101
  rev: number;
@@ -94,23 +108,27 @@ export type SessionPolicyTightenOutcome = {
94
108
  } | {
95
109
  kind: 'capability_absent';
96
110
  why: 'not_reported' | 'absent';
97
- } | {
98
- kind: 'legacy_tool_name';
99
- message: string;
100
- field?: SessionPolicyRemovedEntry['field'];
101
- name?: string;
102
- verdict?: LegacyToolNameVerdict;
103
- currentName?: string;
111
+ } | SessionPolicyEarlierGrammarRefusal | ({
112
+ kind: 'inert_entry';
113
+ } & SessionPolicyInertEntryRefusal & {
104
114
  alreadyInRecord?: boolean;
105
- } | {
115
+ }) | {
106
116
  kind: 'unknown';
107
117
  why: SessionPolicyTightenUnknownWhy;
108
118
  message?: string;
109
119
  };
120
+ export interface SessionPolicyInertEntryRefusal {
121
+ message: string;
122
+ field?: SessionPolicyRemovedEntry['field'];
123
+ entry?: string;
124
+ reason?: SessionRuleInertReason;
125
+ verdict?: LegacyToolNameVerdict;
126
+ currentName?: string;
127
+ }
110
128
  export declare function tightenSessionPolicy(facade: SessionPolicyFacade, req: SessionPolicyTightenRequest, opts?: {
111
129
  signal?: AbortSignal;
112
130
  }): Promise<SessionPolicyTightenOutcome>;
113
- export declare function sessionPolicyTightenNotice(outcome: SessionPolicyTightenOutcome): string;
131
+ export declare function sessionPolicyTightenNotice(outcome: SessionPolicyTightenOutcome, lang?: UiLanguage): string;
114
132
  export declare function sessionPolicyReadNotice(outcome: Exclude<SessionPolicyReadOutcome, {
115
133
  kind: 'present';
116
134
  }> | {
@@ -153,14 +171,9 @@ export type SessionPolicyRemovalOutcome = {
153
171
  kind: 'write_unconfirmed';
154
172
  why: Extract<SessionPolicyTightenUnknownWhy, 'write_indeterminate' | 'write_receipt_unreadable' | 'write_receipt_mismatch'>;
155
173
  message?: string;
156
- } | {
157
- kind: 'legacy_tool_name';
158
- message: string;
159
- field?: SessionPolicyRemovedEntry['field'];
160
- name?: string;
161
- verdict?: LegacyToolNameVerdict;
162
- currentName?: string;
163
- };
174
+ } | SessionPolicyEarlierGrammarRefusal | ({
175
+ kind: 'inert_entry';
176
+ } & SessionPolicyInertEntryRefusal);
164
177
  export type SessionPolicyRosterRequiredWhy = 'absent' | 'unreadable';
165
178
  export declare function removeRefusedSessionRules(facade: SessionPolicyFacade, sessionId: string, roster: ToolRosterView | undefined, opts?: {
166
179
  signal?: AbortSignal;
@@ -1,10 +1,17 @@
1
1
  import { wireFailureShapeOf } from '../wireFailureShape.js';
2
2
  import { hostLog } from '../host.js';
3
+ import { msg, renderMessage } from '../i18n/translate.js';
3
4
  import { ruleToolNameVerdictOf } from '../protocolSegment.js';
4
- import { RULES_LEGACY_TOOL_NAME_WRITE_NOTE } from './refusedToolNameProse.js';
5
- import { RULES_LEGACY_TOOL_NAME, isRulesRefusalCode } from '../engineErrorCodes.js';
5
+ import { RULES_INERT_ENTRY, RULES_INVALID_SHAPE, isRulesRefusalCode } from '../engineErrorCodes.js';
6
+ import { deniedEntryToolName } from '../engineToolDirectory.js';
6
7
  import { rosterMountedNames } from '../rosterMountedNames.js';
7
- import { CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
8
+ import { CORE_ALLOW_ENTRY_INERT_REASONS, CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
9
+ const EARLIER_GRAMMAR_REASON = 'earlier_grammar';
10
+ const _inertReasonPin = true;
11
+ void _inertReasonPin;
12
+ function isSessionRuleInertReason(v) {
13
+ return typeof v === 'string' && CORE_ALLOW_ENTRY_INERT_REASONS.includes(v);
14
+ }
8
15
  function isLegacyToolNameVerdict(v) {
9
16
  return typeof v === 'string' && v !== 'current' && CORE_RULE_TOOL_NAME_VERDICTS.includes(v);
10
17
  }
@@ -12,25 +19,43 @@ const RULE_FIELD_PRESENCE = {
12
19
  toolAllow: true,
13
20
  toolDeny: true,
14
21
  allowDirs: true,
15
- commandAllow: true,
16
- commandDeny: true,
17
22
  };
23
+ const RECORD_META_KEYS = Object.freeze(['rev', 'gen', 'grammar']);
24
+ const _metaKeysPin = true;
25
+ void _metaKeysPin;
18
26
  export const SESSION_POLICY_RULE_FIELDS = Object.freeze(Object.keys(RULE_FIELD_PRESENCE));
27
+ function foreignRecordKeysOf(record) {
28
+ return Object.getOwnPropertyNames(record).filter((key) => !SESSION_POLICY_RULE_FIELDS.includes(key) && !RECORD_META_KEYS.includes(key));
29
+ }
30
+ function recordKeyLabel(key) {
31
+ return /^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(key) ? `"${key}"` : 'unnamed';
32
+ }
19
33
  const MAX_RULE_ENTRIES = 10_000;
20
34
  export function classifySessionPolicyFailure(e) {
21
- const { status, errorCode, message, bodyField, bodyName, bodyVerdict, bodyCurrentName } = wireFailureShapeOf(e);
35
+ const { status, errorCode, message, bodyField, bodyVerdict, bodyCurrentName, bodyEntry, bodyReason } = wireFailureShapeOf(e);
22
36
  if (errorCode === undefined || errorCode === '')
23
37
  return { kind: 'no-verdict', message };
24
- if (errorCode === RULES_LEGACY_TOOL_NAME) {
38
+ if (errorCode === RULES_INERT_ENTRY && bodyReason === EARLIER_GRAMMAR_REASON && status !== undefined && status >= 400 && status < 500) {
39
+ return {
40
+ kind: 'earlier-grammar',
41
+ message,
42
+ ...(bodyField === 'toolAllow' ? { field: bodyField } : {}),
43
+ ...(bodyEntry !== undefined && bodyEntry !== '' ? { entry: bodyEntry } : {}),
44
+ };
45
+ }
46
+ if (errorCode === RULES_INERT_ENTRY && status !== undefined && status >= 400 && status < 500) {
25
47
  return {
26
- kind: 'legacy-tool-name',
48
+ kind: 'inert-entry',
27
49
  message,
28
50
  ...(bodyField === 'toolDeny' || bodyField === 'toolAllow' ? { field: bodyField } : {}),
29
- ...(bodyName !== undefined && bodyName !== '' ? { name: bodyName } : {}),
51
+ ...(bodyEntry !== undefined && bodyEntry !== '' ? { entry: bodyEntry } : {}),
52
+ ...(isSessionRuleInertReason(bodyReason) ? { reason: bodyReason } : {}),
30
53
  ...(isLegacyToolNameVerdict(bodyVerdict) ? { verdict: bodyVerdict } : {}),
31
54
  ...(bodyCurrentName !== undefined && bodyCurrentName !== '' ? { currentName: bodyCurrentName } : {}),
32
55
  };
33
56
  }
57
+ if (errorCode === RULES_INVALID_SHAPE && status !== undefined && status >= 400 && status < 500)
58
+ return { kind: 'request-rejected', message };
34
59
  if (isRulesRefusalCode(errorCode) && status !== undefined && status >= 400 && status < 500)
35
60
  return { kind: 'error', message };
36
61
  if (status === 501 || errorCode === 'capability.session_store_required') {
@@ -95,6 +120,21 @@ function narrowStoredRecord(body) {
95
120
  if (typeof rev !== 'number' || !Number.isSafeInteger(rev) || rev < 0) {
96
121
  return { ok: false, why: 'rev', detail: 'policy record carries no readable version stamp' };
97
122
  }
123
+ let grammarSlot;
124
+ try {
125
+ grammarSlot = Object.getOwnPropertyDescriptor(envelope, 'grammar');
126
+ }
127
+ catch {
128
+ return { ok: false, why: 'grammar_stamp', detail: 'policy record’s rule-grammar stamp could not be read' };
129
+ }
130
+ let grammar;
131
+ if (grammarSlot !== undefined) {
132
+ const v = Object.hasOwn(grammarSlot, 'value') ? grammarSlot.value : undefined;
133
+ if (typeof v !== 'number' || !Number.isSafeInteger(v) || v < 0) {
134
+ return { ok: false, why: 'grammar_stamp', detail: 'policy record carries a rule-grammar stamp that is not a whole number of 0 or more' };
135
+ }
136
+ grammar = v;
137
+ }
98
138
  const rules = {};
99
139
  for (const field of SESSION_POLICY_RULE_FIELDS) {
100
140
  if (!Object.hasOwn(envelope, field))
@@ -107,7 +147,19 @@ function narrowStoredRecord(body) {
107
147
  return { ok: false, why: 'rule_field', detail: read.detail };
108
148
  rules[field] = read.list;
109
149
  }
110
- return { ok: true, rev, rules };
150
+ for (const key of foreignRecordKeysOf(envelope)) {
151
+ const slot = Object.getOwnPropertyDescriptor(envelope, key);
152
+ if (slot === undefined)
153
+ continue;
154
+ if (!Object.hasOwn(slot, 'value') || Array.isArray(slot.value)) {
155
+ return {
156
+ ok: false,
157
+ why: 'rule_field',
158
+ detail: `policy record carries a rule field this client does not read (${recordKeyLabel(key)}) — refusing to treat it as readable, since writing the record back would drop that field`,
159
+ };
160
+ }
161
+ }
162
+ return { ok: true, rev, rules, grammar };
111
163
  }
112
164
  export async function readSessionPolicy(facade, sessionId, opts) {
113
165
  let body;
@@ -126,7 +178,7 @@ export async function readSessionPolicy(facade, sessionId, opts) {
126
178
  }
127
179
  if (!read.ok)
128
180
  return { kind: 'malformed', why: read.why, detail: read.detail };
129
- return { kind: 'present', rev: read.rev, rules: read.rules };
181
+ return { kind: 'present', rev: read.rev, rules: read.rules, ...(read.grammar !== undefined ? { grammar: read.grammar } : {}) };
130
182
  }
131
183
  export function tightenedSessionRules(prior, add) {
132
184
  const p = narrowRulesInput(prior, 'prior');
@@ -159,6 +211,10 @@ function narrowRulesInput(raw, side) {
159
211
  if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
160
212
  throw new TypeError(`tightenedSessionRules: "${side}" must be a rules record object`);
161
213
  }
214
+ const foreign = foreignRecordKeysOf(raw);
215
+ if (foreign.length > 0) {
216
+ throw new TypeError(`tightenedSessionRules: "${side}" carries a key that is not a rule field (${recordKeyLabel(foreign[0] ?? '')}) — it would not be written, so the record is refused instead of written without it`);
217
+ }
162
218
  const out = {};
163
219
  for (const field of SESSION_POLICY_RULE_FIELDS) {
164
220
  if (!Object.hasOwn(raw, field))
@@ -173,10 +229,13 @@ function narrowRulesInput(raw, side) {
173
229
  }
174
230
  return out;
175
231
  }
176
- function receiptMismatch(sent, receipt, sentRev, receiptRev) {
232
+ function receiptMismatch(sent, receipt, sentRev, receiptRev, sentGrammar, receiptGrammar) {
177
233
  if (receiptRev <= sentRev) {
178
234
  return `policy write receipt did not advance the version stamp (sent with ${sentRev}, receipt says ${receiptRev})`;
179
235
  }
236
+ if (sentGrammar !== undefined && (receiptGrammar === undefined || receiptGrammar < sentGrammar)) {
237
+ 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`;
238
+ }
180
239
  for (const field of SESSION_POLICY_RULE_FIELDS) {
181
240
  const s = Object.hasOwn(sent, field) ? sent[field] : undefined;
182
241
  if (s === undefined)
@@ -227,32 +286,66 @@ function unknownFrom(failure, leg) {
227
286
  return { kind: 'unknown', why: leg === 'read' ? 'read_failed' : 'write_indeterminate', message };
228
287
  case 'loosen-forbidden':
229
288
  case 'cas-conflict':
230
- case 'legacy-tool-name':
289
+ case 'earlier-grammar':
290
+ case 'inert-entry':
231
291
  case 'error':
232
292
  return { kind: 'unknown', why: leg === 'read' ? 'read_failed' : 'write_refused', message };
233
293
  }
234
294
  }
235
- function legacyToolNameOutcome(failure, prior, add) {
236
- const { field, name, verdict, currentName } = failure;
237
- let alreadyInRecord;
238
- if (field !== undefined && name !== undefined) {
239
- const inPrior = Object.hasOwn(prior, field) ? prior[field] : undefined;
240
- const inAdd = Object.hasOwn(add, field) ? add[field] : undefined;
241
- if (inPrior !== undefined && inPrior.includes(name))
242
- alreadyInRecord = true;
243
- else if (inAdd !== undefined && inAdd.includes(name))
244
- alreadyInRecord = false;
245
- }
295
+ function alreadyInRecordOf(field, entry, prior, add) {
296
+ if (field === undefined || entry === undefined)
297
+ return undefined;
298
+ const inPrior = Object.hasOwn(prior, field) ? prior[field] : undefined;
299
+ const inAdd = Object.hasOwn(add, field) ? add[field] : undefined;
300
+ if (inPrior !== undefined && inPrior.includes(entry))
301
+ return true;
302
+ if (inAdd !== undefined && inAdd.includes(entry))
303
+ return false;
304
+ return undefined;
305
+ }
306
+ function inertEntryRefusalOf(failure) {
307
+ const { field, entry, reason, verdict, currentName } = failure;
246
308
  return {
247
- kind: 'legacy_tool_name',
248
309
  message: failure.message,
249
310
  ...(field !== undefined ? { field } : {}),
250
- ...(name !== undefined ? { name } : {}),
311
+ ...(entry !== undefined ? { entry } : {}),
312
+ ...(reason !== undefined ? { reason } : {}),
251
313
  ...(verdict !== undefined ? { verdict } : {}),
252
314
  ...(currentName !== undefined ? { currentName } : {}),
253
- ...(alreadyInRecord !== undefined ? { alreadyInRecord } : {}),
254
315
  };
255
316
  }
317
+ function earlierGrammarEntriesOf(prior, outgoing) {
318
+ const priorAllow = Object.hasOwn(prior, 'toolAllow') ? prior.toolAllow : undefined;
319
+ const outAllow = Object.hasOwn(outgoing, 'toolAllow') ? outgoing.toolAllow : undefined;
320
+ if (priorAllow === undefined || outAllow === undefined)
321
+ return [];
322
+ const carried = new Set(outAllow);
323
+ const seen = new Set();
324
+ const out = [];
325
+ for (const rule of priorAllow) {
326
+ if (seen.has(rule) || !carried.has(rule))
327
+ continue;
328
+ const name = deniedEntryToolName(rule);
329
+ if (name === undefined || name === rule)
330
+ continue;
331
+ seen.add(rule);
332
+ out.push({ field: 'toolAllow', rule });
333
+ }
334
+ return out;
335
+ }
336
+ function earlierGrammarOutcome(failure) {
337
+ return {
338
+ kind: 'refused',
339
+ why: 'earlier_grammar_rows',
340
+ entries: Object.hasOwn(failure, 'field') && Object.hasOwn(failure, 'entry') && failure.field !== undefined && failure.entry !== undefined ? [{ field: failure.field, rule: failure.entry }] : [],
341
+ };
342
+ }
343
+ function rulesWithStamp(rules, grammar) {
344
+ return grammar === undefined ? rules : { ...rules, grammar };
345
+ }
346
+ function grammarStampOf(read) {
347
+ return Object.hasOwn(read, 'grammar') ? read.grammar : undefined;
348
+ }
256
349
  async function attemptTighten(facade, req, opts) {
257
350
  const read = await readSessionPolicy(facade, req.sessionId, opts);
258
351
  if (read.kind === 'failed')
@@ -261,9 +354,15 @@ async function attemptTighten(facade, req, opts) {
261
354
  return { done: { kind: 'unknown', why: 'read_unreadable', message: read.detail } };
262
355
  }
263
356
  const rules = tightenedSessionRules(read.rules, req.add);
357
+ const stamp = grammarStampOf(read);
358
+ if (stamp === undefined) {
359
+ const earlier = earlierGrammarEntriesOf(read.rules, rules);
360
+ if (earlier.length > 0)
361
+ return { done: { kind: 'refused', why: 'earlier_grammar_rows', entries: earlier } };
362
+ }
264
363
  let receipt;
265
364
  try {
266
- receipt = await facade.putPolicy(req.sessionId, { rules, expectedRev: read.rev }, opts);
365
+ receipt = await facade.putPolicy(req.sessionId, { rules: rulesWithStamp(rules, stamp), expectedRev: read.rev }, opts);
267
366
  }
268
367
  catch (e) {
269
368
  const failure = classifySessionPolicyFailure(e);
@@ -271,8 +370,12 @@ async function attemptTighten(facade, req, opts) {
271
370
  return { done: { kind: 'loosen_forbidden', message: failure.message } };
272
371
  if (failure.kind === 'cas-conflict')
273
372
  return { retryAtRev: read.rev };
274
- if (failure.kind === 'legacy-tool-name')
275
- return { done: legacyToolNameOutcome(failure, read.rules, req.add) };
373
+ if (failure.kind === 'earlier-grammar')
374
+ return { done: earlierGrammarOutcome(failure) };
375
+ if (failure.kind === 'inert-entry') {
376
+ const alreadyInRecord = alreadyInRecordOf(failure.field, failure.entry, read.rules, req.add);
377
+ return { done: { kind: 'inert_entry', ...inertEntryRefusalOf(failure), ...(alreadyInRecord !== undefined ? { alreadyInRecord } : {}) } };
378
+ }
276
379
  return { done: unknownFrom(failure, 'write') };
277
380
  }
278
381
  let narrowed;
@@ -285,7 +388,7 @@ async function attemptTighten(facade, req, opts) {
285
388
  if (!narrowed.ok) {
286
389
  return { done: { kind: 'unknown', why: 'write_receipt_unreadable', message: narrowed.detail } };
287
390
  }
288
- const mismatch = receiptMismatch(rules, narrowed.rules, read.rev, narrowed.rev);
391
+ const mismatch = receiptMismatch(rules, narrowed.rules, read.rev, narrowed.rev, stamp, narrowed.grammar);
289
392
  if (mismatch !== null)
290
393
  return { done: { kind: 'unknown', why: 'write_receipt_mismatch', message: mismatch } };
291
394
  return { done: { kind: 'written', rev: narrowed.rev } };
@@ -309,46 +412,48 @@ export async function tightenSessionPolicy(facade, req, opts) {
309
412
  return second.done;
310
413
  return { kind: 'conflict', rev: second.retryAtRev };
311
414
  }
312
- export function sessionPolicyTightenNotice(outcome) {
415
+ const earlierGrammarNote = (lang) => renderMessage(msg('rules.sessionPolicy.tighten.earlierGrammar'), lang);
416
+ export function sessionPolicyTightenNotice(outcome, lang) {
417
+ const say = (key, params) => renderMessage(msg(key, params), lang);
313
418
  switch (outcome.kind) {
314
419
  case 'written':
315
- return `Session rules saved (version ${outcome.rev}) — the record now holds everything it already had plus the entries sent here, and it applies from this session’s next tool gate onward; the step already running is not interrupted.`;
420
+ return say('rules.sessionPolicy.tighten.written', { rev: outcome.rev });
316
421
  case 'conflict':
317
- return 'Session rules were changed by someone else while this tightening was being written, twice in a row — nothing was written. Re-read the current rules and try again, or let the other writer finish first.';
422
+ return say('rules.sessionPolicy.tighten.conflict');
318
423
  case 'loosen_forbidden':
319
- return 'The engine refused this write because it would relax the session rules: at least one entry sent here widens what is allowed rather than narrowing it. Only entries that add restrictions are accepted on this path.';
424
+ return say('rules.sessionPolicy.tighten.loosenForbidden');
320
425
  case 'capability_absent':
321
- return outcome.why === 'absent'
322
- ? 'This deployment does not keep per-session tool rules, so there is nothing to tighten here.'
323
- : 'This engine does not say whether it keeps per-session tool rules — it predates that answer — so the tightening entry stays hidden rather than being written blind.';
324
- case 'legacy_tool_name':
325
- return RULES_LEGACY_TOOL_NAME_WRITE_NOTE;
426
+ return outcome.why === 'absent' ? say('rules.sessionPolicy.tighten.capabilityAbsent') : say('rules.sessionPolicy.tighten.capabilityNotReported');
427
+ case 'refused':
428
+ return earlierGrammarNote(lang);
429
+ case 'inert_entry':
430
+ return say('rules.sessionPolicy.tighten.inertEntry');
326
431
  case 'unknown':
327
432
  switch (outcome.why) {
328
433
  case 'face_unavailable':
329
- return 'This deployment does not keep per-session tool rules, so nothing was written.';
434
+ return say('rules.sessionPolicy.tighten.unknown.faceUnavailable');
330
435
  case 'session_not_found':
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.';
436
+ return say('rules.sessionPolicy.tighten.unknown.sessionNotFound');
332
437
  case 'session_ownerless':
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.';
438
+ return say('rules.sessionPolicy.tighten.unknown.sessionOwnerless');
334
439
  case 'unauthorized':
335
- return 'This call could not present a verifiable identity, so nothing was written.';
440
+ return say('rules.sessionPolicy.tighten.unknown.unauthorized');
336
441
  case 'request_rejected':
337
- return 'The engine rejected the shape of this rule record, so nothing was written. Fix the entries before sending them again.';
442
+ return say('rules.sessionPolicy.tighten.unknown.requestRejected');
338
443
  case 'store_corrupt':
339
- return 'The engine could not establish which version this session’s rules are at and refused to stamp a new one, so nothing was written. The entries sent here are not the problem — this needs an operator to look at the rule store.';
444
+ return say('rules.sessionPolicy.tighten.unknown.storeCorrupt');
340
445
  case 'read_unreadable':
341
- return 'The session’s current rules could not be read, so nothing was written — writing over a record that cannot be read would replace real restrictions with an empty one.';
446
+ return say('rules.sessionPolicy.tighten.unknown.readUnreadable');
342
447
  case 'read_failed':
343
- return 'The session’s current rules could not be fetched, so nothing was written.';
448
+ return say('rules.sessionPolicy.tighten.unknown.readFailed');
344
449
  case 'write_refused':
345
- return 'The engine refused this write, so nothing was written. Re-read the current rules before trying again.';
450
+ return say('rules.sessionPolicy.tighten.unknown.writeRefused');
346
451
  case 'write_indeterminate':
347
- return 'The write never came back with an answer from the engine — it may or may not have taken effect. Re-read the current rules before writing anything else.';
452
+ return say('rules.sessionPolicy.tighten.unknown.writeIndeterminate');
348
453
  case 'write_receipt_unreadable':
349
- return 'The tightening was sent but the engine’s answer could not be read — it may already be in effect. Re-read the current rules before writing anything else.';
454
+ return say('rules.sessionPolicy.tighten.unknown.writeReceiptUnreadable');
350
455
  case 'write_receipt_mismatch':
351
- return 'The tightening was sent but the engine’s answer does not account for it (the version did not advance, or the entries sent here are not in the returned record) — it may already be in effect. Re-read the current rules before writing anything else.';
456
+ return say('rules.sessionPolicy.tighten.unknown.writeReceiptMismatch');
352
457
  }
353
458
  }
354
459
  }
@@ -371,8 +476,10 @@ function readFailureSentence(kind) {
371
476
  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.';
372
477
  case 'request-rejected':
373
478
  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.';
374
- case 'legacy-tool-name':
375
- return 'The engine answered this read with a refusal about a tool name it no longer accepts, so this session’s rule record could not be read. Try again; if it keeps happening, whoever runs the engine needs to look at it.';
479
+ case 'earlier-grammar':
480
+ return 'The engine answered this read with a refusal that only applies to writing back rules saved under an earlier rule grammar, so this session’s rule record could not be read. Try again; if it keeps happening, whoever runs the engine needs to look at it.';
481
+ case 'inert-entry':
482
+ return 'The engine answered this read with a refusal that only applies to writing a rule entry it reads as matching nothing, so this session’s rule record could not be read. Try again; if it keeps happening, whoever runs the engine needs to look at it.';
376
483
  case 'unauthorized':
377
484
  return 'This call could not present a verifiable identity, so this session’s rule record could not be read.';
378
485
  case 'error':
@@ -389,6 +496,8 @@ function readMalformedSentence(why) {
389
496
  return 'The engine’s answer for this session’s rule record carried no usable version, so this version will not show it — a record it cannot place may still hold restrictions that are in effect. Try again.';
390
497
  case 'rule_field':
391
498
  return 'One of the lists in this session’s rule record came back in a form this version cannot read, so none of the record is shown rather than part of it — a partly shown record could leave out restrictions that are in effect.';
499
+ case 'grammar_stamp':
500
+ return 'The engine’s answer for this session’s rule record carried a rule-grammar mark this version cannot read, so it will not show the record — the same entry can mean different things in different grammars, so what it allows is not known. Try again; if it keeps happening, this version and the engine may be out of step.';
392
501
  }
393
502
  }
394
503
  function ownRead(o, key) {
@@ -492,9 +601,15 @@ async function attemptRemoval(facade, sessionId, mounted, opts) {
492
601
  const { next, removed } = judged;
493
602
  if (removed.length === 0)
494
603
  return { done: { kind: 'nothing_to_remove', rev: read.rev } };
604
+ const stamp = grammarStampOf(read);
605
+ if (stamp === undefined) {
606
+ const earlier = earlierGrammarEntriesOf(read.rules, next);
607
+ if (earlier.length > 0)
608
+ return { done: { kind: 'refused', why: 'earlier_grammar_rows', entries: earlier } };
609
+ }
495
610
  let receipt;
496
611
  try {
497
- receipt = await facade.putPolicy(sessionId, { rules: next, expectedRev: read.rev }, opts);
612
+ receipt = await facade.putPolicy(sessionId, { rules: rulesWithStamp(next, stamp), expectedRev: read.rev }, opts);
498
613
  }
499
614
  catch (e) {
500
615
  const failure = classifySessionPolicyFailure(e);
@@ -502,18 +617,10 @@ async function attemptRemoval(facade, sessionId, mounted, opts) {
502
617
  return { done: { kind: 'loosen_forbidden', message: failure.message, refused: removed } };
503
618
  if (failure.kind === 'cas-conflict')
504
619
  return { retryAtRev: read.rev };
505
- if (failure.kind === 'legacy-tool-name') {
506
- return {
507
- done: {
508
- kind: 'legacy_tool_name',
509
- message: failure.message,
510
- ...(failure.field !== undefined ? { field: failure.field } : {}),
511
- ...(failure.name !== undefined ? { name: failure.name } : {}),
512
- ...(failure.verdict !== undefined ? { verdict: failure.verdict } : {}),
513
- ...(failure.currentName !== undefined ? { currentName: failure.currentName } : {}),
514
- },
515
- };
516
- }
620
+ if (failure.kind === 'earlier-grammar')
621
+ return { done: earlierGrammarOutcome(failure) };
622
+ if (failure.kind === 'inert-entry')
623
+ return { done: { kind: 'inert_entry', ...inertEntryRefusalOf(failure) } };
517
624
  return { done: removalFailed(failure, 'write') };
518
625
  }
519
626
  let narrowed;
@@ -525,7 +632,7 @@ async function attemptRemoval(facade, sessionId, mounted, opts) {
525
632
  }
526
633
  if (!narrowed.ok)
527
634
  return { done: { kind: 'write_unconfirmed', why: 'write_receipt_unreadable', message: narrowed.detail } };
528
- const mismatch = receiptMismatch(next, narrowed.rules, read.rev, narrowed.rev) ??
635
+ const mismatch = receiptMismatch(next, narrowed.rules, read.rev, narrowed.rev, stamp, narrowed.grammar) ??
529
636
  (removed.some((e) => {
530
637
  const r = Object.hasOwn(narrowed.rules, e.field) ? narrowed.rules[e.field] : undefined;
531
638
  return r !== undefined && r.includes(e.rule);
@@ -609,8 +716,10 @@ export function sessionPolicyRemovalNotice(outcome) {
609
716
  }
610
717
  case 'write_unconfirmed':
611
718
  return 'The removal was sent but no clear answer came back from the engine — it may already be in effect. Run the removal again: it re-reads the record, and reports nothing left to remove if it did take effect.';
612
- case 'legacy_tool_name':
613
- return 'Nothing was changed: the engine refused to write this session’s rule record back, because an entry this removal keeps is a name the engine no longer accepts in that record (the engine\'s own error text names the entry). The removal keeps it on purpose — the supplied roster mounts a tool by exactly that name, or this version does not yet recognise the name as one the engine refuses. While the record holds that entry it cannot be written, so the entries this removal meant to take out stay in it as well.';
719
+ case 'refused':
720
+ return earlierGrammarNote();
721
+ case 'inert_entry':
722
+ return 'Nothing was changed: the engine refused to write this session’s rule record back, because an entry this removal keeps reads as matching nothing (the engine’s own error text names the entry and why). This removal only takes out names the engine refuses to start a run with, so it leaves that entry for a person to correct or remove; while the record holds it, it cannot be written, so the entries this removal meant to take out stay in it as well.';
614
723
  default:
615
724
  return generic;
616
725
  }