@sema-agent/client-core 0.97.1 → 0.98.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 (281) hide show
  1. package/CHANGELOG.md +408 -0
  2. package/README.md +74 -37
  3. package/dist/adapt/arms.d.ts +1 -0
  4. package/dist/adapt/arms.js +128 -34
  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/textStream.d.ts +1 -0
  10. package/dist/adapt/textStream.js +24 -7
  11. package/dist/adapt/toolCards.d.ts +27 -5
  12. package/dist/adapt/toolCards.js +39 -6
  13. package/dist/adapt/wireShapes.js +6 -2
  14. package/dist/adapt.d.ts +2 -0
  15. package/dist/adapt.js +8 -3
  16. package/dist/adapter/activeRunSelfHeal.d.ts +5 -0
  17. package/dist/adapter/activeRunSelfHeal.js +51 -28
  18. package/dist/adapter/downstream/eventToSdkMessage.d.ts +2 -5
  19. package/dist/adapter/downstream/eventToSdkMessage.js +27 -34
  20. package/dist/adapter/downstream/terminalToSdkResult.js +11 -4
  21. package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +5 -2
  22. package/dist/adapter/downstream/turnUsageToModelUsage.js +2 -0
  23. package/dist/adapter/runStream.js +29 -13
  24. package/dist/adapter/types.d.ts +4 -4
  25. package/dist/agentSession/backgroundView.js +2 -1
  26. package/dist/agentSession/engineAgentRegistryRead.d.ts +2 -2
  27. package/dist/agentSession/engineAgentRegistryRead.js +12 -13
  28. package/dist/agentsWireCaps.d.ts +8 -8
  29. package/dist/agentsWireCaps.js +168 -175
  30. package/dist/approvalDecidedByCapability.d.ts +0 -4
  31. package/dist/approvalDecidedByCapability.js +0 -6
  32. package/dist/approvalsStreamLiveCapability.d.ts +0 -4
  33. package/dist/approvalsStreamLiveCapability.js +0 -6
  34. package/dist/assistantInbox.d.ts +34 -0
  35. package/dist/assistantInbox.js +118 -0
  36. package/dist/centerWiringCapability.js +9 -1
  37. package/dist/clientSlice.d.ts +0 -9
  38. package/dist/clientSlice.js +0 -3
  39. package/dist/cloudConfigWireCaps.d.ts +4 -2
  40. package/dist/cloudConfigWireCaps.js +81 -61
  41. package/dist/compensations.js +1 -1
  42. package/dist/connectorStatus.d.ts +21 -0
  43. package/dist/connectorStatus.js +112 -0
  44. package/dist/controlRouter.d.ts +14 -1
  45. package/dist/controlRouter.js +68 -17
  46. package/dist/coreValuePorts.d.ts +3 -3
  47. package/dist/decideReceipt.d.ts +2 -2
  48. package/dist/decideReceipt.js +5 -2
  49. package/dist/declared/supportFloorDeclared.js +3 -3
  50. package/dist/deviceExecutorManagementCapability.d.ts +0 -4
  51. package/dist/deviceExecutorManagementCapability.js +0 -6
  52. package/dist/deviceRebind.d.ts +49 -1
  53. package/dist/deviceRebind.js +221 -3
  54. package/dist/displayUntrusted.d.ts +1 -0
  55. package/dist/displayUntrusted.js +250 -76
  56. package/dist/engineAgentAbsence.d.ts +1 -1
  57. package/dist/engineAgentPanelStore.d.ts +5 -4
  58. package/dist/engineAgentPanelStore.js +3 -12
  59. package/dist/engineCapReader.d.ts +0 -4
  60. package/dist/engineCapReader.js +1 -5
  61. package/dist/engineCapsCache.d.ts +4 -0
  62. package/dist/engineCapsCache.js +17 -12
  63. package/dist/engineClosedSets.d.ts +1 -0
  64. package/dist/engineClosedSets.js +4 -1
  65. package/dist/engineErrorCodes.d.ts +9 -2
  66. package/dist/engineErrorCodes.js +9 -1
  67. package/dist/engineIdentity.d.ts +1 -0
  68. package/dist/engineIdentity.js +8 -0
  69. package/dist/engineRowRef.d.ts +28 -0
  70. package/dist/engineRowRef.js +111 -0
  71. package/dist/engineSessionMessages.d.ts +67 -0
  72. package/dist/engineSessionMessages.js +364 -0
  73. package/dist/engineToolDirectory.d.ts +0 -9
  74. package/dist/engineToolDirectory.js +1 -94
  75. package/dist/engineUrl.d.ts +18 -0
  76. package/dist/engineUrl.js +52 -0
  77. package/dist/env/uiLanguage.d.ts +9 -0
  78. package/dist/env/uiLanguage.js +35 -0
  79. package/dist/eventLeg.d.ts +92 -0
  80. package/dist/eventLeg.js +285 -0
  81. package/dist/executionLaneCapability.d.ts +0 -4
  82. package/dist/executionLaneCapability.js +3 -16
  83. package/dist/fileHistoryCaptureCapability.d.ts +0 -4
  84. package/dist/fileHistoryCaptureCapability.js +5 -7
  85. package/dist/fleet/fleetLedger.d.ts +0 -3
  86. package/dist/fleet/fleetLedger.js +21 -36
  87. package/dist/fleet/fleetProjection.d.ts +2 -2
  88. package/dist/fleet/fleetProjection.js +10 -8
  89. package/dist/frameBinding.d.ts +5 -0
  90. package/dist/frameBinding.js +23 -0
  91. package/dist/gateFailureCodes.d.ts +2 -0
  92. package/dist/gateFailureCodes.js +15 -0
  93. package/dist/gateOutcome.d.ts +3 -1
  94. package/dist/gateOutcome.js +25 -6
  95. package/dist/gateVocabulary.d.ts +5 -0
  96. package/dist/gateVocabulary.js +64 -2
  97. package/dist/generated/engineAskFaceTables.js +2 -2
  98. package/dist/generated/engineFactTables.d.ts +6 -3
  99. package/dist/generated/engineFactTables.js +22 -4
  100. package/dist/generated/engineNoticeTables.js +9 -0
  101. package/dist/generated/toolNameTables.d.ts +1 -0
  102. package/dist/generated/toolNameTables.js +1 -0
  103. package/dist/hitl/allowRuleShadow.d.ts +6 -1
  104. package/dist/hitl/allowRuleShadow.js +21 -17
  105. package/dist/hitl/approvalCardReading.d.ts +15 -0
  106. package/dist/hitl/approvalCardReading.js +154 -0
  107. package/dist/hitl/approvalCardReasons.d.ts +13 -0
  108. package/dist/hitl/approvalCardReasons.js +120 -0
  109. package/dist/hitl/approvalHolds.d.ts +47 -15
  110. package/dist/hitl/approvalHolds.js +134 -28
  111. package/dist/hitl/approvalResolution.d.ts +51 -26
  112. package/dist/hitl/approvalResolution.js +86 -122
  113. package/dist/hitl/approvalsFeed.js +6 -18
  114. package/dist/hitl/askClassPair.d.ts +2 -0
  115. package/dist/hitl/askClassPair.js +1 -0
  116. package/dist/hitl/askGateWire.d.ts +20 -2
  117. package/dist/hitl/askGateWire.js +49 -1
  118. package/dist/hitl/askParkRowRouting.d.ts +5 -0
  119. package/dist/hitl/askParkRowRouting.js +32 -1
  120. package/dist/hitl/askSettlementLine.d.ts +18 -0
  121. package/dist/hitl/askSettlementLine.js +79 -0
  122. package/dist/hitl/automaticAnswerVerdict.d.ts +1 -1
  123. package/dist/hitl/automaticAnswerVerdict.js +21 -5
  124. package/dist/hitl/crashConverged.d.ts +11 -20
  125. package/dist/hitl/crashConverged.js +56 -0
  126. package/dist/hitl/crashConvergedNotice.d.ts +3 -1
  127. package/dist/hitl/crashConvergedNotice.js +48 -11
  128. package/dist/hitl/decisionConflict.js +3 -10
  129. package/dist/hitl/decisionRefusal.d.ts +40 -1
  130. package/dist/hitl/decisionRefusal.js +199 -42
  131. package/dist/hitl/frameRouter.d.ts +5 -2
  132. package/dist/hitl/frameRouter.js +20 -16
  133. package/dist/hitl/gateLedger.d.ts +2 -0
  134. package/dist/hitl/gateLedger.js +50 -13
  135. package/dist/hitl/hitlBridge.d.ts +1 -2
  136. package/dist/hitl/hitlBridge.js +10 -7
  137. package/dist/hitl/livePendingAsk.d.ts +3 -3
  138. package/dist/hitl/livePendingAsk.js +7 -18
  139. package/dist/hitl/parkResolver.d.ts +2 -24
  140. package/dist/hitl/parkResolver.js +61 -97
  141. package/dist/hitl/persistedRulesWire.d.ts +24 -0
  142. package/dist/hitl/persistedRulesWire.js +122 -2
  143. package/dist/hitl/planReviewWire.d.ts +26 -13
  144. package/dist/hitl/planReviewWire.js +112 -78
  145. package/dist/hitl/refusedToolNameProse.d.ts +0 -1
  146. package/dist/hitl/refusedToolNameProse.js +0 -3
  147. package/dist/hitl/replayedApproval.d.ts +4 -3
  148. package/dist/hitl/replayedApproval.js +7 -4
  149. package/dist/hitl/sessionPolicyDeliverable.js +2 -4
  150. package/dist/hitl/sessionPolicyWire.d.ts +32 -20
  151. package/dist/hitl/sessionPolicyWire.js +162 -52
  152. package/dist/hitl/standingRows.d.ts +20 -2
  153. package/dist/hitl/standingRows.js +69 -8
  154. package/dist/hitl/toolApprovalWire.d.ts +87 -152
  155. package/dist/hitl/toolApprovalWire.js +591 -380
  156. package/dist/hookMatcherVerdict.d.ts +6 -1
  157. package/dist/hookMatcherVerdict.js +14 -7
  158. package/dist/hooksWireCaps.d.ts +2 -2
  159. package/dist/hooksWireCaps.js +59 -55
  160. package/dist/i18n/messages.en.d.ts +121 -0
  161. package/dist/i18n/messages.en.js +119 -0
  162. package/dist/i18n/messages.zh.d.ts +3 -0
  163. package/dist/i18n/messages.zh.js +120 -0
  164. package/dist/i18n/translate.d.ts +25 -0
  165. package/dist/i18n/translate.js +127 -0
  166. package/dist/index.d.ts +53 -20
  167. package/dist/index.js +38 -11
  168. package/dist/inheritEnvCapability.d.ts +31 -0
  169. package/dist/inheritEnvCapability.js +104 -0
  170. package/dist/liveModelCatalog.d.ts +1 -2
  171. package/dist/liveModelCatalog.js +2 -6
  172. package/dist/liveQuestionStore.d.ts +2 -21
  173. package/dist/mcpLiveness.d.ts +2 -1
  174. package/dist/mcpProbeWire.d.ts +6 -1
  175. package/dist/mcpProbeWire.js +1 -1
  176. package/dist/mcpWireCaps.d.ts +13 -2
  177. package/dist/mcpWireCaps.js +48 -0
  178. package/dist/memoryEntriesWire.d.ts +4 -9
  179. package/dist/model/modelSupplyRules.d.ts +0 -2
  180. package/dist/model/modelSupplyRules.js +3 -12
  181. package/dist/model/modelsPolicy.d.ts +0 -5
  182. package/dist/model/modelsPolicy.js +6 -36
  183. package/dist/model/subagentModelWord.d.ts +31 -0
  184. package/dist/model/subagentModelWord.js +261 -0
  185. package/dist/notifications.d.ts +6 -0
  186. package/dist/notifications.js +71 -23
  187. package/dist/outputStyleSeatCapability.d.ts +0 -3
  188. package/dist/outputStyleSeatCapability.js +0 -3
  189. package/dist/ownKey.d.ts +15 -0
  190. package/dist/ownKey.js +60 -3
  191. package/dist/panelReduce.d.ts +15 -9
  192. package/dist/panelReduce.js +680 -549
  193. package/dist/panelStateHolder.d.ts +3 -7
  194. package/dist/panelStateHolder.js +14 -130
  195. package/dist/peerLaneCapability.d.ts +0 -4
  196. package/dist/peerLaneCapability.js +0 -6
  197. package/dist/permissionRulesWriteCapability.d.ts +0 -4
  198. package/dist/permissionRulesWriteCapability.js +0 -6
  199. package/dist/permissionWireCaps.d.ts +2 -2
  200. package/dist/permissionWireCaps.js +2 -0
  201. package/dist/permissionsAllowGrantCapability.d.ts +0 -4
  202. package/dist/permissionsAllowGrantCapability.js +0 -6
  203. package/dist/permissionsDeclaration.d.ts +0 -11
  204. package/dist/permissionsDeclaration.js +60 -53
  205. package/dist/pluginHooksWire.d.ts +1 -1
  206. package/dist/pluginHooksWire.js +3 -6
  207. package/dist/preemptWire.d.ts +44 -0
  208. package/dist/preemptWire.js +83 -0
  209. package/dist/printToolResultFrame.js +15 -4
  210. package/dist/replayBody.d.ts +3 -0
  211. package/dist/replayBody.js +14 -0
  212. package/dist/request/taskRequest.d.ts +0 -1
  213. package/dist/request/taskRequest.js +0 -3
  214. package/dist/resumeRefusalCopy.d.ts +2 -1
  215. package/dist/resumeRefusalCopy.js +38 -2
  216. package/dist/retryStatus.d.ts +8 -0
  217. package/dist/retryStatus.js +106 -1
  218. package/dist/rewindArchiveCapability.d.ts +0 -4
  219. package/dist/rewindArchiveCapability.js +2 -10
  220. package/dist/rewindArchiveReading.d.ts +0 -1
  221. package/dist/rewindArchiveReading.js +0 -3
  222. package/dist/runCancelContext.d.ts +2 -1
  223. package/dist/runCancelContext.js +4 -2
  224. package/dist/runReattachPolicy.d.ts +31 -0
  225. package/dist/runReattachPolicy.js +46 -0
  226. package/dist/runTerminal.d.ts +3 -6
  227. package/dist/runTerminal.js +8 -9
  228. package/dist/sandboxWire.d.ts +3 -1
  229. package/dist/sandboxWire.js +76 -27
  230. package/dist/scenarioWire.js +1 -1
  231. package/dist/seam.d.ts +24 -2
  232. package/dist/seam.js +15 -3
  233. package/dist/seatContract.d.ts +53 -10
  234. package/dist/seatContract.js +132 -81
  235. package/dist/sessionPolicyCapability.d.ts +0 -4
  236. package/dist/sessionPolicyCapability.js +0 -6
  237. package/dist/sessionSyncFileHistoryCapability.d.ts +0 -3
  238. package/dist/sessionSyncFileHistoryCapability.js +0 -3
  239. package/dist/skillToolListsCapability.d.ts +0 -3
  240. package/dist/skillToolListsCapability.js +0 -3
  241. package/dist/sqlEngineCapability.d.ts +0 -4
  242. package/dist/sqlEngineCapability.js +0 -6
  243. package/dist/steerPriorityCapability.d.ts +0 -3
  244. package/dist/steerPriorityCapability.js +0 -3
  245. package/dist/streamDrainingLegs.d.ts +4 -0
  246. package/dist/streamDrainingLegs.js +6 -1
  247. package/dist/subagent/engineCompactWire.d.ts +0 -1
  248. package/dist/subagent/engineCompactWire.js +7 -5
  249. package/dist/subagent/engineTaskHandleWire.js +4 -7
  250. package/dist/subagentContentStore.js +1 -11
  251. package/dist/submitReopen.d.ts +24 -0
  252. package/dist/submitReopen.js +75 -0
  253. package/dist/terminalStatuses.d.ts +3 -0
  254. package/dist/terminalStatuses.js +10 -2
  255. package/dist/toolResult.d.ts +10 -1
  256. package/dist/toolResult.js +34 -6
  257. package/dist/toolRoster.d.ts +0 -2
  258. package/dist/toolRoster.js +0 -2
  259. package/dist/turnFailurePresentation.d.ts +28 -0
  260. package/dist/turnFailurePresentation.js +205 -0
  261. package/dist/userInvokedSkillsCapability.d.ts +0 -3
  262. package/dist/userInvokedSkillsCapability.js +0 -3
  263. package/dist/webSearchBackendCapability.d.ts +0 -4
  264. package/dist/webSearchBackendCapability.js +0 -6
  265. package/dist/wireErrorTriage.js +5 -24
  266. package/dist/wireFailureShape.d.ts +62 -5
  267. package/dist/wireFailureShape.js +105 -5
  268. package/dist/wireRefusalCopy.d.ts +12 -2
  269. package/dist/wireRefusalCopy.js +50 -52
  270. package/dist/workflowsJournalCapability.d.ts +0 -4
  271. package/dist/workflowsJournalCapability.js +0 -6
  272. package/dist/writeProtectionCapability.d.ts +0 -4
  273. package/dist/writeProtectionCapability.js +0 -6
  274. package/docs/INTEGRATION-CLIENTS.md +1253 -46
  275. package/package.json +5 -4
  276. package/dist/attachmentSeatCapability.d.ts +0 -12
  277. package/dist/attachmentSeatCapability.js +0 -23
  278. package/dist/hitl/approvalOutcomeNote.d.ts +0 -10
  279. package/dist/hitl/approvalOutcomeNote.js +0 -29
  280. package/dist/sessionReadRedactionCapability.d.ts +0 -13
  281. package/dist/sessionReadRedactionCapability.js +0 -35
@@ -1,8 +1,9 @@
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';
4
+ import { CORE_ALLOW_ENTRY_INERT_REASONS, CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
5
5
  export type LegacyToolNameVerdict = Exclude<(typeof CORE_RULE_TOOL_NAME_VERDICTS)[number], 'current'>;
6
+ export type SessionRuleInertReason = (typeof CORE_ALLOW_ENTRY_INERT_REASONS)[number];
6
7
  export interface SessionPolicyFacade {
7
8
  getPolicy(sessionId: string, opts?: {
8
9
  signal?: AbortSignal;
@@ -41,10 +42,16 @@ export type SessionPolicyFailure = {
41
42
  kind: 'request-rejected';
42
43
  message: string;
43
44
  } | {
44
- kind: 'legacy-tool-name';
45
+ kind: 'earlier-grammar';
46
+ message: string;
47
+ field?: 'toolAllow';
48
+ entry?: string;
49
+ } | {
50
+ kind: 'inert-entry';
45
51
  message: string;
46
52
  field?: SessionPolicyRemovedEntry['field'];
47
- name?: string;
53
+ entry?: string;
54
+ reason?: SessionRuleInertReason;
48
55
  verdict?: LegacyToolNameVerdict;
49
56
  currentName?: string;
50
57
  } | {
@@ -58,11 +65,12 @@ export type SessionPolicyFailure = {
58
65
  message: string;
59
66
  };
60
67
  export declare function classifySessionPolicyFailure(e: unknown): SessionPolicyFailure;
61
- export type SessionPolicyMalformedWhy = 'envelope' | 'rev' | 'rule_field';
68
+ export type SessionPolicyMalformedWhy = 'envelope' | 'rev' | 'rule_field' | 'grammar_stamp';
62
69
  export type SessionPolicyReadOutcome = {
63
70
  kind: 'present';
64
71
  rev: number;
65
72
  rules: SessionPermissionRules;
73
+ grammar?: number;
66
74
  } | {
67
75
  kind: 'malformed';
68
76
  why: SessionPolicyMalformedWhy;
@@ -82,6 +90,11 @@ export interface SessionPolicyTightenRequest {
82
90
  }
83
91
  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
92
  export declare const SESSION_POLICY_TIGHTEN_UNKNOWN_WHY: readonly SessionPolicyTightenUnknownWhy[];
93
+ export type SessionPolicyEarlierGrammarRefusal = {
94
+ kind: 'refused';
95
+ why: 'earlier_grammar_rows';
96
+ entries: readonly SessionPolicyRemovedEntry[];
97
+ };
85
98
  export type SessionPolicyTightenOutcome = {
86
99
  kind: 'written';
87
100
  rev: number;
@@ -94,19 +107,23 @@ export type SessionPolicyTightenOutcome = {
94
107
  } | {
95
108
  kind: 'capability_absent';
96
109
  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;
110
+ } | SessionPolicyEarlierGrammarRefusal | ({
111
+ kind: 'inert_entry';
112
+ } & SessionPolicyInertEntryRefusal & {
104
113
  alreadyInRecord?: boolean;
105
- } | {
114
+ }) | {
106
115
  kind: 'unknown';
107
116
  why: SessionPolicyTightenUnknownWhy;
108
117
  message?: string;
109
118
  };
119
+ export interface SessionPolicyInertEntryRefusal {
120
+ message: string;
121
+ field?: SessionPolicyRemovedEntry['field'];
122
+ entry?: string;
123
+ reason?: SessionRuleInertReason;
124
+ verdict?: LegacyToolNameVerdict;
125
+ currentName?: string;
126
+ }
110
127
  export declare function tightenSessionPolicy(facade: SessionPolicyFacade, req: SessionPolicyTightenRequest, opts?: {
111
128
  signal?: AbortSignal;
112
129
  }): Promise<SessionPolicyTightenOutcome>;
@@ -153,14 +170,9 @@ export type SessionPolicyRemovalOutcome = {
153
170
  kind: 'write_unconfirmed';
154
171
  why: Extract<SessionPolicyTightenUnknownWhy, 'write_indeterminate' | 'write_receipt_unreadable' | 'write_receipt_mismatch'>;
155
172
  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
- };
173
+ } | SessionPolicyEarlierGrammarRefusal | ({
174
+ kind: 'inert_entry';
175
+ } & SessionPolicyInertEntryRefusal);
164
176
  export type SessionPolicyRosterRequiredWhy = 'absent' | 'unreadable';
165
177
  export declare function removeRefusedSessionRules(facade: SessionPolicyFacade, sessionId: string, roster: ToolRosterView | undefined, opts?: {
166
178
  signal?: AbortSignal;
@@ -1,10 +1,16 @@
1
1
  import { wireFailureShapeOf } from '../wireFailureShape.js';
2
2
  import { hostLog } from '../host.js';
3
3
  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';
4
+ import { RULES_INERT_ENTRY, RULES_INVALID_SHAPE, isRulesRefusalCode } from '../engineErrorCodes.js';
5
+ import { deniedEntryToolName } from '../engineToolDirectory.js';
6
6
  import { rosterMountedNames } from '../rosterMountedNames.js';
7
- import { CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
7
+ import { CORE_ALLOW_ENTRY_INERT_REASONS, CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
8
+ const EARLIER_GRAMMAR_REASON = 'earlier_grammar';
9
+ const _inertReasonPin = true;
10
+ void _inertReasonPin;
11
+ function isSessionRuleInertReason(v) {
12
+ return typeof v === 'string' && CORE_ALLOW_ENTRY_INERT_REASONS.includes(v);
13
+ }
8
14
  function isLegacyToolNameVerdict(v) {
9
15
  return typeof v === 'string' && v !== 'current' && CORE_RULE_TOOL_NAME_VERDICTS.includes(v);
10
16
  }
@@ -12,25 +18,43 @@ const RULE_FIELD_PRESENCE = {
12
18
  toolAllow: true,
13
19
  toolDeny: true,
14
20
  allowDirs: true,
15
- commandAllow: true,
16
- commandDeny: true,
17
21
  };
22
+ const RECORD_META_KEYS = Object.freeze(['rev', 'gen', 'grammar']);
23
+ const _metaKeysPin = true;
24
+ void _metaKeysPin;
18
25
  export const SESSION_POLICY_RULE_FIELDS = Object.freeze(Object.keys(RULE_FIELD_PRESENCE));
26
+ function foreignRecordKeysOf(record) {
27
+ return Object.getOwnPropertyNames(record).filter((key) => !SESSION_POLICY_RULE_FIELDS.includes(key) && !RECORD_META_KEYS.includes(key));
28
+ }
29
+ function recordKeyLabel(key) {
30
+ return /^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(key) ? `"${key}"` : 'unnamed';
31
+ }
19
32
  const MAX_RULE_ENTRIES = 10_000;
20
33
  export function classifySessionPolicyFailure(e) {
21
- const { status, errorCode, message, bodyField, bodyName, bodyVerdict, bodyCurrentName } = wireFailureShapeOf(e);
34
+ const { status, errorCode, message, bodyField, bodyVerdict, bodyCurrentName, bodyEntry, bodyReason } = wireFailureShapeOf(e);
22
35
  if (errorCode === undefined || errorCode === '')
23
36
  return { kind: 'no-verdict', message };
24
- if (errorCode === RULES_LEGACY_TOOL_NAME) {
37
+ if (errorCode === RULES_INERT_ENTRY && bodyReason === EARLIER_GRAMMAR_REASON && status !== undefined && status >= 400 && status < 500) {
38
+ return {
39
+ kind: 'earlier-grammar',
40
+ message,
41
+ ...(bodyField === 'toolAllow' ? { field: bodyField } : {}),
42
+ ...(bodyEntry !== undefined && bodyEntry !== '' ? { entry: bodyEntry } : {}),
43
+ };
44
+ }
45
+ if (errorCode === RULES_INERT_ENTRY && status !== undefined && status >= 400 && status < 500) {
25
46
  return {
26
- kind: 'legacy-tool-name',
47
+ kind: 'inert-entry',
27
48
  message,
28
49
  ...(bodyField === 'toolDeny' || bodyField === 'toolAllow' ? { field: bodyField } : {}),
29
- ...(bodyName !== undefined && bodyName !== '' ? { name: bodyName } : {}),
50
+ ...(bodyEntry !== undefined && bodyEntry !== '' ? { entry: bodyEntry } : {}),
51
+ ...(isSessionRuleInertReason(bodyReason) ? { reason: bodyReason } : {}),
30
52
  ...(isLegacyToolNameVerdict(bodyVerdict) ? { verdict: bodyVerdict } : {}),
31
53
  ...(bodyCurrentName !== undefined && bodyCurrentName !== '' ? { currentName: bodyCurrentName } : {}),
32
54
  };
33
55
  }
56
+ if (errorCode === RULES_INVALID_SHAPE && status !== undefined && status >= 400 && status < 500)
57
+ return { kind: 'request-rejected', message };
34
58
  if (isRulesRefusalCode(errorCode) && status !== undefined && status >= 400 && status < 500)
35
59
  return { kind: 'error', message };
36
60
  if (status === 501 || errorCode === 'capability.session_store_required') {
@@ -95,6 +119,21 @@ function narrowStoredRecord(body) {
95
119
  if (typeof rev !== 'number' || !Number.isSafeInteger(rev) || rev < 0) {
96
120
  return { ok: false, why: 'rev', detail: 'policy record carries no readable version stamp' };
97
121
  }
122
+ let grammarSlot;
123
+ try {
124
+ grammarSlot = Object.getOwnPropertyDescriptor(envelope, 'grammar');
125
+ }
126
+ catch {
127
+ return { ok: false, why: 'grammar_stamp', detail: 'policy record’s rule-grammar stamp could not be read' };
128
+ }
129
+ let grammar;
130
+ if (grammarSlot !== undefined) {
131
+ const v = Object.hasOwn(grammarSlot, 'value') ? grammarSlot.value : undefined;
132
+ if (typeof v !== 'number' || !Number.isSafeInteger(v) || v < 0) {
133
+ return { ok: false, why: 'grammar_stamp', detail: 'policy record carries a rule-grammar stamp that is not a whole number of 0 or more' };
134
+ }
135
+ grammar = v;
136
+ }
98
137
  const rules = {};
99
138
  for (const field of SESSION_POLICY_RULE_FIELDS) {
100
139
  if (!Object.hasOwn(envelope, field))
@@ -107,7 +146,19 @@ function narrowStoredRecord(body) {
107
146
  return { ok: false, why: 'rule_field', detail: read.detail };
108
147
  rules[field] = read.list;
109
148
  }
110
- return { ok: true, rev, rules };
149
+ for (const key of foreignRecordKeysOf(envelope)) {
150
+ const slot = Object.getOwnPropertyDescriptor(envelope, key);
151
+ if (slot === undefined)
152
+ continue;
153
+ if (!Object.hasOwn(slot, 'value') || Array.isArray(slot.value)) {
154
+ return {
155
+ ok: false,
156
+ why: 'rule_field',
157
+ 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`,
158
+ };
159
+ }
160
+ }
161
+ return { ok: true, rev, rules, grammar };
111
162
  }
112
163
  export async function readSessionPolicy(facade, sessionId, opts) {
113
164
  let body;
@@ -126,7 +177,7 @@ export async function readSessionPolicy(facade, sessionId, opts) {
126
177
  }
127
178
  if (!read.ok)
128
179
  return { kind: 'malformed', why: read.why, detail: read.detail };
129
- return { kind: 'present', rev: read.rev, rules: read.rules };
180
+ return { kind: 'present', rev: read.rev, rules: read.rules, ...(read.grammar !== undefined ? { grammar: read.grammar } : {}) };
130
181
  }
131
182
  export function tightenedSessionRules(prior, add) {
132
183
  const p = narrowRulesInput(prior, 'prior');
@@ -159,6 +210,10 @@ function narrowRulesInput(raw, side) {
159
210
  if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
160
211
  throw new TypeError(`tightenedSessionRules: "${side}" must be a rules record object`);
161
212
  }
213
+ const foreign = foreignRecordKeysOf(raw);
214
+ if (foreign.length > 0) {
215
+ 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`);
216
+ }
162
217
  const out = {};
163
218
  for (const field of SESSION_POLICY_RULE_FIELDS) {
164
219
  if (!Object.hasOwn(raw, field))
@@ -173,10 +228,13 @@ function narrowRulesInput(raw, side) {
173
228
  }
174
229
  return out;
175
230
  }
176
- function receiptMismatch(sent, receipt, sentRev, receiptRev) {
231
+ function receiptMismatch(sent, receipt, sentRev, receiptRev, sentGrammar, receiptGrammar) {
177
232
  if (receiptRev <= sentRev) {
178
233
  return `policy write receipt did not advance the version stamp (sent with ${sentRev}, receipt says ${receiptRev})`;
179
234
  }
235
+ if (sentGrammar !== undefined && (receiptGrammar === undefined || receiptGrammar < sentGrammar)) {
236
+ 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`;
237
+ }
180
238
  for (const field of SESSION_POLICY_RULE_FIELDS) {
181
239
  const s = Object.hasOwn(sent, field) ? sent[field] : undefined;
182
240
  if (s === undefined)
@@ -227,32 +285,66 @@ function unknownFrom(failure, leg) {
227
285
  return { kind: 'unknown', why: leg === 'read' ? 'read_failed' : 'write_indeterminate', message };
228
286
  case 'loosen-forbidden':
229
287
  case 'cas-conflict':
230
- case 'legacy-tool-name':
288
+ case 'earlier-grammar':
289
+ case 'inert-entry':
231
290
  case 'error':
232
291
  return { kind: 'unknown', why: leg === 'read' ? 'read_failed' : 'write_refused', message };
233
292
  }
234
293
  }
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
- }
294
+ function alreadyInRecordOf(field, entry, prior, add) {
295
+ if (field === undefined || entry === undefined)
296
+ return undefined;
297
+ const inPrior = Object.hasOwn(prior, field) ? prior[field] : undefined;
298
+ const inAdd = Object.hasOwn(add, field) ? add[field] : undefined;
299
+ if (inPrior !== undefined && inPrior.includes(entry))
300
+ return true;
301
+ if (inAdd !== undefined && inAdd.includes(entry))
302
+ return false;
303
+ return undefined;
304
+ }
305
+ function inertEntryRefusalOf(failure) {
306
+ const { field, entry, reason, verdict, currentName } = failure;
246
307
  return {
247
- kind: 'legacy_tool_name',
248
308
  message: failure.message,
249
309
  ...(field !== undefined ? { field } : {}),
250
- ...(name !== undefined ? { name } : {}),
310
+ ...(entry !== undefined ? { entry } : {}),
311
+ ...(reason !== undefined ? { reason } : {}),
251
312
  ...(verdict !== undefined ? { verdict } : {}),
252
313
  ...(currentName !== undefined ? { currentName } : {}),
253
- ...(alreadyInRecord !== undefined ? { alreadyInRecord } : {}),
254
314
  };
255
315
  }
316
+ function earlierGrammarEntriesOf(prior, outgoing) {
317
+ const priorAllow = Object.hasOwn(prior, 'toolAllow') ? prior.toolAllow : undefined;
318
+ const outAllow = Object.hasOwn(outgoing, 'toolAllow') ? outgoing.toolAllow : undefined;
319
+ if (priorAllow === undefined || outAllow === undefined)
320
+ return [];
321
+ const carried = new Set(outAllow);
322
+ const seen = new Set();
323
+ const out = [];
324
+ for (const rule of priorAllow) {
325
+ if (seen.has(rule) || !carried.has(rule))
326
+ continue;
327
+ const name = deniedEntryToolName(rule);
328
+ if (name === undefined || name === rule)
329
+ continue;
330
+ seen.add(rule);
331
+ out.push({ field: 'toolAllow', rule });
332
+ }
333
+ return out;
334
+ }
335
+ function earlierGrammarOutcome(failure) {
336
+ return {
337
+ kind: 'refused',
338
+ why: 'earlier_grammar_rows',
339
+ entries: Object.hasOwn(failure, 'field') && Object.hasOwn(failure, 'entry') && failure.field !== undefined && failure.entry !== undefined ? [{ field: failure.field, rule: failure.entry }] : [],
340
+ };
341
+ }
342
+ function rulesWithStamp(rules, grammar) {
343
+ return grammar === undefined ? rules : { ...rules, grammar };
344
+ }
345
+ function grammarStampOf(read) {
346
+ return Object.hasOwn(read, 'grammar') ? read.grammar : undefined;
347
+ }
256
348
  async function attemptTighten(facade, req, opts) {
257
349
  const read = await readSessionPolicy(facade, req.sessionId, opts);
258
350
  if (read.kind === 'failed')
@@ -261,9 +353,15 @@ async function attemptTighten(facade, req, opts) {
261
353
  return { done: { kind: 'unknown', why: 'read_unreadable', message: read.detail } };
262
354
  }
263
355
  const rules = tightenedSessionRules(read.rules, req.add);
356
+ const stamp = grammarStampOf(read);
357
+ if (stamp === undefined) {
358
+ const earlier = earlierGrammarEntriesOf(read.rules, rules);
359
+ if (earlier.length > 0)
360
+ return { done: { kind: 'refused', why: 'earlier_grammar_rows', entries: earlier } };
361
+ }
264
362
  let receipt;
265
363
  try {
266
- receipt = await facade.putPolicy(req.sessionId, { rules, expectedRev: read.rev }, opts);
364
+ receipt = await facade.putPolicy(req.sessionId, { rules: rulesWithStamp(rules, stamp), expectedRev: read.rev }, opts);
267
365
  }
268
366
  catch (e) {
269
367
  const failure = classifySessionPolicyFailure(e);
@@ -271,8 +369,12 @@ async function attemptTighten(facade, req, opts) {
271
369
  return { done: { kind: 'loosen_forbidden', message: failure.message } };
272
370
  if (failure.kind === 'cas-conflict')
273
371
  return { retryAtRev: read.rev };
274
- if (failure.kind === 'legacy-tool-name')
275
- return { done: legacyToolNameOutcome(failure, read.rules, req.add) };
372
+ if (failure.kind === 'earlier-grammar')
373
+ return { done: earlierGrammarOutcome(failure) };
374
+ if (failure.kind === 'inert-entry') {
375
+ const alreadyInRecord = alreadyInRecordOf(failure.field, failure.entry, read.rules, req.add);
376
+ return { done: { kind: 'inert_entry', ...inertEntryRefusalOf(failure), ...(alreadyInRecord !== undefined ? { alreadyInRecord } : {}) } };
377
+ }
276
378
  return { done: unknownFrom(failure, 'write') };
277
379
  }
278
380
  let narrowed;
@@ -285,7 +387,7 @@ async function attemptTighten(facade, req, opts) {
285
387
  if (!narrowed.ok) {
286
388
  return { done: { kind: 'unknown', why: 'write_receipt_unreadable', message: narrowed.detail } };
287
389
  }
288
- const mismatch = receiptMismatch(rules, narrowed.rules, read.rev, narrowed.rev);
390
+ const mismatch = receiptMismatch(rules, narrowed.rules, read.rev, narrowed.rev, stamp, narrowed.grammar);
289
391
  if (mismatch !== null)
290
392
  return { done: { kind: 'unknown', why: 'write_receipt_mismatch', message: mismatch } };
291
393
  return { done: { kind: 'written', rev: narrowed.rev } };
@@ -309,6 +411,8 @@ export async function tightenSessionPolicy(facade, req, opts) {
309
411
  return second.done;
310
412
  return { kind: 'conflict', rev: second.retryAtRev };
311
413
  }
414
+ const SESSION_POLICY_INERT_ENTRY_WRITE_NOTE = 'The engine refused to write these session rules, and the stored record was not changed at all: one entry in them reads as matching nothing — written in, it would neither block nor allow any call (the engine’s own error text names the entry and why). Correct that entry or take it out, then write the rules again. If that entry was already in this session’s rule record, it has to be removed from the record before the record can be written again.';
415
+ const SESSION_POLICY_EARLIER_GRAMMAR_NOTE = 'Nothing was written: this session’s rules were saved before the current rule grammar, when an allow entry written in a tool’s own rule form (such as Bash(git:*)) was read as a plain tool name and so allowed nothing. Writing the record back now would read those entries in the current grammar, where they do allow calls — a widening nobody asked for — so the record was left as it is. Rewrite this session’s allow entries in the current form, or take those entries out, before changing its rules here.';
312
416
  export function sessionPolicyTightenNotice(outcome) {
313
417
  switch (outcome.kind) {
314
418
  case 'written':
@@ -321,8 +425,10 @@ export function sessionPolicyTightenNotice(outcome) {
321
425
  return outcome.why === 'absent'
322
426
  ? 'This deployment does not keep per-session tool rules, so there is nothing to tighten here.'
323
427
  : '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;
428
+ case 'refused':
429
+ return SESSION_POLICY_EARLIER_GRAMMAR_NOTE;
430
+ case 'inert_entry':
431
+ return SESSION_POLICY_INERT_ENTRY_WRITE_NOTE;
326
432
  case 'unknown':
327
433
  switch (outcome.why) {
328
434
  case 'face_unavailable':
@@ -348,7 +454,7 @@ export function sessionPolicyTightenNotice(outcome) {
348
454
  case 'write_receipt_unreadable':
349
455
  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.';
350
456
  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.';
457
+ return 'The tightening was sent but the engine’s answer does not account for it (the version did not advance, the entries sent here are not in the returned record, or the record came back without the rule-grammar mark it was sent with, so its entries may now read differently) — it may already be in effect. Re-read the current rules before writing anything else.';
352
458
  }
353
459
  }
354
460
  }
@@ -371,8 +477,10 @@ function readFailureSentence(kind) {
371
477
  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
478
  case 'request-rejected':
373
479
  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.';
480
+ case 'earlier-grammar':
481
+ 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.';
482
+ case 'inert-entry':
483
+ 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
484
  case 'unauthorized':
377
485
  return 'This call could not present a verifiable identity, so this session’s rule record could not be read.';
378
486
  case 'error':
@@ -389,6 +497,8 @@ function readMalformedSentence(why) {
389
497
  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
498
  case 'rule_field':
391
499
  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.';
500
+ case 'grammar_stamp':
501
+ 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
502
  }
393
503
  }
394
504
  function ownRead(o, key) {
@@ -492,9 +602,15 @@ async function attemptRemoval(facade, sessionId, mounted, opts) {
492
602
  const { next, removed } = judged;
493
603
  if (removed.length === 0)
494
604
  return { done: { kind: 'nothing_to_remove', rev: read.rev } };
605
+ const stamp = grammarStampOf(read);
606
+ if (stamp === undefined) {
607
+ const earlier = earlierGrammarEntriesOf(read.rules, next);
608
+ if (earlier.length > 0)
609
+ return { done: { kind: 'refused', why: 'earlier_grammar_rows', entries: earlier } };
610
+ }
495
611
  let receipt;
496
612
  try {
497
- receipt = await facade.putPolicy(sessionId, { rules: next, expectedRev: read.rev }, opts);
613
+ receipt = await facade.putPolicy(sessionId, { rules: rulesWithStamp(next, stamp), expectedRev: read.rev }, opts);
498
614
  }
499
615
  catch (e) {
500
616
  const failure = classifySessionPolicyFailure(e);
@@ -502,18 +618,10 @@ async function attemptRemoval(facade, sessionId, mounted, opts) {
502
618
  return { done: { kind: 'loosen_forbidden', message: failure.message, refused: removed } };
503
619
  if (failure.kind === 'cas-conflict')
504
620
  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
- }
621
+ if (failure.kind === 'earlier-grammar')
622
+ return { done: earlierGrammarOutcome(failure) };
623
+ if (failure.kind === 'inert-entry')
624
+ return { done: { kind: 'inert_entry', ...inertEntryRefusalOf(failure) } };
517
625
  return { done: removalFailed(failure, 'write') };
518
626
  }
519
627
  let narrowed;
@@ -525,7 +633,7 @@ async function attemptRemoval(facade, sessionId, mounted, opts) {
525
633
  }
526
634
  if (!narrowed.ok)
527
635
  return { done: { kind: 'write_unconfirmed', why: 'write_receipt_unreadable', message: narrowed.detail } };
528
- const mismatch = receiptMismatch(next, narrowed.rules, read.rev, narrowed.rev) ??
636
+ const mismatch = receiptMismatch(next, narrowed.rules, read.rev, narrowed.rev, stamp, narrowed.grammar) ??
529
637
  (removed.some((e) => {
530
638
  const r = Object.hasOwn(narrowed.rules, e.field) ? narrowed.rules[e.field] : undefined;
531
639
  return r !== undefined && r.includes(e.rule);
@@ -609,8 +717,10 @@ export function sessionPolicyRemovalNotice(outcome) {
609
717
  }
610
718
  case 'write_unconfirmed':
611
719
  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.';
720
+ case 'refused':
721
+ return SESSION_POLICY_EARLIER_GRAMMAR_NOTE;
722
+ case 'inert_entry':
723
+ 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
724
  default:
615
725
  return generic;
616
726
  }
@@ -1,4 +1,6 @@
1
- export type StandingRowWhy = 'given' | 'card_unreadable' | 'remember_refused' | 'remember_unreadable' | 'route_cannot_carry' | 'classes_unreadable' | 'not_deployment_list_question' | 'evidence_unreadable' | 'offers_unreadable' | 'engine_lane_closed' | 'args_unavailable' | 'grant_binding' | 'tool_name_unreadable' | 'rule_cannot_clear' | 'host_fact_missing' | 'face_not_generic' | 'rule_not_sent';
1
+ import { type PermissionsKeptReason } from '../permissionsDeclaration.js';
2
+ import type { PermissionsAllowGrantReading } from '../permissionsAllowGrantCapability.js';
3
+ export type StandingRowWhy = 'given' | 'card_unreadable' | 'remember_refused' | 'remember_unreadable' | 'route_cannot_carry' | 'classes_unreadable' | 'not_deployment_list_question' | 'evidence_unreadable' | 'offers_unreadable' | 'engine_lane_closed' | 'args_unavailable' | 'grant_binding' | 'tool_name_unreadable' | 'rule_cannot_clear' | 'host_fact_missing' | 'card_face_absent' | 'face_not_generic' | 'rule_not_sent';
2
4
  export type StandingPersistentRow = 'engine_offer' | 'local_whole_tool' | 'none';
3
5
  export interface StandingRowsWhy {
4
6
  readonly session: StandingRowWhy;
@@ -12,8 +14,24 @@ export interface StandingRows {
12
14
  readonly why: StandingRowsWhy;
13
15
  }
14
16
  export interface StandingRowsHostFacts {
15
- readonly cardFace?: string;
16
17
  readonly wholeToolRuleSent?: boolean;
17
18
  }
18
19
  export declare function standingRowsOf(card: object | null | undefined, host?: StandingRowsHostFacts | null): StandingRows;
19
20
  export declare function seatSessionRowOf(card: object | null | undefined, requestId: string | undefined): boolean;
21
+ export type RememberOffer = 'offerable' | 'refused' | 'unknown';
22
+ export interface RememberOfferable {
23
+ readonly session: RememberOffer;
24
+ readonly persistentRule: RememberOffer;
25
+ readonly deploymentList: RememberOffer;
26
+ }
27
+ export declare function rememberOfferable(card: object | null | undefined, host?: StandingRowsHostFacts | null): RememberOfferable;
28
+ export interface LocalAllowRuleHostFacts {
29
+ readonly hostLadderEnforces?: boolean;
30
+ }
31
+ export interface LocalAllowRuleEnforcement {
32
+ readonly enforceable: boolean;
33
+ readonly by?: 'host_ladder' | 'engine_grant';
34
+ readonly engine: 'sent' | 'kept' | 'unreadable';
35
+ readonly keptReason?: PermissionsKeptReason;
36
+ }
37
+ export declare function localAllowRuleEnforceable(rule: string, caps: PermissionsAllowGrantReading, host?: LocalAllowRuleHostFacts | null): LocalAllowRuleEnforcement;
@@ -1,8 +1,9 @@
1
- import { readAskClassPairState } from './askClassPair.js';
1
+ import { DEPLOYMENT_LIST_ASK_CLASSES, readAskClassPairState } from './askClassPair.js';
2
2
  import { automaticAnswerVerdict } from './automaticAnswerVerdict.js';
3
- import { readRuleOffers } from './toolApprovalWire.js';
3
+ import { readApprovalCardWord, readRuleOffers } from './toolApprovalWire.js';
4
+ import { CORE_SKILL_TOOL_NAME } from '../generated/toolNameTables.js';
4
5
  import { toolPermissionRequestIdDomain } from '../seatContract.js';
5
- const DEPLOYMENT_LIST_ASK_CLASSES = Object.freeze(['ask_rule']);
6
+ import { permissionsDeclarationReport } from '../permissionsDeclaration.js';
6
7
  const UNREADABLE = Symbol('unreadable');
7
8
  function ownData(v, key) {
8
9
  try {
@@ -65,15 +66,17 @@ function persistentRowOf(card, host, evidenceUnreadable) {
65
66
  const tool = ownData(card, 'toolName');
66
67
  if (typeof tool !== 'string' || tool.length === 0)
67
68
  return { row: 'none', why: 'tool_name_unreadable' };
69
+ if (tool === CORE_SKILL_TOOL_NAME)
70
+ return { row: 'none', why: 'grant_binding' };
68
71
  if (automaticAnswerVerdict(card, 'standing_rule').kind !== 'may_answer')
69
72
  return { row: 'none', why: 'rule_cannot_clear' };
70
- if (typeof host !== 'object' || host === null)
71
- return { row: 'none', why: 'host_fact_missing' };
72
- const face = ownData(host, 'cardFace');
73
- if (typeof face !== 'string')
74
- return { row: 'none', why: 'host_fact_missing' };
73
+ const face = readApprovalCardWord(card);
74
+ if (face === undefined)
75
+ return { row: 'none', why: 'card_face_absent' };
75
76
  if (face !== 'generic')
76
77
  return { row: 'none', why: 'face_not_generic' };
78
+ if (typeof host !== 'object' || host === null)
79
+ return { row: 'none', why: 'host_fact_missing' };
77
80
  const sent = ownData(host, 'wholeToolRuleSent');
78
81
  if (typeof sent !== 'boolean')
79
82
  return { row: 'none', why: 'host_fact_missing' };
@@ -146,3 +149,61 @@ export function seatSessionRowOf(card, requestId) {
146
149
  return false;
147
150
  }
148
151
  }
152
+ const SESSION_OFFER = Object.freeze({
153
+ given: 'offerable',
154
+ card_unreadable: 'unknown',
155
+ remember_refused: 'refused',
156
+ remember_unreadable: 'unknown',
157
+ route_cannot_carry: 'refused',
158
+ classes_unreadable: 'unknown',
159
+ not_deployment_list_question: 'refused',
160
+ evidence_unreadable: 'unknown',
161
+ offers_unreadable: 'unknown',
162
+ engine_lane_closed: 'refused',
163
+ args_unavailable: 'refused',
164
+ grant_binding: 'refused',
165
+ tool_name_unreadable: 'unknown',
166
+ rule_cannot_clear: 'refused',
167
+ host_fact_missing: 'unknown',
168
+ card_face_absent: 'unknown',
169
+ face_not_generic: 'refused',
170
+ rule_not_sent: 'refused',
171
+ });
172
+ const PERSISTENT_OFFER = Object.freeze({ ...SESSION_OFFER, remember_refused: 'unknown' });
173
+ export function rememberOfferable(card, host) {
174
+ const rows = standingRowsOf(card, host);
175
+ return Object.freeze({
176
+ session: SESSION_OFFER[rows.why.session],
177
+ persistentRule: PERSISTENT_OFFER[rows.why.persistent],
178
+ deploymentList: SESSION_OFFER[rows.why.deploymentList],
179
+ });
180
+ }
181
+ export function localAllowRuleEnforceable(rule, caps, host) {
182
+ const ladder = typeof host === 'object' && host !== null && ownData(host, 'hostLadderEnforces') === true;
183
+ let engine = 'unreadable';
184
+ let keptReason;
185
+ if (typeof rule === 'string' && rule.trim() !== '') {
186
+ try {
187
+ const report = permissionsDeclarationReport({ deny: [], ask: [], allow: [rule], settingsRead: 'clean' }, caps);
188
+ const kept = report.kept.find((k) => k.bucket === 'allow' && k.rule === rule);
189
+ if (kept !== undefined) {
190
+ engine = 'kept';
191
+ keptReason = kept.reason;
192
+ }
193
+ else {
194
+ const sentAs = report.rewritten.find((r) => r.bucket === 'allow' && r.from === rule)?.to ?? rule;
195
+ engine = report.permissions?.allow?.includes(sentAs) === true ? 'sent' : 'unreadable';
196
+ }
197
+ }
198
+ catch {
199
+ engine = 'unreadable';
200
+ }
201
+ }
202
+ const by = ladder ? 'host_ladder' : engine === 'sent' ? 'engine_grant' : undefined;
203
+ return Object.freeze({
204
+ enforceable: by !== undefined,
205
+ ...(by !== undefined ? { by } : {}),
206
+ engine,
207
+ ...(keptReason !== undefined ? { keptReason } : {}),
208
+ });
209
+ }