@sema-agent/client-core 0.95.0 → 0.97.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 (233) hide show
  1. package/CHANGELOG.md +269 -0
  2. package/README.md +61 -31
  3. package/dist/adapt/arms.js +7 -1
  4. package/dist/adapt/toolCards.d.ts +1 -0
  5. package/dist/adapt/toolCards.js +16 -3
  6. package/dist/adapt/wireShapes.js +2 -16
  7. package/dist/adapter/activeRunSelfHeal.d.ts +4 -2
  8. package/dist/adapter/activeRunSelfHeal.js +32 -6
  9. package/dist/adapter/downstream/eventToSdkMessage.js +11 -3
  10. package/dist/adapter/downstream/terminalToSdkResult.js +14 -11
  11. package/dist/adapter/runStream.js +30 -47
  12. package/dist/agentSession/backgroundView.js +2 -15
  13. package/dist/agentSession/engineAgentRegistryRead.js +5 -4
  14. package/dist/agentSession/engineSessionBackgroundStop.js +22 -15
  15. package/dist/agentsWireCaps.d.ts +1 -1
  16. package/dist/agentsWireCaps.js +29 -12
  17. package/dist/approvalDecidedByCapability.d.ts +2 -0
  18. package/dist/approvalDecidedByCapability.js +0 -1
  19. package/dist/approvalsStreamLiveCapability.d.ts +2 -0
  20. package/dist/approvalsStreamLiveCapability.js +3 -2
  21. package/dist/attachmentSeatCapability.d.ts +2 -0
  22. package/dist/attachmentSeatCapability.js +0 -1
  23. package/dist/callerCwdCapability.d.ts +2 -4
  24. package/dist/callerCwdCapability.js +0 -7
  25. package/dist/centerWiringCapability.d.ts +2 -4
  26. package/dist/centerWiringCapability.js +4 -9
  27. package/dist/classifierVerdictWire.js +4 -16
  28. package/dist/clientSlice.js +1 -1
  29. package/dist/cloudConfigWireCaps.d.ts +4 -4
  30. package/dist/cloudConfigWireCaps.js +241 -85
  31. package/dist/coreValuePorts.d.ts +11 -7
  32. package/dist/coreValuePorts.js +5 -8
  33. package/dist/credentialProvenance.d.ts +28 -0
  34. package/dist/credentialProvenance.js +76 -0
  35. package/dist/decideFailureNote.js +3 -1
  36. package/dist/decideReceipt.js +7 -10
  37. package/dist/declared/supportFloorDeclared.js +3 -3
  38. package/dist/deviceExecutorManagementCapability.d.ts +2 -0
  39. package/dist/deviceExecutorManagementCapability.js +3 -2
  40. package/dist/displayUntrusted.d.ts +10 -3
  41. package/dist/displayUntrusted.js +371 -60
  42. package/dist/engineCapReader.d.ts +8 -2
  43. package/dist/engineCapReader.js +15 -7
  44. package/dist/engineCapsCache.d.ts +17 -3
  45. package/dist/engineCapsCache.js +268 -43
  46. package/dist/engineDirectory.d.ts +79 -0
  47. package/dist/engineDirectory.js +186 -0
  48. package/dist/engineErrorCodes.d.ts +17 -1
  49. package/dist/engineErrorCodes.js +46 -16
  50. package/dist/engineNoticeCodes.d.ts +17 -0
  51. package/dist/engineNoticeCodes.js +54 -1
  52. package/dist/executionLaneCapability.d.ts +2 -0
  53. package/dist/executionLaneCapability.js +3 -2
  54. package/dist/fileHistoryCaptureCapability.d.ts +16 -0
  55. package/dist/fileHistoryCaptureCapability.js +98 -2
  56. package/dist/fileHistoryLegNotice.js +4 -0
  57. package/dist/fleet/bgSubagentRow.d.ts +15 -0
  58. package/dist/fleet/bgSubagentRow.js +67 -0
  59. package/dist/fleet/fleetLedger.d.ts +1 -0
  60. package/dist/fleet/fleetLedger.js +164 -126
  61. package/dist/fleet/fleetProjection.d.ts +1 -0
  62. package/dist/fleet/fleetProjection.js +10 -3
  63. package/dist/fleetTaskDesc.js +4 -17
  64. package/dist/gateOutcome.d.ts +12 -1
  65. package/dist/gateOutcome.js +56 -1
  66. package/dist/gateVocabulary.d.ts +2 -2
  67. package/dist/gateVocabulary.js +4 -3
  68. package/dist/generated/engineAskFaceTables.js +1 -1
  69. package/dist/generated/engineFactTables.d.ts +5 -3
  70. package/dist/generated/engineFactTables.js +17 -4
  71. package/dist/generated/engineNoticeTables.js +12 -3
  72. package/dist/generated/sdkFactTables.d.ts +2 -0
  73. package/dist/generated/sdkFactTables.js +7 -0
  74. package/dist/generated/settingsSchemaFns.d.ts +3 -0
  75. package/dist/generated/settingsSchemaFns.js +53 -0
  76. package/dist/generated/toolNameTables.d.ts +1 -1
  77. package/dist/generated/toolNameTables.js +1 -42
  78. package/dist/hitl/approvalDecisionNoteAudit.js +2 -2
  79. package/dist/hitl/approvalEndingCause.d.ts +2 -0
  80. package/dist/hitl/approvalEndingCause.js +26 -21
  81. package/dist/hitl/approvalHolds.d.ts +16 -1
  82. package/dist/hitl/approvalHolds.js +52 -12
  83. package/dist/hitl/askGateWire.js +1 -0
  84. package/dist/hitl/askParkRowRouting.js +1 -1
  85. package/dist/hitl/automaticAnswerVerdict.d.ts +1 -1
  86. package/dist/hitl/automaticAnswerVerdict.js +13 -10
  87. package/dist/hitl/decisionConflict.js +4 -5
  88. package/dist/hitl/decisionRefusal.d.ts +8 -0
  89. package/dist/hitl/decisionRefusal.js +55 -14
  90. package/dist/hitl/frameRouter.d.ts +3 -1
  91. package/dist/hitl/frameRouter.js +58 -39
  92. package/dist/hitl/gateLedger.d.ts +0 -5
  93. package/dist/hitl/gateLedger.js +0 -21
  94. package/dist/hitl/hitlBridge.d.ts +6 -2
  95. package/dist/hitl/hitlBridge.js +46 -11
  96. package/dist/hitl/localAllowRule.d.ts +1 -0
  97. package/dist/hitl/localAllowRule.js +2 -2
  98. package/dist/hitl/noAllowCause.d.ts +2 -1
  99. package/dist/hitl/noAllowCause.js +3 -2
  100. package/dist/hitl/parkResolver.d.ts +1 -0
  101. package/dist/hitl/parkResolver.js +14 -56
  102. package/dist/hitl/parkRowBirthWait.d.ts +1 -0
  103. package/dist/hitl/parkRowBirthWait.js +2 -2
  104. package/dist/hitl/planReviewWire.d.ts +1 -0
  105. package/dist/hitl/planReviewWire.js +98 -20
  106. package/dist/hitl/replayedApproval.d.ts +3 -3
  107. package/dist/hitl/replayedApproval.js +21 -16
  108. package/dist/hitl/sessionPolicyDeliverable.d.ts +0 -1
  109. package/dist/hitl/sessionPolicyDeliverable.js +17 -17
  110. package/dist/hitl/sessionPolicyWire.d.ts +2 -0
  111. package/dist/hitl/sessionPolicyWire.js +15 -3
  112. package/dist/hitl/standingRows.d.ts +19 -0
  113. package/dist/hitl/standingRows.js +148 -0
  114. package/dist/hitl/suspendedReopen.js +2 -1
  115. package/dist/hitl/toolApprovalWire.d.ts +22 -10
  116. package/dist/hitl/toolApprovalWire.js +286 -47
  117. package/dist/hookMatcherVerdict.d.ts +1 -9
  118. package/dist/hookMatcherVerdict.js +21 -61
  119. package/dist/hooksWireCaps.js +2 -2
  120. package/dist/hostCallbackGuard.d.ts +1 -0
  121. package/dist/hostCallbackGuard.js +6 -0
  122. package/dist/index.d.ts +26 -12
  123. package/dist/index.js +20 -10
  124. package/dist/liveModelCatalog.d.ts +13 -0
  125. package/dist/liveModelCatalog.js +56 -0
  126. package/dist/mcpProbeCapability.d.ts +2 -4
  127. package/dist/mcpProbeCapability.js +3 -8
  128. package/dist/mcpWireCaps.d.ts +1 -0
  129. package/dist/mcpWireCaps.js +4 -1
  130. package/dist/memoryComplianceCapability.d.ts +2 -4
  131. package/dist/memoryComplianceCapability.js +3 -8
  132. package/dist/memoryOriginCapability.d.ts +2 -4
  133. package/dist/memoryOriginCapability.js +3 -8
  134. package/dist/memorySpecWire.js +2 -2
  135. package/dist/modelIdentityResolvability.d.ts +2 -2
  136. package/dist/modelIdentityResolvability.js +14 -5
  137. package/dist/noticePresentation.d.ts +68 -0
  138. package/dist/noticePresentation.js +247 -0
  139. package/dist/outputStyleSeatCapability.d.ts +2 -1
  140. package/dist/outputStyleSeatCapability.js +0 -4
  141. package/dist/ownKey.d.ts +2 -0
  142. package/dist/ownKey.js +18 -0
  143. package/dist/peerFrames.js +13 -9
  144. package/dist/peerLaneCapability.d.ts +2 -0
  145. package/dist/peerLaneCapability.js +3 -2
  146. package/dist/permissionModesCapability.d.ts +2 -4
  147. package/dist/permissionModesCapability.js +0 -7
  148. package/dist/permissionRuleIssue.js +0 -3
  149. package/dist/permissionRulesWriteCapability.d.ts +2 -0
  150. package/dist/permissionRulesWriteCapability.js +3 -2
  151. package/dist/permissionsAllowGrantCapability.d.ts +2 -0
  152. package/dist/permissionsAllowGrantCapability.js +4 -2
  153. package/dist/permissionsDeclaration.d.ts +2 -0
  154. package/dist/permissionsDeclaration.js +72 -59
  155. package/dist/printPermissionOutcome.d.ts +12 -0
  156. package/dist/printPermissionOutcome.js +50 -0
  157. package/dist/printToolResultFrame.d.ts +1 -0
  158. package/dist/printToolResultFrame.js +5 -9
  159. package/dist/promptBlockedCopy.d.ts +5 -0
  160. package/dist/promptBlockedCopy.js +35 -0
  161. package/dist/protocolSegment.d.ts +9 -6
  162. package/dist/protocolSegment.js +31 -21
  163. package/dist/providerRejection.d.ts +4 -0
  164. package/dist/providerRejection.js +75 -14
  165. package/dist/replayBody.d.ts +12 -0
  166. package/dist/replayBody.js +25 -0
  167. package/dist/request/taskRequest.d.ts +1 -4
  168. package/dist/request/taskRequest.js +74 -19
  169. package/dist/resumeRefusalCopy.js +9 -25
  170. package/dist/retryStatus.d.ts +9 -1
  171. package/dist/retryStatus.js +9 -2
  172. package/dist/rewindArchiveCapability.d.ts +3 -13
  173. package/dist/rewindArchiveCapability.js +12 -58
  174. package/dist/rewindArchiveReading.d.ts +17 -0
  175. package/dist/rewindArchiveReading.js +55 -0
  176. package/dist/ruleCompileVerdict.d.ts +9 -1
  177. package/dist/ruleCompileVerdict.js +29 -4
  178. package/dist/runTerminal.d.ts +48 -5
  179. package/dist/runTerminal.js +215 -12
  180. package/dist/sandboxWire.js +13 -0
  181. package/dist/sdkWireTransit.d.ts +2 -1
  182. package/dist/sdkWireTransit.js +1 -0
  183. package/dist/sealedKeyCapability.d.ts +2 -4
  184. package/dist/sealedKeyCapability.js +0 -7
  185. package/dist/seam.js +4 -2
  186. package/dist/seatContract.d.ts +76 -11
  187. package/dist/seatContract.js +222 -27
  188. package/dist/sessionPolicyCapability.d.ts +2 -0
  189. package/dist/sessionPolicyCapability.js +3 -2
  190. package/dist/sessionReadRedactionCapability.d.ts +2 -4
  191. package/dist/sessionReadRedactionCapability.js +0 -7
  192. package/dist/sessionSyncFileHistory.d.ts +4 -0
  193. package/dist/sessionSyncFileHistory.js +66 -0
  194. package/dist/sessionSyncFileHistoryCapability.d.ts +2 -1
  195. package/dist/sessionSyncFileHistoryCapability.js +3 -5
  196. package/dist/skillToolListsCapability.d.ts +2 -1
  197. package/dist/skillToolListsCapability.js +3 -5
  198. package/dist/sqlEngineCapability.d.ts +2 -0
  199. package/dist/sqlEngineCapability.js +4 -3
  200. package/dist/steerPriorityCapability.d.ts +2 -1
  201. package/dist/steerPriorityCapability.js +3 -5
  202. package/dist/storePostureCapability.d.ts +2 -4
  203. package/dist/storePostureCapability.js +4 -9
  204. package/dist/subagent/engineSubagentSteer.js +7 -5
  205. package/dist/subagentContentStore.js +4 -15
  206. package/dist/supportFloor.d.ts +1 -0
  207. package/dist/supportFloor.js +13 -9
  208. package/dist/systemReminderTag.d.ts +1 -0
  209. package/dist/systemReminderTag.js +8 -0
  210. package/dist/toolHistoryBadTurn.js +3 -2
  211. package/dist/toolHistoryMismatch.d.ts +0 -3
  212. package/dist/toolHistoryMismatch.js +3 -7
  213. package/dist/toolResult.d.ts +12 -1
  214. package/dist/toolResult.js +55 -1
  215. package/dist/toolResultContent.d.ts +2 -0
  216. package/dist/toolResultContent.js +63 -0
  217. package/dist/usageAnalytics.js +40 -16
  218. package/dist/userInvokedSkillsCapability.d.ts +2 -1
  219. package/dist/userInvokedSkillsCapability.js +3 -5
  220. package/dist/webSearchBackendCapability.d.ts +2 -0
  221. package/dist/webSearchBackendCapability.js +3 -2
  222. package/dist/wireErrorTriage.d.ts +2 -0
  223. package/dist/wireErrorTriage.js +91 -38
  224. package/dist/wireFailureShape.d.ts +20 -3
  225. package/dist/wireFailureShape.js +128 -72
  226. package/dist/workflowsJournalCapability.d.ts +2 -0
  227. package/dist/workflowsJournalCapability.js +3 -2
  228. package/dist/writeProtectionCapability.d.ts +2 -0
  229. package/dist/writeProtectionCapability.js +3 -2
  230. package/docs/INTEGRATION-CLIENTS.md +1784 -20
  231. package/package.json +5 -5
  232. package/dist/engineRefusedToolName.d.ts +0 -1
  233. package/dist/engineRefusedToolName.js +0 -8
@@ -3,6 +3,7 @@ import { toCappedSpec } from './skillsWireCaps.js';
3
3
  import { TIER_ORDER } from './model/tierVocabulary.js';
4
4
  import { SETTINGS_NARROW_AT_MENTION } from './generated/settingsSchemaFns.js';
5
5
  import { modelsPolicyOf } from './model/modelsPolicy.js';
6
+ import { guardedRead, readOwnDataNonEmptyString, readOwnDataValue } from './ownKey.js';
6
7
  import { SETTINGS_BUDGET_NUMBER_KEYS, SETTINGS_BUDGET_PER_MODEL_NUMBER_KEYS, SETTINGS_BUDGET_STRING_KEYS, SETTINGS_TIER_GROUP_NAME_RE, } from './generated/settingsSchemaTables.js';
7
8
  const asRec = (v) => (typeof v === 'object' && v !== null && !Array.isArray(v) ? v : undefined);
8
9
  const asArr = (v) => (Array.isArray(v) ? v : []);
@@ -62,6 +63,10 @@ export function cloudMcpToSpecs(servers, env) {
62
63
  drop(name, 'missing-field');
63
64
  continue;
64
65
  }
66
+ if (asStr(t.principalHeader) && asStr(t.assertionHeader)) {
67
+ drop(name, 'principal-and-assertion-header');
68
+ continue;
69
+ }
65
70
  const headers = {};
66
71
  for (const [headerName, ref] of Object.entries(asRec(t.headerRefs) ?? {})) {
67
72
  const refName = asStr(ref);
@@ -92,6 +97,7 @@ export function cloudMcpToSpecs(servers, env) {
92
97
  ...(allowTools.length > 0 ? { allowTools } : {}),
93
98
  ...(s.elicitation === true ? { elicitation: true } : {}),
94
99
  ...(s.toolFaces !== undefined ? { toolFaces: s.toolFaces } : {}),
100
+ ...(s.toolAxes !== undefined ? { toolAxes: s.toolAxes } : {}),
95
101
  });
96
102
  }
97
103
  return { specs, missingEnv: [...new Set(missingEnv)], droppedServers };
@@ -136,6 +142,9 @@ const TIER_GROUP_NAME = SETTINGS_TIER_GROUP_NAME_RE;
136
142
  const setOwn = (o, k, v) => {
137
143
  Object.defineProperty(o, k, { value: v, enumerable: true, writable: true, configurable: true });
138
144
  };
145
+ function modelsDocPositionOf(m, key) {
146
+ return guardedRead(() => (Object.hasOwn(m, key) ? { value: m[key] } : 'absent')) ?? 'unreadable';
147
+ }
139
148
  function modelsDocOf(models, opts) {
140
149
  const m = asRec(models);
141
150
  if (!m || !Array.isArray(m.models))
@@ -165,9 +174,12 @@ function modelsDocOf(models, opts) {
165
174
  };
166
175
  const refOf = (v) => (typeof v === 'string' && v !== '' ? { target: v } : {});
167
176
  const reasonOf = (v) => typeof v === 'string' && v !== '' ? (rawNames.has(v) ? 'rejected' : 'dangling') : 'unreadable';
177
+ const modelRefVerdict = (v) => names.has(v) ? (accept === undefined ? 'unvouched' : 'accepted') : reasonOf(v);
168
178
  let written = stripped;
179
+ let catalogKept = false;
169
180
  if (acceptedFlags && stripped.length > 0) {
170
181
  const anyAccepted = acceptedFlags.some((f) => f);
182
+ catalogKept = !anyAccepted;
171
183
  stripped.forEach((e, i) => {
172
184
  if (!acceptedFlags[i])
173
185
  issues.push({ key: 'models', target: e.name, reason: 'rejected', outcome: anyAccepted ? 'dropped' : 'kept' });
@@ -178,108 +190,156 @@ function modelsDocOf(models, opts) {
178
190
  const doc = { models: written };
179
191
  const policy = modelsPolicyOf(m);
180
192
  if (policy.unreadable) {
181
- drop({ key: 'default', reason: 'unreadable' });
182
- drop({ key: 'atModelAllowlist', at: 'models', reason: 'unreadable' });
193
+ drop({ key: 'roles', at: 'default', reason: 'unreadable' });
194
+ drop({ key: 'atMention', at: 'models', reason: 'unreadable' });
183
195
  }
184
196
  if (policy.defaultUnreadable)
185
- drop({ key: 'default', reason: 'unreadable' });
197
+ drop({ key: 'roles', at: 'default', reason: 'unreadable' });
186
198
  const dm = policy.defaultModel;
187
- if (accept && dm !== undefined) {
188
- if (names.has(dm.name))
189
- doc.default = dm.name;
199
+ let defaultRole;
200
+ let defaultWinner;
201
+ if (dm !== undefined && dm.from === 'default') {
202
+ const verdict = modelRefVerdict(dm.name);
203
+ if (verdict === 'accepted')
204
+ defaultRole = { model: dm.name };
190
205
  else
191
- drop({ key: 'default', target: dm.name, reason: reasonOf(dm.name) });
206
+ drop({ key: 'roles', at: 'default', target: dm.name, reason: verdict });
207
+ if (verdict === 'accepted' || verdict === 'unvouched')
208
+ defaultWinner = dm.name;
192
209
  }
193
210
  const roles = asRec(m.roles);
194
- if (roles) {
195
- const kept = {};
196
- for (const [role, t] of Object.entries(roles)) {
197
- if (role === 'default' && accept && dm?.from === 'roles.default')
198
- continue;
211
+ const keptRoles = {};
212
+ let sameModelDefaultRole;
213
+ for (const [role, t] of Object.entries(roles ?? {})) {
214
+ if (role === 'default' && defaultWinner !== undefined) {
199
215
  const tr = asRec(t);
200
- const model = tr && Object.hasOwn(tr, 'model') ? tr.model : undefined;
201
- if (typeof model === 'string' && !names.has(model)) {
202
- drop({ key: 'roles', at: role, ...refOf(model), reason: reasonOf(model) });
216
+ let model;
217
+ let snapshot;
218
+ try {
219
+ model = tr && Object.hasOwn(tr, 'model') ? tr.model : undefined;
220
+ if (tr !== undefined && typeof model === 'string' && model === defaultWinner) {
221
+ snapshot = {};
222
+ for (const [k, v] of Object.entries(tr))
223
+ setOwn(snapshot, k, v);
224
+ setOwn(snapshot, 'model', model);
225
+ }
226
+ }
227
+ catch {
228
+ model = undefined;
229
+ snapshot = undefined;
230
+ }
231
+ if (snapshot !== undefined)
232
+ sameModelDefaultRole = snapshot;
233
+ else
234
+ drop({ key: 'roles', at: 'default', ...refOf(model), reason: 'shadowed' });
235
+ continue;
236
+ }
237
+ const tr = asRec(t);
238
+ const model = tr && Object.hasOwn(tr, 'model') ? tr.model : undefined;
239
+ if (typeof model === 'string') {
240
+ const verdict = modelRefVerdict(model);
241
+ if (verdict !== 'accepted') {
242
+ drop({ key: 'roles', at: role, ...refOf(model), reason: verdict });
203
243
  continue;
204
244
  }
205
- setOwn(kept, role, t);
206
245
  }
207
- doc.roles = kept;
246
+ setOwn(keptRoles, role, t);
208
247
  }
248
+ const rolesOut = {};
249
+ if (defaultRole !== undefined)
250
+ setOwn(rolesOut, 'default', sameModelDefaultRole ?? defaultRole);
251
+ else if (Object.hasOwn(keptRoles, 'default'))
252
+ setOwn(rolesOut, 'default', keptRoles.default);
253
+ for (const [k, v] of Object.entries(keptRoles))
254
+ if (k !== 'default')
255
+ setOwn(rolesOut, k, v);
256
+ if (Object.keys(rolesOut).length > 0)
257
+ doc.roles = rolesOut;
209
258
  const am = policy.atMention;
210
259
  if (am?.kind === 'unreadable') {
211
- drop({ key: 'atModelAllowlist', at: am.from, reason: 'unreadable' });
260
+ drop({ key: 'atMention', at: am.from, reason: 'unreadable' });
212
261
  }
213
262
  else if (am !== undefined) {
214
263
  for (let i = 0; i < am.unreadableEntries; i += 1)
215
- drop({ key: 'atModelAllowlist', reason: 'unreadable' });
264
+ drop({ key: 'atMention', reason: 'unreadable' });
216
265
  const v = am.value;
217
- if (v.mode === 'none') {
218
- drop({ key: 'atModelAllowlist', at: 'atMention', reason: 'unsupported' });
219
- }
220
- else if (v.mode === 'only') {
266
+ if (v.mode === 'only') {
267
+ for (const n of v.models)
268
+ if (!names.has(n))
269
+ drop({ key: 'atMention', target: n, reason: reasonOf(n) });
221
270
  const narrowed = SETTINGS_NARROW_AT_MENTION(v, names);
222
271
  if (narrowed.mode === 'only') {
223
- for (const n of v.models)
224
- if (!names.has(n))
225
- drop({ key: 'atModelAllowlist', target: n, reason: reasonOf(n) });
226
- doc.atModelAllowlist = [...narrowed.models];
272
+ doc.atMention = { mode: 'only', models: [...narrowed.models] };
227
273
  }
228
274
  else {
229
- doc.atModelAllowlist = [...v.models];
230
- for (const n of v.models)
231
- issues.push({ key: 'atModelAllowlist', target: n, reason: reasonOf(n), outcome: 'kept' });
275
+ doc.atMention = { mode: narrowed.mode };
276
+ if (narrowed.mode === 'none' && !catalogKept)
277
+ drop({ key: 'atMention', at: 'atMention', reason: 'dangling' });
232
278
  }
233
279
  }
280
+ else if (v.mode === 'none') {
281
+ doc.atMention = { mode: 'none' };
282
+ }
234
283
  }
235
284
  let groupNames;
236
- const rawGroupNames = new Set((Array.isArray(m.tierGroups) ? m.tierGroups : []).map((g) => asStr(asRec(g)?.name)).filter((n) => !!n));
237
- if (accept && Object.hasOwn(m, 'tierGroups')) {
238
- if (!Array.isArray(m.tierGroups)) {
239
- drop({ key: 'tierGroups', reason: 'unreadable' });
285
+ if (accept) {
286
+ const tg = modelsDocPositionOf(m, 'tierGroups');
287
+ const atg = modelsDocPositionOf(m, 'activeTierGroup');
288
+ const tgValue = typeof tg === 'object' ? tg.value : undefined;
289
+ const rawGroupNames = new Set((Array.isArray(tgValue) ? tgValue : []).map((g) => asStr(asRec(g)?.name)).filter((n) => !!n));
290
+ if (tg === 'unreadable') {
291
+ drop({ key: 'tierGroups', at: 'models', reason: 'unreadable' });
240
292
  }
241
- else {
242
- const groups = [];
243
- groupNames = new Set();
244
- for (const raw of m.tierGroups) {
245
- const g = asRec(raw);
246
- const name = g ? asStr(g.name) : undefined;
247
- if (!g || !name || !TIER_GROUP_NAME.test(name)) {
248
- drop({ key: 'tierGroups', ...(name ? { at: name } : {}), reason: 'unreadable' });
249
- continue;
250
- }
251
- if (groupNames.has(name)) {
252
- drop({ key: 'tierGroups', at: name, reason: 'duplicate' });
253
- continue;
254
- }
255
- groupNames.add(name);
256
- const tiers = {};
257
- if (Object.hasOwn(g, 'tiers')) {
258
- const t = asRec(g.tiers);
259
- if (!t)
260
- drop({ key: 'tierGroups', at: `${name}.tiers`, reason: 'unreadable' });
261
- else {
262
- for (const [tier, model] of Object.entries(t)) {
263
- if (!TIER_WORDS.includes(tier))
264
- drop({ key: 'tierGroups', at: `${name}.${tier}`, ...refOf(model), reason: 'unsupported' });
265
- else if (typeof model === 'string' && names.has(model))
266
- setOwn(tiers, tier, model);
267
- else
268
- drop({ key: 'tierGroups', at: `${name}.${tier}`, ...refOf(model), reason: reasonOf(model) });
293
+ else if (tg !== 'absent') {
294
+ if (!Array.isArray(tgValue)) {
295
+ drop({ key: 'tierGroups', reason: 'unreadable' });
296
+ }
297
+ else {
298
+ const groups = [];
299
+ groupNames = new Set();
300
+ for (const raw of tgValue) {
301
+ const g = asRec(raw);
302
+ const name = g ? asStr(g.name) : undefined;
303
+ if (!g || !name || !TIER_GROUP_NAME.test(name)) {
304
+ drop({ key: 'tierGroups', ...(name ? { at: name } : {}), reason: 'unreadable' });
305
+ continue;
306
+ }
307
+ if (groupNames.has(name)) {
308
+ drop({ key: 'tierGroups', at: name, reason: 'duplicate' });
309
+ continue;
310
+ }
311
+ groupNames.add(name);
312
+ const tiers = {};
313
+ if (Object.hasOwn(g, 'tiers')) {
314
+ const t = asRec(g.tiers);
315
+ if (!t)
316
+ drop({ key: 'tierGroups', at: `${name}.tiers`, reason: 'unreadable' });
317
+ else {
318
+ for (const [tier, model] of Object.entries(t)) {
319
+ if (!TIER_WORDS.includes(tier))
320
+ drop({ key: 'tierGroups', at: `${name}.${tier}`, ...refOf(model), reason: 'unsupported' });
321
+ else if (typeof model === 'string' && names.has(model))
322
+ setOwn(tiers, tier, model);
323
+ else
324
+ drop({ key: 'tierGroups', at: `${name}.${tier}`, ...refOf(model), reason: reasonOf(model) });
325
+ }
269
326
  }
270
327
  }
328
+ groups.push({ name, tiers, ...(typeof g.notes === 'string' ? { notes: g.notes } : {}) });
271
329
  }
272
- groups.push({ name, tiers, ...(typeof g.notes === 'string' ? { notes: g.notes } : {}) });
330
+ doc.tierGroups = groups;
273
331
  }
274
- doc.tierGroups = groups;
275
332
  }
276
- }
277
- if (accept && Object.hasOwn(m, 'activeTierGroup')) {
278
- const a = m.activeTierGroup;
279
- if (typeof a === 'string' && groupNames?.has(a))
280
- doc.activeTierGroup = a;
281
- else
282
- drop({ key: 'activeTierGroup', ...refOf(a), reason: typeof a === 'string' && a !== '' ? (rawGroupNames.has(a) ? 'rejected' : 'dangling') : 'unreadable' });
333
+ if (atg === 'unreadable') {
334
+ drop({ key: 'activeTierGroup', at: 'models', reason: 'unreadable' });
335
+ }
336
+ else if (atg !== 'absent') {
337
+ const a = atg.value;
338
+ if (typeof a === 'string' && groupNames?.has(a))
339
+ doc.activeTierGroup = a;
340
+ else
341
+ drop({ key: 'activeTierGroup', ...refOf(a), reason: typeof a === 'string' && a !== '' ? (rawGroupNames.has(a) ? 'rejected' : 'dangling') : 'unreadable' });
342
+ }
283
343
  }
284
344
  return { doc, issues };
285
345
  }
@@ -536,13 +596,17 @@ function droppedRefNotice(d) {
536
596
  switch (d.key) {
537
597
  case 'models':
538
598
  return { level: 'warn', text: `cloud config: ${t === undefined ? 'a model entry' : `model "${t}"`} ${ENTRY_REJECTED} — it was left out of the local catalog` };
539
- case 'default':
540
- return { level: 'warn', text: `cloud config: ${t === undefined ? 'the default model setting could not be read' : `the default model "${t}" ${notServed}`}, so it was left out — the local engine picks its default from roles and tiers instead` };
541
599
  case 'roles':
600
+ if (d.at === 'default' && d.reason === 'unreadable')
601
+ return { level: 'warn', text: 'cloud config: the default model setting could not be read, so it was left out — the local engine picks its default from roles and tiers instead' };
602
+ if (d.reason === 'unvouched')
603
+ return { level: 'warn', text: `cloud config: role "${d.at ?? ''}" points at model "${t ?? ''}", but this client was given no way to check that the local engine can use that model — the role was left out and falls back along the engine's role chain` };
604
+ if (d.reason === 'shadowed')
605
+ return { level: 'warn', text: `cloud config: the "${d.at ?? ''}" role${t === undefined ? '' : ` ("${t}")`} is overridden by the default model setting served to you — only one default model can be set, so that role entry was left out` };
542
606
  return { level: 'warn', text: `cloud config: role "${d.at ?? ''}" ${t === undefined ? 'names no readable model' : `points at model "${t}", which ${notServed}`} — the role was left out and falls back along the engine's role chain` };
543
- case 'atModelAllowlist':
544
- if (d.reason === 'unsupported')
545
- return { level: 'warn', text: 'cloud config: the @-mention setting served to you allows no model to be @-mentioned, which the local engine\'s settings format cannot express — it was left out, so @-mentions are not restricted on the local engine' };
607
+ case 'atMention':
608
+ if (d.at === 'atMention' && d.reason === 'dangling')
609
+ return { level: 'warn', text: 'cloud config: none of the models in the @-mention allowlist served to you can be used on the local engine, so @-mentions are turned off there' };
546
610
  if (d.at !== undefined)
547
611
  return { level: 'warn', text: 'cloud config: the @-mention setting served to you could not be read by this client — it was left out, so @-mentions are not restricted on the local engine' };
548
612
  return { level: 'warn', text: `cloud config: ${t === undefined ? 'an unreadable @-mention allowlist entry' : `@-mention allowlist entry "${t}" ${notServed} and`} was left out` };
@@ -553,11 +617,111 @@ function droppedRefNotice(d) {
553
617
  return { level: 'warn', text: `cloud config: tier "${d.at ?? ''}" is bound to model "${t ?? ''}", which ${notServed} — that tier was left unbound` };
554
618
  if (d.reason === 'unsupported')
555
619
  return { level: 'warn', text: `cloud config: tier "${d.at ?? ''}" is not a tier the local engine reads (${TIER_WORDS.join(', ')}) — that binding was left out` };
620
+ if (d.at === 'models')
621
+ return { level: 'warn', text: 'cloud config: the tier group settings served to you could not be read by this client — they were left out, so no tier groups are set on the local engine' };
556
622
  return { level: 'warn', text: `cloud config: a tier group setting${d.at === undefined ? '' : ` ("${d.at}")`} could not be read and was left out` };
557
623
  case 'activeTierGroup':
558
624
  return { level: 'warn', text: `cloud config: ${t === undefined ? 'the active tier group setting could not be read' : `the active tier group "${t}" is not among the readable tier groups`} — no tier table is active on the local engine` };
559
625
  }
560
626
  }
627
+ const MCP_DROP_SENTENCES = {
628
+ 'missing-field': {
629
+ level: 'warn',
630
+ text: (who) => `cloud config: MCP server entries left out because a required setting is missing or unreadable (a name, a transport, or its command or address): ${who}`,
631
+ },
632
+ 'unknown-transport': {
633
+ level: 'warn',
634
+ text: (who) => `cloud config: MCP server entries left out because their transport is not one this client supports: ${who}`,
635
+ },
636
+ 'principal-and-assertion-header': {
637
+ level: 'warn',
638
+ text: (who) => `cloud config: MCP server entries left out because they set both principalHeader and assertionHeader, which name the same identity header — keep only one of the two: ${who}`,
639
+ },
640
+ capped: {
641
+ level: 'warn',
642
+ text: (who) => `cloud config: MCP server entries left out because more than ${MCP_CAPS.items} were served to you and this client passes at most ${MCP_CAPS.items} to the engine: ${who}`,
643
+ },
644
+ disabled: {
645
+ level: 'info',
646
+ text: (who) => `cloud config: MCP server entries switched off in the served settings, so they are not started here: ${who}`,
647
+ },
648
+ };
649
+ const MCP_DROP_UNRECOGNISED = (who) => `cloud config: MCP server entries left out for a reason this client does not recognise: ${who}`;
650
+ const MCP_DROP_LIST_UNREADABLE = 'cloud config: the list of MCP server entries left out could not be read by this client — servers missing from the local tool list may have been left out without notice';
651
+ const MCP_DROP_NAMES_SHOWN = 5;
652
+ function mcpDropWho(names, unnamed) {
653
+ const distinct = [...new Set(names)];
654
+ const parts = distinct.slice(0, MCP_DROP_NAMES_SHOWN).map((n) => `"${n}"`);
655
+ if (distinct.length > MCP_DROP_NAMES_SHOWN)
656
+ parts.push(`+${distinct.length - MCP_DROP_NAMES_SHOWN} more`);
657
+ if (unnamed > 0)
658
+ parts.push(`${unnamed} without a readable name`);
659
+ return parts.join(', ');
660
+ }
661
+ function guarded(read) {
662
+ try {
663
+ return read();
664
+ }
665
+ catch {
666
+ return undefined;
667
+ }
668
+ }
669
+ function droppedMcpNotices(carrier) {
670
+ const listUnreadable = [{ level: 'warn', text: MCP_DROP_LIST_UNREADABLE }];
671
+ let desc;
672
+ try {
673
+ desc = Object.getOwnPropertyDescriptor(carrier, 'droppedMcpServers');
674
+ }
675
+ catch {
676
+ return listUnreadable;
677
+ }
678
+ if (desc === undefined)
679
+ return [];
680
+ if (!Object.hasOwn(desc, 'value'))
681
+ return listUnreadable;
682
+ const rows = desc.value;
683
+ let count = 0;
684
+ try {
685
+ if (!Array.isArray(rows))
686
+ return listUnreadable;
687
+ count = rows.length;
688
+ }
689
+ catch {
690
+ return listUnreadable;
691
+ }
692
+ const known = new Map();
693
+ const unrecognised = { names: [], unnamed: 0 };
694
+ for (let i = 0; i < count; i++) {
695
+ const row = guarded(() => readOwnDataValue(rows, String(i)));
696
+ const rowObj = typeof row === 'object' && row !== null ? row : undefined;
697
+ const reason = guarded(() => {
698
+ const raw = readOwnDataValue(rowObj, 'reason');
699
+ return typeof raw === 'string' && Object.hasOwn(MCP_DROP_SENTENCES, raw) ? raw : undefined;
700
+ });
701
+ const name = guarded(() => readOwnDataNonEmptyString(rowObj, 'name'));
702
+ let bucket = unrecognised;
703
+ if (reason !== undefined) {
704
+ let b = known.get(reason);
705
+ if (b === undefined)
706
+ known.set(reason, (b = { names: [], unnamed: 0 }));
707
+ bucket = b;
708
+ }
709
+ if (name === undefined)
710
+ bucket.unnamed++;
711
+ else
712
+ bucket.names.push(name);
713
+ }
714
+ const out = [];
715
+ for (const reason of Object.keys(MCP_DROP_SENTENCES)) {
716
+ const b = known.get(reason);
717
+ if (b !== undefined)
718
+ out.push({ level: MCP_DROP_SENTENCES[reason].level, text: MCP_DROP_SENTENCES[reason].text(mcpDropWho(b.names, b.unnamed)) });
719
+ }
720
+ if (unrecognised.names.length + unrecognised.unnamed > 0) {
721
+ out.push({ level: 'warn', text: MCP_DROP_UNRECOGNISED(mcpDropWho(unrecognised.names, unrecognised.unnamed)) });
722
+ }
723
+ return out;
724
+ }
561
725
  function budgetNotice(r) {
562
726
  switch (r.kind) {
563
727
  case 'configured': {
@@ -625,15 +789,7 @@ export function cloudEffectiveNotices(p) {
625
789
  'the catalog is kept as sent, and a locally started engine rejects the cloud model settings: at startup it uses its own environment catalog instead, and on a later refresh it keeps the previous configuration',
626
790
  });
627
791
  }
628
- const keptAllowlist = p.modelRefIssues.filter((d) => d.outcome === 'kept' && d.key === 'atModelAllowlist');
629
- if (keptAllowlist.length > 0) {
630
- const names = keptAllowlist.flatMap((d) => (d.target === undefined ? [] : [`"${d.target}"`]));
631
- out.push({
632
- level: 'warn',
633
- text: `cloud config: no @-mention allowlist entry${names.length > 0 ? ` (${names.join(', ')})` : ''} names a usable model in the catalog served to you — ` +
634
- 'the list is kept as sent, because an empty list would let every model be mentioned; with it, a locally started engine rejects the cloud model settings — at startup it uses its own environment catalog instead, and on a later refresh it keeps the previous configuration',
635
- });
636
- }
792
+ out.push(...droppedMcpNotices(p));
637
793
  out.push(budgetNotice(p.budget));
638
794
  out.push(runtimeCapsNotice(p.runtimeCaps));
639
795
  return out;
@@ -91,17 +91,21 @@ export interface PerModelAuthView {
91
91
  apiKey?: string;
92
92
  headers?: Record<string, string>;
93
93
  }
94
+ export type RuleGrammarView = 'command' | 'domain' | 'name' | 'path';
95
+ export interface RuleToolNameVerdictView {
96
+ readonly kind: 'current' | 'retired' | 'unprefixed_namespace' | 'unmintable_segment' | (string & {});
97
+ readonly guidance?: string;
98
+ readonly currentName?: string;
99
+ }
94
100
  export interface CoreValuePorts {
95
101
  resolveAutonomousLoopPrompt: (prompt: string, opts?: AutonomousLoopPromptOptionsView) => string | null;
96
102
  loosenReasons: (prior: SessionPermissionRules, next: SessionPermissionRules) => string[];
97
103
  combinePolicies: (...policies: CoreToolPolicyHandle[]) => CoreToolPolicyHandle;
98
104
  createAllowDenyPolicy: (opts: AllowDenyPolicyOptionsView) => CoreToolPolicyHandle;
99
105
  MCP_NAMESPACE: CoreProtocolNamespaceView;
100
- RETIRED_TOOL_NAMES: ReadonlyMap<string, string>;
101
106
  protocolOf: (name: string) => CoreProtocolNamespaceView | undefined;
102
- ruleToolGrammarOf: (toolName: string) => 'command' | 'path' | undefined;
107
+ ruleToolGrammarOf: (toolName: string) => RuleGrammarView | undefined;
103
108
  validatePermissionRules: (rules: PermissionRuleView[], opts?: ValidatePermissionRulesOptionsView) => PermissionRuleIssueView[];
104
- DISCUSSION_WORKFLOW_NAME: string;
105
109
  mintNamespacePrefix(ns: CoreProtocolNamespaceView, peer: string): string;
106
110
  compileAllowList(entries: readonly string[], behavior: 'allow' | 'deny', roster?: ReadonlySet<string>): AllowListReadingView;
107
111
  routeKeyOf(url: string | undefined): string;
@@ -113,8 +117,9 @@ export interface CoreValuePorts {
113
117
  routePairingStatus(brain: {
114
118
  adjudicateRoute?(model: RouteModelView, perModelAuth?: PerModelAuthView): RouteAdjudicationView | undefined;
115
119
  }, model: RouteModelView, perModelAuth?: PerModelAuthView): RoutePairingStatusView;
120
+ judgeRuleToolName(name: string, roster: ReadonlySet<string>): RuleToolNameVerdictView;
116
121
  }
117
- export declare const CORE_VALUE_PORT_NAMES: readonly ["resolveAutonomousLoopPrompt", "loosenReasons", "combinePolicies", "createAllowDenyPolicy", "MCP_NAMESPACE", "RETIRED_TOOL_NAMES", "protocolOf", "ruleToolGrammarOf", "validatePermissionRules", "DISCUSSION_WORKFLOW_NAME", "mintNamespacePrefix", "compileAllowList", "routeKeyOf", "joinRequestUrl", "resolveRouteCredential", "adjudicateModelRoute", "routePairingStatus"];
122
+ export declare const CORE_VALUE_PORT_NAMES: readonly ["resolveAutonomousLoopPrompt", "loosenReasons", "combinePolicies", "createAllowDenyPolicy", "MCP_NAMESPACE", "protocolOf", "ruleToolGrammarOf", "validatePermissionRules", "mintNamespacePrefix", "compileAllowList", "routeKeyOf", "joinRequestUrl", "resolveRouteCredential", "adjudicateModelRoute", "routePairingStatus", "judgeRuleToolName"];
118
123
  export type CoreValuePortName = (typeof CORE_VALUE_PORT_NAMES)[number];
119
124
  export declare function installCoreValuePorts(ports: Partial<CoreValuePorts> | null): () => void;
120
125
  export declare function hasCoreValuePorts(): boolean;
@@ -125,13 +130,12 @@ export declare function loosenReasons(prior: SessionPermissionRules, next: Sessi
125
130
  export declare function combinePolicies(...policies: CoreToolPolicyHandle[]): CoreToolPolicyHandle | undefined;
126
131
  export declare function createAllowDenyPolicy(opts: AllowDenyPolicyOptionsView): CoreToolPolicyHandle | undefined;
127
132
  export declare function mcpNamespace(): CoreProtocolNamespaceView | undefined;
128
- export declare function retiredToolNames(): ReadonlyMap<string, string> | undefined;
129
133
  export declare function protocolOf(name: string): CoreProtocolNamespaceView | undefined;
130
- export declare function ruleToolGrammarOf(toolName: string): 'command' | 'path' | undefined;
134
+ export declare function ruleToolGrammarOf(toolName: string): RuleGrammarView | undefined;
131
135
  export declare function validatePermissionRules(rules: PermissionRuleView[], opts?: ValidatePermissionRulesOptionsView): PermissionRuleIssueView[] | undefined;
132
- export declare function discussionWorkflowName(): string | undefined;
133
136
  export declare function mintNamespacePrefix(ns: CoreProtocolNamespaceView, peer: string): string | undefined;
134
137
  export declare function compileAllowList(entries: readonly string[], behavior: 'allow' | 'deny', roster?: ReadonlySet<string>): AllowListReadingView | undefined;
138
+ export declare function ruleToolNameJudgement(name: string, roster: ReadonlySet<string>): RuleToolNameVerdictView | undefined;
135
139
  export declare function routeKeyOf(url: string | undefined): string | undefined;
136
140
  export declare function joinRequestUrl(root: string | undefined, suffix: string): string | undefined;
137
141
  export declare function resolveRouteCredential(input: RouteCredentialInputView): RouteCredentialView | undefined;
@@ -4,11 +4,9 @@ export const CORE_VALUE_PORT_NAMES = Object.freeze([
4
4
  'combinePolicies',
5
5
  'createAllowDenyPolicy',
6
6
  'MCP_NAMESPACE',
7
- 'RETIRED_TOOL_NAMES',
8
7
  'protocolOf',
9
8
  'ruleToolGrammarOf',
10
9
  'validatePermissionRules',
11
- 'DISCUSSION_WORKFLOW_NAME',
12
10
  'mintNamespacePrefix',
13
11
  'compileAllowList',
14
12
  'routeKeyOf',
@@ -16,6 +14,7 @@ export const CORE_VALUE_PORT_NAMES = Object.freeze([
16
14
  'resolveRouteCredential',
17
15
  'adjudicateModelRoute',
18
16
  'routePairingStatus',
17
+ 'judgeRuleToolName',
19
18
  ]);
20
19
  let installed = null;
21
20
  export function installCoreValuePorts(ports) {
@@ -56,9 +55,6 @@ export function createAllowDenyPolicy(opts) {
56
55
  export function mcpNamespace() {
57
56
  return port('MCP_NAMESPACE');
58
57
  }
59
- export function retiredToolNames() {
60
- return port('RETIRED_TOOL_NAMES');
61
- }
62
58
  export function protocolOf(name) {
63
59
  const fn = port('protocolOf');
64
60
  return fn === undefined ? undefined : fn(name);
@@ -71,9 +67,6 @@ export function validatePermissionRules(rules, opts) {
71
67
  const fn = port('validatePermissionRules');
72
68
  return fn === undefined ? undefined : fn(rules, opts);
73
69
  }
74
- export function discussionWorkflowName() {
75
- return port('DISCUSSION_WORKFLOW_NAME');
76
- }
77
70
  export function mintNamespacePrefix(ns, peer) {
78
71
  const fn = port('mintNamespacePrefix');
79
72
  return fn === undefined ? undefined : fn(ns, peer);
@@ -82,6 +75,10 @@ export function compileAllowList(entries, behavior, roster) {
82
75
  const fn = port('compileAllowList');
83
76
  return fn === undefined ? undefined : fn(entries, behavior, roster);
84
77
  }
78
+ export function ruleToolNameJudgement(name, roster) {
79
+ const fn = port('judgeRuleToolName');
80
+ return fn === undefined ? undefined : fn(name, roster);
81
+ }
85
82
  export function routeKeyOf(url) {
86
83
  const fn = port('routeKeyOf');
87
84
  return fn === undefined ? undefined : fn(url);
@@ -0,0 +1,28 @@
1
+ export declare const CREDENTIAL_ATTACH_WORDS: readonly ["local-sidecar", "service-token", "registry", "none"];
2
+ export type CredentialAttachWord = (typeof CREDENTIAL_ATTACH_WORDS)[number];
3
+ declare const TARGET_KIND_WORDS: readonly ["local-managed", "loopback-pin", "remote", "unresolved"];
4
+ declare const TARGET_PROVENANCE_WORDS: readonly ["managed", "connect-pin", "manual", "none"];
5
+ declare const CONSENT_WORDS: readonly ["granted", "absent", "n/a"];
6
+ declare const SIDECAR_OWNERSHIP_WORDS: readonly ["verified", "mismatch"];
7
+ export type CredentialTargetKind = (typeof TARGET_KIND_WORDS)[number];
8
+ export type CredentialTargetProvenance = (typeof TARGET_PROVENANCE_WORDS)[number];
9
+ export type CredentialConsent = (typeof CONSENT_WORDS)[number];
10
+ export type CredentialSidecarOwnership = (typeof SIDECAR_OWNERSHIP_WORDS)[number];
11
+ export interface CredentialFacts {
12
+ readonly kind: CredentialTargetKind;
13
+ readonly provenance?: CredentialTargetProvenance;
14
+ readonly sidecarOwnership?: CredentialSidecarOwnership;
15
+ readonly serviceTokenAtFirstWrite?: boolean;
16
+ readonly loggedIn?: boolean;
17
+ readonly consent?: CredentialConsent;
18
+ }
19
+ export type CredentialFactName = 'facts' | 'kind' | 'sidecarOwnership' | 'serviceTokenAtFirstWrite' | 'loggedIn' | 'provenance' | 'consent';
20
+ export type CredentialProvenance = {
21
+ readonly kind: 'word';
22
+ readonly word: CredentialAttachWord;
23
+ } | {
24
+ readonly kind: 'unreadable';
25
+ readonly reason: CredentialFactName;
26
+ };
27
+ export declare function credentialProvenance(facts: CredentialFacts): CredentialProvenance;
28
+ export {};
@@ -0,0 +1,76 @@
1
+ export const CREDENTIAL_ATTACH_WORDS = Object.freeze(['local-sidecar', 'service-token', 'registry', 'none']);
2
+ const TARGET_KIND_WORDS = ['local-managed', 'loopback-pin', 'remote', 'unresolved'];
3
+ const TARGET_PROVENANCE_WORDS = ['managed', 'connect-pin', 'manual', 'none'];
4
+ const CONSENT_WORDS = ['granted', 'absent', 'n/a'];
5
+ const SIDECAR_OWNERSHIP_WORDS = ['verified', 'mismatch'];
6
+ function said(word) {
7
+ const r = { kind: 'word', word };
8
+ return Object.freeze(r);
9
+ }
10
+ function unreadable(reason) {
11
+ const r = { kind: 'unreadable', reason };
12
+ return Object.freeze(r);
13
+ }
14
+ function ownData(holder, key) {
15
+ try {
16
+ if (typeof holder !== 'object' || holder === null)
17
+ return undefined;
18
+ const d = Object.getOwnPropertyDescriptor(holder, key);
19
+ return d !== undefined && Object.hasOwn(d, 'value') ? d.value : undefined;
20
+ }
21
+ catch {
22
+ return undefined;
23
+ }
24
+ }
25
+ function ownBool(holder, key) {
26
+ const v = ownData(holder, key);
27
+ return typeof v === 'boolean' ? v : undefined;
28
+ }
29
+ function ownWord(holder, key, closed) {
30
+ const v = ownData(holder, key);
31
+ return typeof v === 'string' ? closed.find((w) => w === v) : undefined;
32
+ }
33
+ function decide(facts) {
34
+ if (typeof facts !== 'object' || facts === null || Array.isArray(facts))
35
+ return unreadable('facts');
36
+ const kind = ownWord(facts, 'kind', TARGET_KIND_WORDS);
37
+ if (kind === undefined)
38
+ return unreadable('kind');
39
+ if (kind === 'unresolved')
40
+ return said('none');
41
+ if (kind === 'local-managed') {
42
+ const owned = ownWord(facts, 'sidecarOwnership', SIDECAR_OWNERSHIP_WORDS);
43
+ if (owned === undefined)
44
+ return unreadable('sidecarOwnership');
45
+ return said(owned === 'verified' ? 'local-sidecar' : 'none');
46
+ }
47
+ const tokenAtFirstWrite = ownBool(facts, 'serviceTokenAtFirstWrite');
48
+ if (tokenAtFirstWrite === undefined)
49
+ return unreadable('serviceTokenAtFirstWrite');
50
+ if (tokenAtFirstWrite)
51
+ return said('service-token');
52
+ const loggedIn = ownBool(facts, 'loggedIn');
53
+ if (loggedIn === undefined)
54
+ return unreadable('loggedIn');
55
+ if (!loggedIn)
56
+ return said('none');
57
+ const provenance = ownWord(facts, 'provenance', TARGET_PROVENANCE_WORDS);
58
+ if (provenance === undefined)
59
+ return unreadable('provenance');
60
+ if (provenance !== 'connect-pin')
61
+ return said('none');
62
+ if (kind === 'remote')
63
+ return said('registry');
64
+ const consent = ownWord(facts, 'consent', CONSENT_WORDS);
65
+ if (consent === undefined || consent === 'n/a')
66
+ return unreadable('consent');
67
+ return said(consent === 'granted' ? 'registry' : 'none');
68
+ }
69
+ export function credentialProvenance(facts) {
70
+ try {
71
+ return decide(facts);
72
+ }
73
+ catch {
74
+ return unreadable('facts');
75
+ }
76
+ }