primcli 3.0.0-preview.7 → 3.0.0-preview.9

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.
@@ -1,12 +1,12 @@
1
- import { $ as readEmailInteraction, A as explainPendingMailNotFound, At as cliInvocation, Bt as formatAlreadySentNotice, C as promptLine$2, Ct as sendOutcomeExitCode, D as formatBrowserLoginInstructions, Dt as successfulSendOutcome, E as canOfferCopyHotkey, Et as serializeErrorPayload, F as latestOwnOutbound, Ft as loadSkillLine, G as clearConsumedPendingMail, Gt as classificationFactLines, H as wakeRecipientField, Ht as emptyEmailListHint, I as readThreadContext, It as currentMailSessionKey, J as readPendingMail, Jt as noFactsMessage, K as clearReadPendingMail, Kt as classificationOf, L as sentInThread, Lt as detectAgentRuntime, M as describeWake, N as displayAddress, Nt as generatedAgentName, O as listenForCopyHotkey, Ot as thrownErrorExitCode, P as formatMailWakeLine, Pt as identitySuggestions, Q as interactionNextActions, Qt as ConnectedAgentKeyRequiredError, R as serverMuted, Rt as detectRuntimeSession, S as approveThenRerunHint, St as sendIdentityFields, T as withApproval, Tt as sentHistoryWindowStart, U as wakeRelationship, Ut as mailReach, V as wakeReadCommand, Vt as withFlagSuggestion, W as claimPendingMail, Wt as readCallerMailFor, X as removePendingMail, Xt as ATTACHMENT_WAIT_MAX_MS, Y as recordPendingMail, Yt as ATTACHMENT_WAIT_FLAG_DESCRIPTION, Z as interactionHumanLine, Zt as withAttachmentWait, _ as takesStepUp, _n as setKnownCommands, _t as formatPriorRepliesWarning, a as createOperationCommand, an as selectedOrgFromEnv, at as SEND_OUTCOME_HELP, b as STEP_UP_HEADER$1, bt as priorRepliesThatWentOut, c as formatErrorPayload, cn as credentialsFromUserLogin, ct as buildFollowUpCommand, d as isGrantRefusal, dn as revokeUserLogin, dt as classifySendError, en as createAuthenticatedCliApiClient, et as replyExpectation, f as parameterFlagName, fn as roleKeyLabel, ft as deriveSendIdempotencyKey, g as surfaceUnauthorizedHint, gn as sanitizeForTerminal, gt as formatPriorRepliesCheckSkipped, h as runWithTiming, hn as routeWarningLines, ht as formatDeletedEarlierSendNotice, i as TIME_FLAG_DESCRIPTION, in as resolveCliApiRequestConfig, it as MAX_SEND_WAIT_TIMEOUT_MS, j as bareAddress, jt as connectNextSteps, k as openBrowser, l as hasControlCharacter$1, ln as fetchCliOrgs, lt as buildThrownSendFailureEnvelope, m as refusalDetails, mn as switchCurrentOrg, mt as followUpCommandPrefix, n as API_ERROR_CODES, nn as refreshStoredCliCredentials, o as extractErrorCode, on as CliOrgRefusedError, ot as apiResultHttpStatus, p as readTextFileFlag, pn as roleKeyText, pt as excludeSignalReplies, q as pendingMailPath, qt as isEmailId, r as OPERATION_PATH_FLAGS, rn as refuseOrgSelectionForProfile, rt as IDEMPOTENCY_KEY_FLAG_DESCRIPTION, s as extractErrorPayload, sn as RESTRICTED_ROLE_NOTE, st as assertValidIdempotencyKey, t as API_BASE_URL_FLAG_DESCRIPTION, tn as createCliApiClient, tt as replyInteractionWarning, u as headerFlagName, un as isBuiltInRoleKey, ut as checkPriorReplies, v as writeErrorWithHints, vn as operationManifest, vt as formatSendFailureSummary, w as terminalApprovalIo, wt as sendWaitClampNotice, x as STEP_UP_TOKEN_FLAG_DESCRIPTION, xt as reportSendCommandResult, y as BROWSER_FLAG_DESCRIPTION, yt as formatSendRecordFailureSummary, z as serverRelationship, zt as runtimeSessionVariableList } from "../api-command-BXYpylbG.js";
2
- import { $ as parseFromHeader, $n as startAgentClaim, $t as getSendFrom, A as lateMailReread, An as listWakeSchedules, At as downloadEmailAttachmentArchiveMember, B as ensureSearchReportsReplyState, Bn as resendAgentSignupVerification, Bt as getCreditBalance, C as waitOutRetryAfter, Cn as listRepeatingSends, Ct as createWakeSchedule, D as encodeReceivedAtSearchCursor, Dn as listSentEmails, Dt as deleteRoute, E as cursorFromAcceptedRows, En as listSentAttachmentArchiveMembers, Et as deleteMemory, F as AWAITING_VALUES, Ft as getAccessMode, G as queryUsesAwaiting, Gn as searchEmails, Gt as getFunctionTestRunTrace, H as formatRepliesCell, Hn as rotateKey, Ht as getEmail, I as REPLY_STATE_UNSUPPORTED_CODE, In as redeemCreditCode, It as getAccount, Jn as sendEmail, Jt as getMemory, Kn as searchMemories, Kt as getInboxStatus, L as ReplyStateUnsupportedError, Ln as removeDefaultNetworkMember, Lt as getAgentContactPolicy, M as sinceFromFlags, Mn as pullWebhookEvent, N as sleep$3, Nt as downloadSentAttachmentArchiveMember, O as fetchEmailSearchPage, On as listWakeAuthorizations, Ot as deleteWakeAuthorization, P as AWAITING_FLAG_DESCRIPTION, Pt as downloadSentAttachmentByFilename, Q as parseWebhookEvent, Qn as simulateRoute, Qt as getRepeatingSend, R as assertAwaitingFilterRows, Rn as reorderRoutes, S as transientFailure, Sn as listOrganizationPeople, St as createWakeAuthorization, T as collectNewAcceptedEmails, Tn as listRoutes, U as hasReplyState, Un as runWakeSchedule, Ut as getFunction, V as formatAwaitingCell, Vn as revokeCredential, Vt as getDefaultNetworkAgent, W as isAwaitingRejectedError, Wt as getFunctionRouting, X as isEmailReceivedEvent, Xn as setFunctionSecret, Xt as getOutboundStatus, Yn as setFunctionRoute, Yt as getOrgRoutingTopology, Zn as setMemory, Zt as getOwnGrant, _ as describeApiFailure, _n as listFunctionLogs, _t as completeWebhookEvent, a as REPEAT_IDLE_MAX_MINUTES, an as listActivityObjects, ar as updateDefaultNetworkMember, at as automatedRejectedError, b as retryTransient, bn as listKeys, bt as createKey, cn as listAgentNetworks, cr as updateRepeatingSend, d as parseRepeatTick, dn as listDefaultNetworkMembers, dr as updateWakeSchedule, dt as addDefaultNetworkMember, en as getSentEmail, er as startAgentSignup, et as AUTOMATED_FILTER_UNSUPPORTED_CODE, f as preparePresenceAliveEmail, fn as listDomains, fr as verifyAgentClaim, ft as cancelSentEmail, g as WaitDeadlineReached, gn as listEndpoints, gt as cliLogout, hn as listEmails, ht as checkSendability, i as REPEAT_EVERY_MAX_MINUTES, in as leaveOrganization, ir as unsetFunctionRoute, it as assertAutomatedVerdict, j as quoteDslValue, jn as pollCliLogin, jt as downloadEmailAttachmentByFilename, k as filtersFromFlags, kn as listWakeDispatches, kt as deleteWakeSchedule, l as parseInteractionEnvelope, lr as updateRoute, lt as hasAutomatedVerdict, m as repeatStopCommand, mn as listEmailRuleResults, mr as whoami, mt as checkDefaultNetworkContactAdmission, nn as getWakeSchedule, nr as stopRepeatFromEmail, nt as AUTOMATED_VALUES, o as REPEAT_MAX_SENDS_MAX, or as updateFunction, p as prepareSignalEmail, pn as listEmailAttachmentArchiveMembers, pr as verifyAgentSignup, pt as cancelSentEmailByIdempotencyKey, qn as semanticSearch, qt as getKey, rr as testFunction, rt as AutomatedFilterUnsupportedError, s as buildRepeatStopBody, sn as listAgentContacts, sr as updateKey, tn as getThread, tr as startCliLogin, tt as AUTOMATED_FLAG_DESCRIPTION, u as parsePresenceEnvelope, un as listDefaultNetworkAgents, ur as updateWakeAuthorization, ut as isAutomatedRejectedError, vn as listFunctions, vt as createEndpoint, w as SINCE_VALUE_HELP, wn as listRoles, x as retryingOperation, xn as listMailboxAccess, xt as createRoute, y as pauseForTransientFailure, yn as listGroups, yt as createFunction, z as awaitingRejectedError, zn as replyToEmail, zt as getConversation } from "../repeats-Dj_o6c-X-C6tOKAqk.js";
3
- import { S as readBundledConnectSkill, _ as NotificationOutcomeUnknownError, a as NotificationRetryError, b as installedConnectSkillFile, c as readConversationStatusContent, d as SESSION_UUID$1, g as NativeSessionNotLoadedError, h as NativeSessionError, i as notificationReceiptPage, l as createListenHandler, m as NativeSessionDisconnectedError, n as notificationSenders, o as isRoutineNotificationContent, p as defaultSessionSocket, r as openSessionNotifications, s as notificationPartReader, t as notificationScope, u as readableDelivery, v as connectSkillTarget, x as installedVersion, y as installConnectSkill } from "../notify-session-Bm59oy4j.js";
1
+ import { $ as readEmailInteraction, A as explainPendingMailNotFound, At as cliInvocation, Bt as formatAlreadySentNotice, C as promptLine$2, Ct as sendOutcomeExitCode, D as formatBrowserLoginInstructions, Dt as successfulSendOutcome, E as canOfferCopyHotkey, Et as serializeErrorPayload, F as latestOwnOutbound, Ft as loadSkillLine, G as clearConsumedPendingMail, Gt as classificationFactLines, H as wakeRecipientField, Ht as emptyEmailListHint, I as readThreadContext, It as currentMailSessionKey, J as readPendingMail, Jt as noFactsMessage, K as clearReadPendingMail, Kt as classificationOf, L as sentInThread, Lt as detectAgentRuntime, M as describeWake, N as displayAddress, Nt as generatedAgentName, O as listenForCopyHotkey, Ot as thrownErrorExitCode, P as formatMailWakeLine, Pt as identitySuggestions, Q as interactionNextActions, Qt as ConnectedAgentKeyRequiredError, R as serverMuted, Rt as detectRuntimeSession, S as approveThenRerunHint, St as sendIdentityFields, T as withApproval, Tt as sentHistoryWindowStart, U as wakeRelationship, Ut as mailReach, V as wakeReadCommand, Vt as withFlagSuggestion, W as claimPendingMail, Wt as readCallerMailFor, X as removePendingMail, Xt as ATTACHMENT_WAIT_MAX_MS, Y as recordPendingMail, Yt as ATTACHMENT_WAIT_FLAG_DESCRIPTION, Z as interactionHumanLine, Zt as withAttachmentWait, _ as takesStepUp, _n as setKnownCommands, _t as formatPriorRepliesWarning, a as createOperationCommand, an as selectedOrgFromEnv, at as SEND_OUTCOME_HELP, b as STEP_UP_HEADER$1, bt as priorRepliesThatWentOut, c as formatErrorPayload, cn as credentialsFromUserLogin, ct as buildFollowUpCommand, d as isGrantRefusal, dn as revokeUserLogin, dt as classifySendError, en as createAuthenticatedCliApiClient, et as replyExpectation, f as parameterFlagName, fn as roleKeyLabel, ft as deriveSendIdempotencyKey, g as surfaceUnauthorizedHint, gn as sanitizeForTerminal, gt as formatPriorRepliesCheckSkipped, h as runWithTiming, hn as routeWarningLines, ht as formatDeletedEarlierSendNotice, i as TIME_FLAG_DESCRIPTION, in as resolveCliApiRequestConfig, it as MAX_SEND_WAIT_TIMEOUT_MS, j as bareAddress, jt as connectNextSteps, k as openBrowser, l as hasControlCharacter$1, ln as fetchCliOrgs, lt as buildThrownSendFailureEnvelope, m as refusalDetails, mn as switchCurrentOrg, mt as followUpCommandPrefix, n as API_ERROR_CODES, nn as refreshStoredCliCredentials, o as extractErrorCode, on as CliOrgRefusedError, ot as apiResultHttpStatus, p as readTextFileFlag, pn as roleKeyText, pt as excludeSignalReplies, q as pendingMailPath, qt as isEmailId, r as OPERATION_PATH_FLAGS, rn as refuseOrgSelectionForProfile, rt as IDEMPOTENCY_KEY_FLAG_DESCRIPTION, s as extractErrorPayload, sn as RESTRICTED_ROLE_NOTE, st as assertValidIdempotencyKey, t as API_BASE_URL_FLAG_DESCRIPTION, tn as createCliApiClient, tt as replyInteractionWarning, u as headerFlagName, un as isBuiltInRoleKey, ut as checkPriorReplies, v as writeErrorWithHints, vn as operationManifest, vt as formatSendFailureSummary, w as terminalApprovalIo, wt as sendWaitClampNotice, x as STEP_UP_TOKEN_FLAG_DESCRIPTION, xt as reportSendCommandResult, y as BROWSER_FLAG_DESCRIPTION, yt as formatSendRecordFailureSummary, z as serverRelationship, zt as runtimeSessionVariableList } from "../api-command-DugUWmZH.js";
2
+ import { $ as parseFromHeader, $n as startAgentClaim, $t as getSendFrom, A as lateMailReread, An as listWakeSchedules, At as downloadEmailAttachmentArchiveMember, B as ensureSearchReportsReplyState, Bn as resendAgentSignupVerification, Bt as getCreditBalance, C as waitOutRetryAfter, Cn as listRepeatingSends, Ct as createWakeSchedule, D as encodeReceivedAtSearchCursor, Dn as listSentEmails, Dt as deleteRoute, E as cursorFromAcceptedRows, En as listSentAttachmentArchiveMembers, Et as deleteMemory, F as AWAITING_VALUES, Ft as getAccessMode, G as queryUsesAwaiting, Gn as searchEmails, Gt as getFunctionTestRunTrace, H as formatRepliesCell, Hn as rotateKey, Ht as getEmail, I as REPLY_STATE_UNSUPPORTED_CODE, In as redeemCreditCode, It as getAccount, Jn as sendEmail, Jt as getMemory, Kn as searchMemories, Kt as getInboxStatus, L as ReplyStateUnsupportedError, Ln as removeDefaultNetworkMember, Lt as getAgentContactPolicy, M as sinceFromFlags, Mn as pullWebhookEvent, N as sleep$3, Nt as downloadSentAttachmentArchiveMember, O as fetchEmailSearchPage, On as listWakeAuthorizations, Ot as deleteWakeAuthorization, P as AWAITING_FLAG_DESCRIPTION, Pt as downloadSentAttachmentByFilename, Q as parseWebhookEvent, Qn as simulateRoute, Qt as getRepeatingSend, R as assertAwaitingFilterRows, Rn as reorderRoutes, S as transientFailure, Sn as listOrganizationPeople, St as createWakeAuthorization, T as collectNewAcceptedEmails, Tn as listRoutes, U as hasReplyState, Un as runWakeSchedule, Ut as getFunction, V as formatAwaitingCell, Vn as revokeCredential, Vt as getDefaultNetworkAgent, W as isAwaitingRejectedError, Wt as getFunctionRouting, X as isEmailReceivedEvent, Xn as setFunctionSecret, Xt as getOutboundStatus, Yn as setFunctionRoute, Yt as getOrgRoutingTopology, Zn as setMemory, Zt as getOwnGrant, _ as describeApiFailure, _n as listFunctionLogs, _t as completeWebhookEvent, a as REPEAT_IDLE_MAX_MINUTES, an as listActivityObjects, ar as updateDefaultNetworkMember, at as automatedRejectedError, b as retryTransient, bn as listKeys, bt as createKey, cn as listAgentNetworks, cr as updateRepeatingSend, d as parseRepeatTick, dn as listDefaultNetworkMembers, dr as updateWakeSchedule, dt as addDefaultNetworkMember, en as getSentEmail, er as startAgentSignup, et as AUTOMATED_FILTER_UNSUPPORTED_CODE, f as preparePresenceAliveEmail, fn as listDomains, fr as verifyAgentClaim, ft as cancelSentEmail, g as WaitDeadlineReached, gn as listEndpoints, gt as cliLogout, hn as listEmails, ht as checkSendability, i as REPEAT_EVERY_MAX_MINUTES, in as leaveOrganization, ir as unsetFunctionRoute, it as assertAutomatedVerdict, j as quoteDslValue, jn as pollCliLogin, jt as downloadEmailAttachmentByFilename, k as filtersFromFlags, kn as listWakeDispatches, kt as deleteWakeSchedule, l as parseInteractionEnvelope, lr as updateRoute, lt as hasAutomatedVerdict, m as repeatStopCommand, mn as listEmailRuleResults, mr as whoami, mt as checkDefaultNetworkContactAdmission, nn as getWakeSchedule, nr as stopRepeatFromEmail, nt as AUTOMATED_VALUES, o as REPEAT_MAX_SENDS_MAX, or as updateFunction, p as prepareSignalEmail, pn as listEmailAttachmentArchiveMembers, pr as verifyAgentSignup, pt as cancelSentEmailByIdempotencyKey, qn as semanticSearch, qt as getKey, rr as testFunction, rt as AutomatedFilterUnsupportedError, s as buildRepeatStopBody, sn as listAgentContacts, sr as updateKey, tn as getThread, tr as startCliLogin, tt as AUTOMATED_FLAG_DESCRIPTION, u as parsePresenceEnvelope, un as listDefaultNetworkAgents, ur as updateWakeAuthorization, ut as isAutomatedRejectedError, vn as listFunctions, vt as createEndpoint, w as SINCE_VALUE_HELP, wn as listRoles, x as retryingOperation, xn as listMailboxAccess, xt as createRoute, y as pauseForTransientFailure, yn as listGroups, yt as createFunction, z as awaitingRejectedError, zn as replyToEmail, zt as getConversation } from "../repeats-Dj_o6c-X-DqcHvqvW.js";
3
+ import { S as readBundledConnectSkill, _ as NotificationOutcomeUnknownError, a as NotificationRetryError, b as installedConnectSkillFile, c as readConversationStatusContent, d as SESSION_UUID$1, g as NativeSessionNotLoadedError, h as NativeSessionError, i as notificationReceiptPage, l as createListenHandler, m as NativeSessionDisconnectedError, n as notificationSenders, o as isRoutineNotificationContent, p as defaultSessionSocket, r as openSessionNotifications, s as notificationPartReader, t as notificationScope, u as readableDelivery, v as connectSkillTarget, x as installedVersion, y as installConnectSkill } from "../notify-session-DHqBICIk.js";
4
4
  import { A as AGENT_ADDRESS_ENV, C as saveCliCredentials, D as loadActiveChatState, E as deleteChatState, F as agentProfilesDirectory, H as requireDefaultLoginProfile, M as AgentConnectionSetupError, N as agentProfileDirectory, O as loadChatConversationByLocalId, P as agentProfileName, R as loadConnectedAgentProfile, S as resolveCliAuth, T as chatStatePath, W as PrimitiveApiClient, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as loadCliCredentials, c as resolveConfigEnvironment, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as AGENT_PROFILE_ENV, k as saveActiveChatState, l as saveCliConfig, m as cliAccessTokenExpiresAt, n as deleteCliConfig, o as redactCliEnvironment, p as acquireCliCredentialsLock, r as emptyCliConfig, s as removeCliEnvironment, u as upsertCliEnvironment, v as deleteCliCredentialsLock, w as saveSignupCredentials, y as detectPrimitiveKeyEnvMisname } from "../cli-config-Ms_0vNjq.js";
5
5
  import { C as resolveListenSubscription, S as normalizeListenOrigin, _ as acquireListenLock, a as mailLockBusy, c as mailTime, d as readMailJson, f as removeMailFile, g as ListenStateError, h as writeMailJson, i as mailId, l as privateMailDirectory, m as withMailLock, o as mailObject, u as privateMailPermissions, y as listenIdentity } from "../shared-mail-files-BAQSGxxz.js";
6
- import { $ as saveConnectionName, A as ContactPolicyReadRetryError, B as AddressNotesApiError, C as RECEIVER_MODES, Ct as upsertManagedBlock, D as registrationSummary, Dt as refreshOwnerMemberAddressPeriodically, E as failedSessionRegistration, Et as addSessionReceiver, F as canonicalContactSelector, G as contactAgentAddress, H as runAddressNotesRequest, I as AgentDisconnectError, J as agentInvitationHash, K as runContactRequest, L as claimOverRevokedSetup, M as NetworkAdmissionPendingError, N as createNotificationContactPolicy, O as registrationSummaryLine, Ot as withOwnerMemberAddress, P as evaluateContactPolicy, Q as refusalAfterReplacement, R as clearRevokedAddresses, S as runAgentConnect, St as readManagedFile, T as verificationReplySubmitted, Tt as readHookLauncherWarning, U as ContactsApiError, V as addressNoteTarget, W as contactAddress, X as readAgentInvitation, Y as connectAgent, Z as receiverStatusCommand, _ as enrollmentResumes, _t as samePath, a as endSession, at as boundSessionProfiles, b as invitationProfileName, bt as inspectManagedBlock, c as pendingSessionDisconnects, ct as describeSessionHookChange, d as registerSession, dt as ephemeralCliReason, et as savedReceiver, f as retryPendingDisconnects, ft as findOnPath, g as enrollAgent, gt as repairClaudeHooks, h as staleSessionAgents, ht as readClaudeSettings, i as connectedProfilesForSession, it as recordHookMailCheck, j as NETWORK_ADMISSION_PENDING_RETRY_MS, k as waitForRegistrations, l as psProcessInfo, lt as describeSessionHookItem, m as runDetached, mt as inspectClaudeHooks, n as OMP_MAIN, nt as releaseRegistrationsForProfile, o as headlessClaudeRun, ot as claudeConfigDir, p as revokeStaleSessionAgents, pt as hasMachineSessionHooks, q as agentConnectionStatus, r as agentConnectionState, rt as installClaudeWakeHook, st as currentCliLocation, t as HOOK_END_GRACE_MS, tt as MACHINE_RUNTIMES, u as readClaudeHookInput, ut as ensureHookLauncher, v as connectWarnings, vt as writeClaudeSettings, w as externalReceivingState, wt as writeManagedFile, x as pollCheckCommand, xt as jsonIndentation, y as defaultAgentProfileName, yt as MachineFileError, z as disconnectAgent } from "../machine-session-YQWxPePp.js";
7
- import { a as readBeforeDeadline, c as readPresenceProjection, i as readApiBeforeDeadline, n as isPlainChatReply, o as scopedChatSenderTrust, r as isScopedChatReply, s as presenceDisposition, t as isConnectedChatCredential } from "../scoped-chat-GItY2PvL.js";
6
+ import { $ as saveConnectionName, A as ContactPolicyReadRetryError, B as AddressNotesApiError, C as RECEIVER_MODES, Ct as upsertManagedBlock, D as registrationSummary, Dt as refreshOwnerMemberAddressPeriodically, E as failedSessionRegistration, Et as addSessionReceiver, F as canonicalContactSelector, G as contactAgentAddress, H as runAddressNotesRequest, I as AgentDisconnectError, J as agentInvitationHash, K as runContactRequest, L as claimOverRevokedSetup, M as NetworkAdmissionPendingError, N as createNotificationContactPolicy, O as registrationSummaryLine, Ot as withOwnerMemberAddress, P as evaluateContactPolicy, Q as refusalAfterReplacement, R as clearRevokedAddresses, S as runAgentConnect, St as readManagedFile, T as verificationReplySubmitted, Tt as readHookLauncherWarning, U as ContactsApiError, V as addressNoteTarget, W as contactAddress, X as readAgentInvitation, Y as connectAgent, Z as receiverStatusCommand, _ as enrollmentResumes, _t as samePath, a as endSession, at as boundSessionProfiles, b as invitationProfileName, bt as inspectManagedBlock, c as pendingSessionDisconnects, ct as describeSessionHookChange, d as registerSession, dt as ephemeralCliReason, et as savedReceiver, f as retryPendingDisconnects, ft as findOnPath, g as enrollAgent, gt as repairClaudeHooks, h as staleSessionAgents, ht as readClaudeSettings, i as connectedProfilesForSession, it as recordHookMailCheck, j as NETWORK_ADMISSION_PENDING_RETRY_MS, k as waitForRegistrations, l as psProcessInfo, lt as describeSessionHookItem, m as runDetached, mt as inspectClaudeHooks, n as OMP_MAIN, nt as releaseRegistrationsForProfile, o as headlessClaudeRun, ot as claudeConfigDir, p as revokeStaleSessionAgents, pt as hasMachineSessionHooks, q as agentConnectionStatus, r as agentConnectionState, rt as installClaudeWakeHook, st as currentCliLocation, t as HOOK_END_GRACE_MS, tt as MACHINE_RUNTIMES, u as readClaudeHookInput, ut as ensureHookLauncher, v as connectWarnings, vt as writeClaudeSettings, w as externalReceivingState, wt as writeManagedFile, x as pollCheckCommand, xt as jsonIndentation, y as defaultAgentProfileName, yt as MachineFileError, z as disconnectAgent } from "../machine-session-Cre9Mt_2.js";
7
+ import { a as readBeforeDeadline, c as readPresenceProjection, i as readApiBeforeDeadline, n as isPlainChatReply, o as scopedChatSenderTrust, r as isScopedChatReply, s as presenceDisposition, t as isConnectedChatCredential } from "../scoped-chat-BpUAgX4g.js";
8
8
  import { autoSignalEligible, dispatchAutoRead, dispatchAutoWorking, haltAutoWorking, isSentSignal, readWorkingLease, restoreAutoWorking } from "./auto-signals.js";
9
- import { a as ensureSharedMailSubscription, c as isConnectedAgentKey, d as reserveSharedMailSubscription, f as followEmailConversation, g as EventReceiverError, h as EventConnection, i as sharedMailScope, l as createSharedMailWaiter, m as otherParticipants, n as reconcileChatSend, p as readConversationFollow, r as openSharedMailReceiver, s as AGENT_CONNECTION_REQUIRED_MESSAGE, t as sendSignal, u as openSharedMailStore } from "../signal-command-CvEDx8qQ.js";
9
+ import { a as ensureSharedMailSubscription, c as isConnectedAgentKey, d as reserveSharedMailSubscription, f as followEmailConversation, g as EventReceiverError, h as EventConnection, i as sharedMailScope, l as createSharedMailWaiter, m as otherParticipants, n as reconcileChatSend, p as readConversationFollow, r as openSharedMailReceiver, s as AGENT_CONNECTION_REQUIRED_MESSAGE, t as sendSignal, u as openSharedMailStore } from "../signal-command-B66YR0-A.js";
10
10
  import { a as thrownErrorBody, i as runStreamingJson, n as guardJsonOutputCommands, r as jsonOutputRequested, t as JSON_OUTPUT_FLAG } from "../json-output-ch756TXV.js";
11
11
  import { a as backgroundListenStatus, c as runBackgroundListen, d as stopBackgroundListen, f as verifyBackgroundListenTarget, o as backgroundListenToken, r as backgroundListenConfiguration, u as startBackgroundListen } from "../listen-background-DyBYca28.js";
12
12
  import { ORG_SELECTION_ENV } from "./global-org-flag.js";
@@ -219,6 +219,11 @@ const openapiDocument = {
219
219
  "description": "Rules a member writes in plain English to pick out received mail. The organization needs the email rules feature, and the caller must be a member (their own session, or an OAuth or CLI token that is not narrowed) with mail:read.",
220
220
  "x-openapi-spec": "https://api.primitive.dev/v1/openapi/tags/email-rules"
221
221
  },
222
+ {
223
+ "name": "Email Rule Oversight",
224
+ "description": "The email rules that reach your own mailboxes, whoever wrote them, read-only. The organization needs the email rules feature, and the caller must be a member with mail:read on their own dashboard session or the Primitive CLI, not narrowed.",
225
+ "x-openapi-spec": "https://api.primitive.dev/v1/openapi/tags/email-rule-oversight"
226
+ },
222
227
  {
223
228
  "name": "Changes",
224
229
  "description": "One replayable long-poll of what changed (ids and kinds) across mail, sent mail, read state, the agent directory and access, so a client can keep its snapshots current without polling every list.",
@@ -1495,7 +1500,7 @@ const openapiDocument = {
1495
1500
  },
1496
1501
  "security": [{ "BearerAuth": [] }],
1497
1502
  "x-primitive-permission": ["account:manage"],
1498
- "description": "Update the mutable account settings (such as display name and notification preferences) for the authenticated organization."
1503
+ "description": "Update the organization's spam threshold (spam_threshold) and whether an email's content is discarded once a webhook confirms receipt (discard_content_on_webhook_confirmed), the opt-in POST /emails/{id}/discard-content needs."
1499
1504
  }
1500
1505
  },
1501
1506
  "/account/storage": { "get": {
@@ -5317,7 +5322,7 @@ const openapiDocument = {
5317
5322
  } }
5318
5323
  },
5319
5324
  "503": {
5320
- "description": "Local receiving is unavailable",
5325
+ "description": "Local receiving is unavailable (pull_unavailable), or the subscription was too busy to claim from in time (service_unavailable): nothing was claimed, so retry the pull after Retry-After",
5321
5326
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
5322
5327
  }
5323
5328
  },
@@ -21978,7 +21983,7 @@ const openapiDocument = {
21978
21983
  "/email-rules/compile": { "post": {
21979
21984
  "operationId": "compileEmailRule",
21980
21985
  "summary": "Compile a rule description",
21981
- "description": "Turns a plain-English description of which received emails to match into a rule tree over the published column contract, with a plain-English explanation of what it matches, the assumptions made, and warnings for anything the columns cannot express. Where the columns fall short the rule may carry up to three yes/no questions, asked about an email only after its conditions match. Nothing is saved. A rule with no questions has its conditions counted, read only, over the caller's recent classified mail (what a test of it covers by default): breadth is that count, and a warning says when it matches nearly all of it. The SQL the rule renders to is returned only when include_sql is true. kind do_not_share compiles a do-not-share rule for recall: conditions only as narrow as the description says, the judgement of what it describes put into questions, and with nothing a column decides for certain, conditions that hold for every email. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
21986
+ "description": "Turns a plain-English description of which received emails to match into a rule tree over the published column contract, with a plain-English explanation of what it matches, the assumptions made, and warnings for anything the columns cannot express. Where the columns fall short the rule may carry up to five yes/no questions, asked about an email only after its conditions match; they read its subject, its new text and its attachments' file names, never what is inside an attachment, and warnings say when a question mentions attachments. Nothing is saved. A rule with no questions has its conditions counted, read only, over the caller's recent classified mail (what a test of it covers by default): breadth is that count, and a warning says when it matches nearly all of it. The SQL the rule renders to is returned only when include_sql is true. kind do_not_share compiles a do-not-share rule for recall: conditions only as narrow as the description says, the judgement of what it describes put into questions, and with nothing a column decides for certain, conditions that hold for every email. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
21982
21987
  "tags": ["Email Rules"],
21983
21988
  "security": [{ "BearerAuth": [] }],
21984
21989
  "requestBody": {
@@ -22107,7 +22112,7 @@ const openapiDocument = {
22107
22112
  }
22108
22113
  },
22109
22114
  "400": {
22110
- "description": "validation_error: give exactly one of tree and rule_id, at most 200 email_ids (with the expected emails), 1 to 100 expected emails none in both lists, and a tree that is valid for the column contract. evaluation_scope, scope_selection and kind go only with tree. A saved rule whose current version no longer renders answers details.reason version_not_renderable; email_ids that with its saved expectations name over 200 emails, too_many_emails.",
22115
+ "description": "validation_error: give exactly one of tree and rule_id, at most 200 email_ids (with the expected emails), 1 to 100 expected emails none in both lists (or only expect.samples, up to 20), and a tree that is valid for the column contract. evaluation_scope, scope_selection and kind go only with tree. A saved rule whose current version no longer renders answers details.reason version_not_renderable; email_ids that with its saved expectations name over 200 emails, too_many_emails.",
22111
22116
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22112
22117
  },
22113
22118
  "401": {
@@ -22178,6 +22183,111 @@ const openapiDocument = {
22178
22183
  }
22179
22184
  }]
22180
22185
  } },
22186
+ "/email-rules/look-alikes": { "post": {
22187
+ "operationId": "generateLookAlikes",
22188
+ "summary": "Write look-alike emails a rule should or should not match",
22189
+ "description": "Writes made-up emails that come close to what a grant rule describes, and reports what the rule decides for each, as test-sample would. Most should not match (expected no_match): business near misses from its senders and topic (kind other: an all-clear, a reply with nothing new, a related document, another subject) and private mail (a personal medical bill, pay for named people, a personal purchase, personal banking or tax). By default a third, rounded down, are near cases it should match (expected match, kind other). failed means the rule decided one against expected. Nothing is stored and no mail is read. Keep them, with their expected, with add_expectation_samples, or for a request with the accept's expectation_samples. Limited to 20 an hour per member. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
22190
+ "tags": ["Email Rules"],
22191
+ "security": [{ "BearerAuth": [] }],
22192
+ "requestBody": {
22193
+ "required": true,
22194
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenerateLookAlikesRequest" } } }
22195
+ },
22196
+ "responses": {
22197
+ "200": {
22198
+ "description": "Write look-alike emails a rule should or should not match",
22199
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
22200
+ "type": "object",
22201
+ "required": ["data"],
22202
+ "properties": { "data": { "$ref": "#/components/schemas/GenerateLookAlikesResult" } }
22203
+ }] } } },
22204
+ "headers": {
22205
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
22206
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
22207
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
22208
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
22209
+ }
22210
+ },
22211
+ "400": {
22212
+ "description": "validation_error: give exactly one of tree, rule_id and request_id, and a tree valid for the column contract; details.reason do_not_share for a do-not-share rule, no_address when no sign-in address was found and no to given, version_not_renderable for a saved version or a request's rule that no longer fits.",
22213
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22214
+ },
22215
+ "401": {
22216
+ "description": "Invalid or missing API key",
22217
+ "content": { "application/json": {
22218
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22219
+ "example": {
22220
+ "success": false,
22221
+ "error": {
22222
+ "code": "unauthorized",
22223
+ "message": "Invalid or missing API key"
22224
+ }
22225
+ }
22226
+ } },
22227
+ "headers": {
22228
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
22229
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
22230
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
22231
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
22232
+ }
22233
+ },
22234
+ "403": {
22235
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead. email_rule_scope_not_readable: scope_selection names something you cannot read.",
22236
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22237
+ },
22238
+ "404": {
22239
+ "description": "not_found: rule_id names no rule you can see (details.reason rule_not_found), or request_id no rule request you can see (request_not_found).",
22240
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22241
+ },
22242
+ "409": {
22243
+ "description": "email_rule_author_unresolvable: the rule cannot run as its author (for an unsaved tree or a request, you). email_rule_request_not_pending: request_id names a request already decided.",
22244
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22245
+ },
22246
+ "422": {
22247
+ "description": "email_rule_look_alikes_not_written: the model declined, ran out of room or time, or wrote none that fit (details.reason refused, truncated, deadline or invalid).",
22248
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22249
+ },
22250
+ "429": {
22251
+ "description": "Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.",
22252
+ "headers": {
22253
+ "Retry-After": {
22254
+ "schema": { "type": "integer" },
22255
+ "description": "Seconds to wait before retrying"
22256
+ },
22257
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
22258
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
22259
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
22260
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
22261
+ },
22262
+ "content": { "application/json": {
22263
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22264
+ "example": {
22265
+ "success": false,
22266
+ "error": {
22267
+ "code": "rate_limit_exceeded",
22268
+ "message": "Rate limit exceeded"
22269
+ }
22270
+ }
22271
+ } }
22272
+ },
22273
+ "503": {
22274
+ "description": "email_rule_compiler_unavailable: the model, or the classifier that decides each, is not available or could not be reached.",
22275
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22276
+ }
22277
+ },
22278
+ "x-primitive-permission": ["mail:read"],
22279
+ "parameters": [{
22280
+ "name": "Idempotency-Key",
22281
+ "in": "header",
22282
+ "required": false,
22283
+ "description": "Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.",
22284
+ "schema": {
22285
+ "type": "string",
22286
+ "minLength": 1,
22287
+ "maxLength": 255
22288
+ }
22289
+ }]
22290
+ } },
22181
22291
  "/email-rules/test-sample": { "post": {
22182
22292
  "operationId": "testEmailRuleOnSample",
22183
22293
  "summary": "Test a rule against a made-up email",
@@ -22279,7 +22389,7 @@ const openapiDocument = {
22279
22389
  "post": {
22280
22390
  "operationId": "createEmailRule",
22281
22391
  "summary": "Create a rule",
22282
- "description": "Saves a rule as version 1 from a compile response or a tree you wrote, checked against the column contract; without an explanation the server writes one. It starts in observe (what it matches is recorded, nothing granted or blocked) unless state is draft (not evaluated). You are its author, and it never reaches mail you cannot read. evaluation_scope is own_mailboxes (your own mail) unless you name author (all you can read) or selected; scope_selection may name only what you can read. The answer carries the scope as stored. expectations saves emails it must match and must not; the answer judges them (expectations_check). kind do_not_share blocks sharing: it runs before every other rule, and while active no rule shares an email it matches or cannot decide; a key cannot list it (details.reason rule_blocks_sharing). Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
22392
+ "description": "Saves a rule as version 1 from a compile response or a tree you wrote, checked against the column contract; without an explanation the server writes one. It starts in observe (what it matches is recorded, nothing granted or blocked) unless state is draft (not evaluated), or active for a do_not_share rule. You are its author, and it never reaches mail you cannot read. evaluation_scope is own_mailboxes (your own mail) unless you name author (all you can read) or selected; scope_selection may name only what you can read. The answer carries the scope as stored. expectations saves emails it must match and must not; the answer judges them (expectations_check). kind do_not_share blocks sharing: it runs before every other rule, and while active no rule shares an email it matches or cannot decide; a key cannot list it (details.reason rule_blocks_sharing). Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
22283
22393
  "tags": ["Email Rules"],
22284
22394
  "security": [{ "BearerAuth": [] }],
22285
22395
  "requestBody": {
@@ -22568,7 +22678,7 @@ const openapiDocument = {
22568
22678
  }
22569
22679
  },
22570
22680
  "403": {
22571
- "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead.",
22681
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead. forbidden with details.reason covering_view_only (your dashboard session or the Primitive CLI only): you may read this rule only because it covers your mailbox; details.covering_view is where (getEmailRuleCoveringMe).",
22572
22682
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22573
22683
  },
22574
22684
  "404": {
@@ -22646,7 +22756,7 @@ const openapiDocument = {
22646
22756
  }
22647
22757
  },
22648
22758
  "400": {
22649
- "description": "validation_error: invalid body or tree (details.errors), too large (too_large), a version that no longer renders (version_not_renderable), or restore_version names the current version (already_current). no_expectations: if_expectations_pass with no expectations to judge; nothing was saved.",
22759
+ "description": "validation_error: invalid body or tree (details.errors), too large (too_large), a version that no longer renders (version_not_renderable), restore_version names the current version (already_current), or over 20 made-up emails (too_many_expectation_samples). no_expectations: if_expectations_pass with no expectations to judge; nothing was saved.",
22650
22760
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22651
22761
  },
22652
22762
  "401": {
@@ -22669,7 +22779,7 @@ const openapiDocument = {
22669
22779
  }
22670
22780
  },
22671
22781
  "403": {
22672
- "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead. email_rule_author_only: only the rule's author may make this change. forbidden (rule_shared): the rule is shared, so only its author or a member manager can turn it off. email_rule_scope_not_readable: scope_selection names something you cannot read. grant_forbidden (mail_depth): if_expectations_pass needs content access to mail.",
22782
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead. email_rule_author_only: only the rule's author may make this change. forbidden (rule_shared): the rule is shared, so only its author or a member manager can turn it off. email_rule_scope_not_readable: scope_selection names something you cannot read. grant_forbidden (mail_depth): if_expectations_pass needs content access to mail when it judges emails by id.",
22673
22783
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22674
22784
  },
22675
22785
  "404": {
@@ -22677,7 +22787,7 @@ const openapiDocument = {
22677
22787
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22678
22788
  },
22679
22789
  "409": {
22680
- "description": "email_rule_conflict: the rule changed (details.reason version_moved with current_version, revision_moved with current_revision, expectations_moved or rule_changed); read it again and retry. email_rule_scope_locked: the rule is shared. email_rule_author_unresolvable: the rule could not run as you over the chosen scope. expectations_failed or expectations_undecided (if_expectations_pass): nothing was saved; details carries current_version, state and expectations_check.",
22790
+ "description": "email_rule_conflict: the rule changed (details.reason version_moved with current_version, revision_moved with current_revision, expectations_moved or rule_changed); read it again and retry. email_rule_scope_locked: the rule is shared. email_rule_author_unresolvable: the rule could not run as you over the chosen scope. expectations_failed, expectations_unreadable (details.unreadable) or expectations_undecided, with if_expectations_pass: nothing was saved; details carries current_version, state and expectations_check.",
22681
22791
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22682
22792
  },
22683
22793
  "429": {
@@ -22947,7 +23057,7 @@ const openapiDocument = {
22947
23057
  "/emails/{id}/rule-results": { "get": {
22948
23058
  "operationId": "listEmailRuleResults",
22949
23059
  "summary": "List every rule's result on an email",
22950
- "description": "Every rule you can see and what it decided about this email (matched, not matched, or not run, with what is known), in the words of the version that decided. A matched do_not_share rule keeps rules from sharing the email while it is active; what was shared before then stays shared. Only a caller who can read the email gets an answer. classification is what the classifier read from the email, every fact as rule conditions read it, so you can see why a rule did or did not match. At most 50 rules are listed, those with a result first; hidden counts the rest. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
23060
+ "description": "What each rule you can see decided about this email (matched, not matched, or not run), in the words of the deciding version. A do_not_share rule that matched it blocks sharing while active; earlier shares stay. Only a caller who can read the email gets an answer. classification is every fact the classifier read, as rule conditions read it. At most 50 rules are listed, results first; not_listed counts the rest. Another member's rule that decided on your own mail is listed read_only (dashboard and CLI only); hidden counts deciding rules you cannot see. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
22951
23061
  "tags": ["Email Rules"],
22952
23062
  "security": [{ "BearerAuth": [] }],
22953
23063
  "parameters": [{
@@ -23695,7 +23805,7 @@ const openapiDocument = {
23695
23805
  "get": {
23696
23806
  "operationId": "getEmailRuleRequest",
23697
23807
  "summary": "Read a rule request",
23698
- "description": "A rule request: the rule as proposed, who made it and to whom, and whether each of them is still a member. A request is visible to the member who made it, the member it was made to, and the organization's owners and admins. If you are the member asked, you can try the rule as proposed before answering: pass the request's tree to POST /v1/email-rules/test to test it on mail you have received. A test on mail already received shows only what has arrived. To see what the rule would do with an email that has not, such as one you would never want shared, pass the request's tree to POST /v1/email-rules/test-sample (testEmailRuleOnSample; in the Primitive CLI, primitive email-rules test-email-rule-on-sample --body-file sample.json) with an email you make up. Nothing is stored, and it needs no access beyond testing a rule of your own. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
23808
+ "description": "A rule request: the rule as proposed, who made it and to whom, and whether each of them is still a member. A request is visible to the member who made it, the member it was made to, and the organization's owners and admins. If you are the member asked, you can try the rule as proposed before answering: pass the request's tree to POST /v1/email-rules/test to test it on mail you have received. A test on mail already received shows only what has arrived. To see what the rule would do with an email that has not, such as one you would never want shared, pass the request's tree to POST /v1/email-rules/test-sample (testEmailRuleOnSample; in the Primitive CLI, primitive email-rules test-email-rule-on-sample --body-file sample.json) with an email you make up, or have look-alikes written for it (POST /v1/email-rules/look-alikes with request_id; primitive email-rules generate-look-alikes). Neither stores anything, and neither needs access beyond testing a rule of your own; the accept keeps any you send as expectation_samples on the rule it writes. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
23699
23809
  "tags": ["Email Rules"],
23700
23810
  "security": [{ "BearerAuth": [] }],
23701
23811
  "parameters": [{
@@ -24072,7 +24182,7 @@ const openapiDocument = {
24072
24182
  "/email-rule-requests/{id}/accept": { "post": {
24073
24183
  "operationId": "acceptEmailRuleRequest",
24074
24184
  "summary": "Accept a rule request",
24075
- "description": "Writes the rule as yours, over your own mailboxes only, turns it on, and shares what it matches from now on with the member who asked. Send no body to accept it as asked, or change it first with english and tree together (and an explanation, or the server writes one). Because this gives your mail to someone else, it is limited to the dashboard and the Primitive CLI: other OAuth apps and agent tokens are refused. Before accepting, test it on your mail: pass the request's tree to POST /v1/email-rules/test. A test on mail already received shows only what has arrived. To see what the rule would do with an email that has not, such as one you would never want shared, pass the request's tree to POST /v1/email-rules/test-sample (testEmailRuleOnSample; in the Primitive CLI, primitive email-rules test-email-rule-on-sample --body-file sample.json) with an email you make up. Nothing is stored, and it needs no access beyond testing a rule of your own. Accepting also needs mail:share_own (sharing your own mail through email rules, which only shows it and never sends): a role without it is refused, whether or not it can send. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
24185
+ "description": "Writes the rule as yours, over your own mailboxes only, turns it on, and shares what it matches from now on with the member who asked: each email in full with its attachments, but never raw. It does not go back through mail already received. Send no body to accept it as asked, or change it first with english and tree together (and an explanation, or the server writes one). Because this gives your mail to someone else, it is limited to the dashboard and the Primitive CLI: other OAuth apps and agent tokens are refused. Before accepting, test it on your mail: pass the request's tree to POST /v1/email-rules/test. A test on mail already received shows only what has arrived. To see what the rule would do with an email that has not, such as one you would never want shared, pass the request's tree to POST /v1/email-rules/test-sample (testEmailRuleOnSample; in the Primitive CLI, primitive email-rules test-email-rule-on-sample --body-file sample.json) with an email you make up, or have look-alikes written for it (POST /v1/email-rules/look-alikes with request_id; primitive email-rules generate-look-alikes). Neither stores anything, and neither needs access beyond testing a rule of your own; the accept keeps any you send as expectation_samples on the rule it writes. Made-up emails sent in expectation_samples are kept on the rule this writes, where GET /v1/email-rules/{id} lists them. Accepting also needs mail:share_own (sharing your own mail through email rules, which only shows it and never sends): a role without it is refused, whether or not it can send. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
24076
24186
  "tags": ["Email Rules"],
24077
24187
  "security": [{ "BearerAuth": [] }],
24078
24188
  "parameters": [{
@@ -24109,7 +24219,7 @@ const openapiDocument = {
24109
24219
  }] } } }
24110
24220
  },
24111
24221
  "400": {
24112
- "description": "validation_error: a changed rule needs english and tree together, and a tree that is valid for the column contract. The rule as asked is checked the same way, and must be changed first if it no longer passes.",
24222
+ "description": "validation_error: a changed rule needs english and tree together, and a tree that is valid for the column contract. The rule as asked is checked the same way, and must be changed first if it no longer passes. Made-up emails in expectation_samples are checked as on a rule write (at most 20, each within the sample bounds). Nothing is accepted.",
24113
24223
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
24114
24224
  },
24115
24225
  "401": {
@@ -24510,7 +24620,7 @@ const openapiDocument = {
24510
24620
  "delete": {
24511
24621
  "operationId": "endEmailRuleShare",
24512
24622
  "summary": "End a rule share",
24513
- "description": "Stops sharing. Either person on the share can do this, or an owner or admin who manages members. The share is kept, marked as ended. This needs no mail access and does not depend on the email rules feature. An owner or admin not on the share must use a dashboard session. Otherwise use your own session, or an OAuth or CLI token that acts with your full access: API keys and agent keys are refused.",
24623
+ "description": "Stops sharing; the rule itself is left unchanged, as the answer's note says. Either person on the share can do this, or an owner or admin who manages members. The share is kept, marked as ended. This needs no mail access and does not depend on the email rules feature. An owner or admin not on the share must use a dashboard session. Otherwise use your own session, or an OAuth or CLI token that acts with your full access: API keys and agent keys are refused.",
24514
24624
  "tags": ["Email Rules"],
24515
24625
  "security": [{ "BearerAuth": [] }],
24516
24626
  "parameters": [{
@@ -24718,29 +24828,43 @@ const openapiDocument = {
24718
24828
  },
24719
24829
  "x-primitive-permission": ["mail:read"]
24720
24830
  } },
24721
- "/domains/{id}/agent-authorization": { "get": {
24722
- "operationId": "getDomainAgentAuthorization",
24723
- "summary": "Get a domain's Agent Authorization status",
24724
- "description": "Whether the domain publishes an Agent Authorization (`_agents`) TXT record that lets agents on Primitive email it, read from DNS with the same engine the send path uses (an answer may be up to five minutes old): published (a record authorizes them), published_unauthorized (a record exists but does not), not_set_up (no record, the default) or inconclusive (the lookup could not tell; retry). The domain is one of the organization's, verified or pending; a credential confined to some mailboxes sees only the domains its scope covers. Needs domains:read.",
24725
- "tags": ["Domains"],
24831
+ "/email-rules/covering-me": { "get": {
24832
+ "operationId": "listEmailRulesCoveringMe",
24833
+ "summary": "List the rules over your mail",
24834
+ "description": "Every email rule that covers one of your own mailboxes now, yours included, newest first, in any state. A rule covers you when the scope it runs with now reaches one of your own mailboxes (your member mailbox, or a personal agent's address): one whose author reads all your mail (author scope, for an author who reads the organization's mail or holds an access entry for your mailbox), or one that names your mailbox or its domain (selected). Another member's own_mailboxes rule never covers you. You see its name, wording, current tree, state, kind and author, and which of your own mailboxes it reaches. Its wording and tree are shown as written, so they can name other addresses (a condition on who an email is from or to). You do not see the other inboxes and domains its scope names, its saved expectations, or the keys, shares and webhooks that use it. A page can hold fewer rules than limit, or none, and still carry a cursor (one request reads a bounded number of rules): keep reading until meta.cursor is null. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature. Only on your own dashboard session or the Primitive CLI.",
24835
+ "tags": ["Email Rule Oversight"],
24726
24836
  "security": [{ "BearerAuth": [] }],
24727
24837
  "parameters": [{
24728
- "name": "id",
24729
- "in": "path",
24730
- "required": true,
24731
- "description": "The domain id from listDomains.",
24838
+ "name": "limit",
24839
+ "in": "query",
24840
+ "required": false,
24841
+ "description": "Rules per page, from 1 to 100; defaults to 50.",
24842
+ "schema": {
24843
+ "default": 50,
24844
+ "type": "integer",
24845
+ "minimum": 1,
24846
+ "maximum": 100
24847
+ }
24848
+ }, {
24849
+ "name": "cursor",
24850
+ "in": "query",
24851
+ "required": false,
24852
+ "description": "meta.cursor from the preceding page. Omit for the first page.",
24732
24853
  "schema": {
24733
24854
  "type": "string",
24734
- "format": "uuid"
24855
+ "maxLength": 200
24735
24856
  }
24736
24857
  }],
24737
24858
  "responses": {
24738
24859
  "200": {
24739
- "description": "Get a domain's Agent Authorization status",
24860
+ "description": "List the rules over your mail",
24740
24861
  "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
24741
24862
  "type": "object",
24742
- "required": ["data"],
24743
- "properties": { "data": { "$ref": "#/components/schemas/GetDomainAgentAuthorizationResult" } }
24863
+ "required": ["data", "meta"],
24864
+ "properties": {
24865
+ "data": { "$ref": "#/components/schemas/ListEmailRulesCoveringMeResult" },
24866
+ "meta": { "$ref": "#/components/schemas/CursorPageMeta" }
24867
+ }
24744
24868
  }] } } },
24745
24869
  "headers": {
24746
24870
  "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
@@ -24750,7 +24874,7 @@ const openapiDocument = {
24750
24874
  }
24751
24875
  },
24752
24876
  "400": {
24753
- "description": "validation_error: the id is not a domain id.",
24877
+ "description": "invalid_cursor: a cursor this list did not give you (each is sealed to you and your organization); pass meta.cursor unchanged.",
24754
24878
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
24755
24879
  },
24756
24880
  "401": {
@@ -24773,30 +24897,92 @@ const openapiDocument = {
24773
24897
  }
24774
24898
  },
24775
24899
  "403": {
24776
- "description": "Authenticated caller lacks permission for the operation",
24900
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead; details.reason first_party_required means this credential is not your dashboard session or the Primitive CLI.",
24901
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
24902
+ },
24903
+ "429": {
24904
+ "description": "Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.",
24905
+ "headers": {
24906
+ "Retry-After": {
24907
+ "schema": { "type": "integer" },
24908
+ "description": "Seconds to wait before retrying"
24909
+ },
24910
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
24911
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
24912
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
24913
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
24914
+ },
24777
24915
  "content": { "application/json": {
24778
24916
  "schema": { "$ref": "#/components/schemas/ErrorResponse" },
24779
24917
  "example": {
24780
24918
  "success": false,
24781
24919
  "error": {
24782
- "code": "forbidden",
24783
- "message": "Insufficient permissions"
24920
+ "code": "rate_limit_exceeded",
24921
+ "message": "Rate limit exceeded"
24784
24922
  }
24785
24923
  }
24786
24924
  } }
24925
+ }
24926
+ },
24927
+ "x-primitive-permission": ["mail:read"]
24928
+ } },
24929
+ "/email-rules/covering-me/{id}": { "get": {
24930
+ "operationId": "getEmailRuleCoveringMe",
24931
+ "summary": "Get a rule over your mail",
24932
+ "description": "One rule that covers one of your own mailboxes, read-only, with its current version; or one that no longer does but decided on mail at your own mailbox (covers_now false), shown as the version that last decided on your mail, without its current name. You see its name, wording, current tree, state, kind and author, and which of your own mailboxes it reaches. Its wording and tree are shown as written, so they can name other addresses (a condition on who an email is from or to). You do not see the other inboxes and domains its scope names, its saved expectations, or the keys, shares and webhooks that use it. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature. Only on your own dashboard session or the Primitive CLI.",
24933
+ "tags": ["Email Rule Oversight"],
24934
+ "security": [{ "BearerAuth": [] }],
24935
+ "parameters": [{
24936
+ "name": "id",
24937
+ "in": "path",
24938
+ "required": true,
24939
+ "description": "The rule id.",
24940
+ "schema": {
24941
+ "type": "string",
24942
+ "format": "uuid"
24943
+ }
24944
+ }],
24945
+ "responses": {
24946
+ "200": {
24947
+ "description": "Get a rule over your mail",
24948
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
24949
+ "type": "object",
24950
+ "required": ["data"],
24951
+ "properties": { "data": { "$ref": "#/components/schemas/GetEmailRuleCoveringMeResult" } }
24952
+ }] } } },
24953
+ "headers": {
24954
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
24955
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
24956
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
24957
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
24958
+ }
24787
24959
  },
24788
- "404": {
24789
- "description": "Resource not found",
24960
+ "401": {
24961
+ "description": "Invalid or missing API key",
24790
24962
  "content": { "application/json": {
24791
24963
  "schema": { "$ref": "#/components/schemas/ErrorResponse" },
24792
24964
  "example": {
24793
24965
  "success": false,
24794
24966
  "error": {
24795
- "code": "not_found",
24796
- "message": "Resource not found"
24967
+ "code": "unauthorized",
24968
+ "message": "Invalid or missing API key"
24797
24969
  }
24798
24970
  }
24799
- } }
24971
+ } },
24972
+ "headers": {
24973
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
24974
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
24975
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
24976
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
24977
+ }
24978
+ },
24979
+ "403": {
24980
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead; details.reason first_party_required means this credential is not your dashboard session or the Primitive CLI.",
24981
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
24982
+ },
24983
+ "404": {
24984
+ "description": "not_found: no rule with this id covers your mailboxes or decided on your mail.",
24985
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
24800
24986
  },
24801
24987
  "429": {
24802
24988
  "description": "Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.",
@@ -24822,32 +25008,69 @@ const openapiDocument = {
24822
25008
  } }
24823
25009
  }
24824
25010
  },
24825
- "x-primitive-permission": ["domains:read"]
25011
+ "x-primitive-permission": ["mail:read"]
24826
25012
  } },
24827
- "/managed-inbox/vanity/{name}": { "get": {
24828
- "operationId": "checkVanityInboxName",
24829
- "summary": "Check a vanity inbox name",
24830
- "description": "Whether a name can be claimed for the managed inbox (name.primitive.email): available, taken, reserved (a protected word) or invalid (5 to 30 lowercase letters and digits, starting with a letter). Any plan may check; claiming needs Power. Rate limited per organization together with claims. Needs domains:read.",
24831
- "tags": ["Domains"],
25013
+ "/email-rules/covering-me/{id}/decisions": { "get": {
25014
+ "operationId": "listEmailRuleDecisionsOnMyMail",
25015
+ "summary": "List what a rule decided about your mail",
25016
+ "description": "What a rule that covers your mailboxes, or decided on your mail, decided about mail at your own mailboxes, newest first: never about anyone else's mail. Each decision is in the words of the version that made it, which can be earlier than the current one: the questions it asked about that email (question_answers) and the columns its conditions read (parts). Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature. Only on your own dashboard session or the Primitive CLI.",
25017
+ "tags": ["Email Rule Oversight"],
24832
25018
  "security": [{ "BearerAuth": [] }],
24833
- "parameters": [{
24834
- "name": "name",
24835
- "in": "path",
24836
- "required": true,
24837
- "description": "The name to check, without the domain.",
24838
- "schema": {
24839
- "type": "string",
24840
- "minLength": 1,
24841
- "maxLength": 64
25019
+ "parameters": [
25020
+ {
25021
+ "name": "id",
25022
+ "in": "path",
25023
+ "required": true,
25024
+ "description": "The rule id.",
25025
+ "schema": {
25026
+ "type": "string",
25027
+ "format": "uuid"
25028
+ }
25029
+ },
25030
+ {
25031
+ "name": "limit",
25032
+ "in": "query",
25033
+ "required": false,
25034
+ "description": "Decisions per page, from 1 to 100; defaults to 50.",
25035
+ "schema": {
25036
+ "default": 50,
25037
+ "type": "integer",
25038
+ "minimum": 1,
25039
+ "maximum": 100
25040
+ }
25041
+ },
25042
+ {
25043
+ "name": "cursor",
25044
+ "in": "query",
25045
+ "required": false,
25046
+ "description": "meta.cursor from the preceding page. Omit for the first page.",
25047
+ "schema": {
25048
+ "type": "string",
25049
+ "maxLength": 200
25050
+ }
25051
+ },
25052
+ {
25053
+ "name": "matched",
25054
+ "in": "query",
25055
+ "required": false,
25056
+ "description": "true for matches only, false for the rest.",
25057
+ "schema": {
25058
+ "description": "Only decisions that matched (true) or did not (false).",
25059
+ "type": "string",
25060
+ "enum": ["true", "false"]
25061
+ }
24842
25062
  }
24843
- }],
25063
+ ],
24844
25064
  "responses": {
24845
25065
  "200": {
24846
- "description": "Check a vanity inbox name",
25066
+ "description": "List what a rule decided about your mail",
24847
25067
  "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
24848
25068
  "type": "object",
24849
- "required": ["data"],
24850
- "properties": { "data": { "$ref": "#/components/schemas/CheckVanityInboxNameResult" } }
25069
+ "required": ["data", "meta"],
25070
+ "properties": {
25071
+ "data": { "$ref": "#/components/schemas/ListEmailRuleDecisionsOnMyMailResult" },
25072
+ "meta": { "$ref": "#/components/schemas/CursorPageMeta" }
25073
+ }
24851
25074
  }] } } },
24852
25075
  "headers": {
24853
25076
  "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
@@ -24857,7 +25080,202 @@ const openapiDocument = {
24857
25080
  }
24858
25081
  },
24859
25082
  "400": {
24860
- "description": "validation_error: the name parameter is missing or too long.",
25083
+ "description": "validation_error: a cursor not returned in meta.cursor.",
25084
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
25085
+ },
25086
+ "401": {
25087
+ "description": "Invalid or missing API key",
25088
+ "content": { "application/json": {
25089
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
25090
+ "example": {
25091
+ "success": false,
25092
+ "error": {
25093
+ "code": "unauthorized",
25094
+ "message": "Invalid or missing API key"
25095
+ }
25096
+ }
25097
+ } },
25098
+ "headers": {
25099
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
25100
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
25101
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
25102
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
25103
+ }
25104
+ },
25105
+ "403": {
25106
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead; details.reason first_party_required means this credential is not your dashboard session or the Primitive CLI. grant_forbidden with details.reason mail_depth: your access to mail shows headers only, and what a rule decided is read from what emails say.",
25107
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
25108
+ },
25109
+ "404": {
25110
+ "description": "not_found: no rule with this id covers your mailboxes or decided on your mail.",
25111
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
25112
+ },
25113
+ "429": {
25114
+ "description": "Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.",
25115
+ "headers": {
25116
+ "Retry-After": {
25117
+ "schema": { "type": "integer" },
25118
+ "description": "Seconds to wait before retrying"
25119
+ },
25120
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
25121
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
25122
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
25123
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
25124
+ },
25125
+ "content": { "application/json": {
25126
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
25127
+ "example": {
25128
+ "success": false,
25129
+ "error": {
25130
+ "code": "rate_limit_exceeded",
25131
+ "message": "Rate limit exceeded"
25132
+ }
25133
+ }
25134
+ } }
25135
+ }
25136
+ },
25137
+ "x-primitive-permission": ["mail:read"]
25138
+ } },
25139
+ "/domains/{id}/agent-authorization": { "get": {
25140
+ "operationId": "getDomainAgentAuthorization",
25141
+ "summary": "Get a domain's Agent Authorization status",
25142
+ "description": "Whether the domain publishes an Agent Authorization (`_agents`) TXT record that lets agents on Primitive email it, read from DNS with the same engine the send path uses (an answer may be up to five minutes old): published (a record authorizes them), published_unauthorized (a record exists but does not), not_set_up (no record, the default) or inconclusive (the lookup could not tell; retry). The domain is one of the organization's, verified or pending; a credential confined to some mailboxes sees only the domains its scope covers. Needs domains:read.",
25143
+ "tags": ["Domains"],
25144
+ "security": [{ "BearerAuth": [] }],
25145
+ "parameters": [{
25146
+ "name": "id",
25147
+ "in": "path",
25148
+ "required": true,
25149
+ "description": "The domain id from listDomains.",
25150
+ "schema": {
25151
+ "type": "string",
25152
+ "format": "uuid"
25153
+ }
25154
+ }],
25155
+ "responses": {
25156
+ "200": {
25157
+ "description": "Get a domain's Agent Authorization status",
25158
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
25159
+ "type": "object",
25160
+ "required": ["data"],
25161
+ "properties": { "data": { "$ref": "#/components/schemas/GetDomainAgentAuthorizationResult" } }
25162
+ }] } } },
25163
+ "headers": {
25164
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
25165
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
25166
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
25167
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
25168
+ }
25169
+ },
25170
+ "400": {
25171
+ "description": "validation_error: the id is not a domain id.",
25172
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
25173
+ },
25174
+ "401": {
25175
+ "description": "Invalid or missing API key",
25176
+ "content": { "application/json": {
25177
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
25178
+ "example": {
25179
+ "success": false,
25180
+ "error": {
25181
+ "code": "unauthorized",
25182
+ "message": "Invalid or missing API key"
25183
+ }
25184
+ }
25185
+ } },
25186
+ "headers": {
25187
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
25188
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
25189
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
25190
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
25191
+ }
25192
+ },
25193
+ "403": {
25194
+ "description": "Authenticated caller lacks permission for the operation",
25195
+ "content": { "application/json": {
25196
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
25197
+ "example": {
25198
+ "success": false,
25199
+ "error": {
25200
+ "code": "forbidden",
25201
+ "message": "Insufficient permissions"
25202
+ }
25203
+ }
25204
+ } }
25205
+ },
25206
+ "404": {
25207
+ "description": "Resource not found",
25208
+ "content": { "application/json": {
25209
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
25210
+ "example": {
25211
+ "success": false,
25212
+ "error": {
25213
+ "code": "not_found",
25214
+ "message": "Resource not found"
25215
+ }
25216
+ }
25217
+ } }
25218
+ },
25219
+ "429": {
25220
+ "description": "Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.",
25221
+ "headers": {
25222
+ "Retry-After": {
25223
+ "schema": { "type": "integer" },
25224
+ "description": "Seconds to wait before retrying"
25225
+ },
25226
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
25227
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
25228
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
25229
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
25230
+ },
25231
+ "content": { "application/json": {
25232
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
25233
+ "example": {
25234
+ "success": false,
25235
+ "error": {
25236
+ "code": "rate_limit_exceeded",
25237
+ "message": "Rate limit exceeded"
25238
+ }
25239
+ }
25240
+ } }
25241
+ }
25242
+ },
25243
+ "x-primitive-permission": ["domains:read"]
25244
+ } },
25245
+ "/managed-inbox/vanity/{name}": { "get": {
25246
+ "operationId": "checkVanityInboxName",
25247
+ "summary": "Check a vanity inbox name",
25248
+ "description": "Whether a name can be claimed for the managed inbox (name.primitive.email): available, taken, reserved (a protected word) or invalid (5 to 30 lowercase letters and digits, starting with a letter). Any plan may check; claiming needs Power. Rate limited per organization together with claims. Needs domains:read.",
25249
+ "tags": ["Domains"],
25250
+ "security": [{ "BearerAuth": [] }],
25251
+ "parameters": [{
25252
+ "name": "name",
25253
+ "in": "path",
25254
+ "required": true,
25255
+ "description": "The name to check, without the domain.",
25256
+ "schema": {
25257
+ "type": "string",
25258
+ "minLength": 1,
25259
+ "maxLength": 64
25260
+ }
25261
+ }],
25262
+ "responses": {
25263
+ "200": {
25264
+ "description": "Check a vanity inbox name",
25265
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
25266
+ "type": "object",
25267
+ "required": ["data"],
25268
+ "properties": { "data": { "$ref": "#/components/schemas/CheckVanityInboxNameResult" } }
25269
+ }] } } },
25270
+ "headers": {
25271
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
25272
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
25273
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
25274
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
25275
+ }
25276
+ },
25277
+ "400": {
25278
+ "description": "validation_error: the name parameter is missing or too long.",
24861
25279
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
24862
25280
  },
24863
25281
  "401": {
@@ -32268,7 +32686,7 @@ const openapiDocument = {
32268
32686
  "summary": "Revoke a credential",
32269
32687
  "tags": ["Credentials"],
32270
32688
  "security": [{ "BearerAuth": [] }],
32271
- "description": "Revoke one credential by kind and id. A Function key is rotated, never revoked (409 rotate_instead). An API key is revoked only by a person whose own access covers the key's access (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. What the credential set up or owns is listed in standing_objects for review, and for an API key also what it carries on for the keys it was rotated from. An organization-owned object (org_owned true) keeps working; any other keeps working only while someone can still act for it, such as a key of the same rotation that still works, or whoever it was given to, except that a block filter an app made keeps blocking, within the reach the app had, unless the person revoking chose to stop it. An API key (kind org_key or connected_key) that is already revoked, and whose record is kept, answers 200 with already_revoked true and revoked 0, to a caller who could have revoked it; anyone else gets the answer they would get for a key that does not exist. The person's own Primitive CLI login revokes an API key (kind org_key) as the person, with no step-up unless the organization's credential policy sets cli_writes_require_approval (then it takes a keys.create step-up); every other kind (a connected key named as org_key included, 403 grant_forbidden target_not_covered), and every other app even with a step-up, is refused outside the dashboard (403). Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:manage. API keys, agent connection keys and agents are refused.",
32689
+ "description": "Revoke one credential by kind and id. A Function key is rotated, never revoked (409 rotate_instead). An API key is revoked by the person who holds it, whatever it reaches, or by a person whose own access covers the key's access (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. Revoking an API key also revokes the keys it replaced in its rotation that are still in their overlap and that you may revoke (predecessors_revoked_count; predecessors_left counts the rest), so its webhooks stop at once; the keys that replaced it carry its work on. Revoking a key already revoked does the same for those keys. What the credential set up or owns is listed in standing_objects for review, and for an API key also what it carries on for the keys it was rotated from, each saying whether anything still acts for it (still_acting, with the key in acting_key_id) and, for a webhook, whether it is still delivering. An organization-owned object (org_owned true) keeps working; any other keeps working only while someone can still act for it, such as a key of the same rotation that still works, or whoever it was given to, except that a block filter an app made keeps blocking, within the reach the app had, unless the person revoking chose to stop it. An API key (kind org_key or connected_key) that is already revoked, and whose record is kept, answers 200 with already_revoked true, to a caller who could have revoked it, and revoked counts only the keys it replaced that were revoked now; anyone else gets the answer they would get for a key that does not exist. The person's own Primitive CLI login revokes an API key (kind org_key) as the person, with no step-up unless the organization's credential policy sets cli_writes_require_approval (then it takes a keys.create step-up); every other kind (a connected key named as org_key included, 403 grant_forbidden target_not_covered), and every other app even with a step-up, is refused outside the dashboard (403). Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:manage. API keys, agent connection keys and agents are refused.",
32272
32690
  "x-primitive-person-only": { "access": "session_or_cli" },
32273
32691
  "parameters": [{
32274
32692
  "name": "Primitive-Step-Up",
@@ -33382,7 +33800,7 @@ const openapiDocument = {
33382
33800
  "summary": "Rotate an API key",
33383
33801
  "tags": ["Credentials"],
33384
33802
  "security": [{ "BearerAuth": [] }],
33385
- "description": "Mint a successor with a new id and the same name, grant, expiry and conditions, and return its secret once. The old key keeps working for overlap_seconds, then stops: 24 hours when overlap_seconds is omitted, at most 7 days, never past its own expires_at, and at once with 0 (which is how to rotate a key whose secret leaked). previous_key_expires_at says when the old key stops. Rotating does not extend a key: the successor keeps the old expiry unless expires_at gives it a new one. That needs keys:manage (403 forbidden otherwise), is held to the credential policy (400 when past its maximum lifetime, with the latest expiry it allows now in the message), and someone limited to their own keys can only keep or bring it forward (403 grant_forbidden, details.reason own_key_tighten_only); the old key then never outlives the new one. The person rotating must cover the key's access (403 grant_forbidden, details.reason target_not_covered), and a key an owner holds is rotated by an owner only (403 forbidden, details.reason owner_key_rotation), since rotating hands over the new secret. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules) unless the organization's credential policy sets cli_writes_require_approval; the Primitive app's token is refused, even with one. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up (unless the organization's credential policy sets cli_writes_require_approval), or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role, or the client, could not do it anyway). The person's role must hold keys:manage. API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.",
33803
+ "description": "Mint a successor with a new id and the same name, grant, expiry and conditions, and return its secret once. The old key keeps working for overlap_seconds, then stops: 24 hours when overlap_seconds is omitted, at most 7 days, never past its own expires_at, and at once with 0 (which is how to rotate a key whose secret leaked). previous_key_expires_at says when the old key stops. Revoking the successor during the overlap also revokes the old key when you may revoke that one too; the revoke's predecessors_left counts any left working. Rotating does not extend a key: the successor keeps the old expiry unless expires_at gives it a new one. That needs keys:manage (403 forbidden otherwise), is held to the credential policy (400 when past its maximum lifetime, with the latest expiry it allows now in the message), and someone limited to their own keys can only keep or bring it forward (403 grant_forbidden, details.reason own_key_tighten_only); the old key then never outlives the new one. The person rotating must cover the key's access (403 grant_forbidden, details.reason target_not_covered), and a key an owner holds is rotated by an owner only (403 forbidden, details.reason owner_key_rotation), since rotating hands over the new secret. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules) unless the organization's credential policy sets cli_writes_require_approval; the Primitive app's token is refused, even with one. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up (unless the organization's credential policy sets cli_writes_require_approval), or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role, or the client, could not do it anyway). The person's role must hold keys:manage. API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.",
33386
33804
  "x-primitive-person-only": {
33387
33805
  "access": "step_up",
33388
33806
  "step_up_purpose": "keys.create",
@@ -33549,7 +33967,7 @@ const openapiDocument = {
33549
33967
  "summary": "Change an API key",
33550
33968
  "tags": ["Credentials"],
33551
33969
  "security": [{ "BearerAuth": [] }],
33552
- "description": "Rename a key, change its expiry, its grant or its conditions, or suspend it. A later expires_at extends the key, which is how to keep a key working past its expiry (rotating keeps it, unless the rotation gives the new key an expires_at): it is held to the credential policy (400 when past its maximum lifetime, with the latest expiry it allows now in the message), and like any loosening needs the person to be allowed to create a key with the key's access. An expired key's expiry cannot be changed (409 conflict, details.reason key_expired); make a new key instead. Any change needs the person's own access to cover the key's access now (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. A grant change is a new version of the key and is held to the same rules as creating one (a custom grant that does not say which mail it reaches is refused with details.reason mail_scope_required), so it takes the same step-up. It replaces the whole grant: send expected_grant_ref (grant.ref from GET /v1/keys/{id}) to have it refused (409 conflict, details.reason grant_changed) if the grant changed since that read; a grant change sent without it answers grant_unguarded: true. On a key limited to selected resources (grant.resources), a grant change must drop the limits on purpose with a custom grant and grant.resources {}; one that leaves them out is refused (409 conflict, details.reason key_has_resource_limits) and nothing changes. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules) unless the organization's credential policy sets cli_writes_require_approval; the Primitive app's token is refused, even with one. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up (unless the organization's credential policy sets cli_writes_require_approval), or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role, or the client, could not do it anyway). The person's role must hold keys:manage. API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.",
33970
+ "description": "Rename a key, change its expiry, its grant or its conditions, or suspend it. A later expires_at extends the key, which is how to keep a key working past its expiry (rotating keeps it, unless the rotation gives the new key an expires_at): it is held to the credential policy (400 when past its maximum lifetime, with the latest expiry it allows now in the message), and like any loosening needs the person to be allowed to create a key with the key's access. An expired key's expiry cannot be changed (409 conflict, details.reason key_expired); make a new key instead. Any change needs the person's own access to cover the key's access now (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. The one exception is suspending a key (suspended true with no other change; expected_grant_ref may accompany it), which the person who holds it may do whatever the key reaches, calling as themself rather than through another app; lifting a suspension is a change like any other. A grant change is a new version of the key and is held to the same rules as creating one (a custom grant that does not say which mail it reaches is refused with details.reason mail_scope_required), so it takes the same step-up. It replaces the whole grant: send expected_grant_ref (grant.ref from GET /v1/keys/{id}) to have it refused (409 conflict, details.reason grant_changed) if the grant changed since that read; a grant change sent without it answers grant_unguarded: true. On a key limited to selected resources (grant.resources), a grant change must drop the limits on purpose with a custom grant and grant.resources {}; one that leaves them out is refused (409 conflict, details.reason key_has_resource_limits) and nothing changes. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules) unless the organization's credential policy sets cli_writes_require_approval; the Primitive app's token is refused, even with one. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up (unless the organization's credential policy sets cli_writes_require_approval), or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role, or the client, could not do it anyway). The person's role must hold keys:manage. API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.",
33553
33971
  "x-primitive-person-only": {
33554
33972
  "access": "step_up",
33555
33973
  "step_up_purpose": "keys.create",
@@ -35310,7 +35728,7 @@ const openapiDocument = {
35310
35728
  "get": {
35311
35729
  "operationId": "listRoles",
35312
35730
  "summary": "List roles",
35313
- "description": "Every role in the organization (built-in, template copies and custom roles), with how many members and groups hold each, the templates not yet copied, and whether roles can be assigned now. `unassigned` describes role restricted on a member with no role_id, which is not a role and is never in `roles`. Any person (a session or OAuth token); API keys are refused.",
35731
+ "description": "Every role in the organization (built-in, template copies and custom roles), with how many members and groups hold each, the templates not yet copied, and whether roles can be assigned now. `unassigned` describes role restricted on a member with no role_id, which is not a role and is never in `roles`. Needs members:read (owners, admins, members and roles that grant it); API keys are refused.",
35314
35732
  "tags": ["Roles and members"],
35315
35733
  "security": [{ "BearerAuth": [] }],
35316
35734
  "responses": {
@@ -36111,7 +36529,7 @@ const openapiDocument = {
36111
36529
  "get": {
36112
36530
  "operationId": "getAccessMode",
36113
36531
  "summary": "Get the mailbox access mode",
36114
- "description": "Whole-org or delegated mailbox access. Holders of roles:assign also get the preview of a switch (every item that needs a decision, each with the decision_options it takes, worded for what the switch does) and the decisions made, plus notices: member and group domain entries that delegated mode keeps off personal mailboxes, each with how many it leaves out. Notices are informational, never decided, and never hold the switch (notices_truncated past 500, notices_unavailable when they could not be read). In whole-org mode it also lists narrowed_agents: every live agent connection the switch narrows to an own-address key (personal agents of members, and shared agents), informational in the same way (narrowed_agents_truncated past 500, narrowed_agents_unavailable when they could not be read). Any person; API keys are refused.",
36532
+ "description": "Whole-org or delegated mailbox access. Holders of roles:assign also get the preview of a switch (every item that needs a decision, each with the decision_options it takes, worded for what the switch does) and the decisions made, plus notices: member and group domain entries that delegated mode keeps off personal mailboxes, each with how many it leaves out. Notices are informational, never decided, and never hold the switch (notices_truncated past 500, notices_unavailable when they could not be read). In whole-org mode it also lists narrowed_agents: every live agent connection the switch narrows to an own-address key (personal agents of members, and shared agents), informational in the same way (narrowed_agents_truncated past 500, narrowed_agents_unavailable when they could not be read). Needs members:read (owners, admins, members and roles that grant it); API keys are refused.",
36115
36533
  "tags": ["Roles and members"],
36116
36534
  "security": [{ "BearerAuth": [] }],
36117
36535
  "responses": {
@@ -37365,7 +37783,7 @@ const openapiDocument = {
37365
37783
  "/access-suspensions/{id}/decision": { "post": {
37366
37784
  "operationId": "decideSuspension",
37367
37785
  "summary": "Decide a suspension",
37368
- "description": "Keep, narrow or revoke one suspended or flagged item. Keeping it, and revoking an API key, needs the decider to cover it (revoking an uncovered key is refused with `grant_forbidden`, `target_not_covered`). Keeping an API key rotated (`rotate: true`, other than the shared Function key) also revokes its older secrets still in a rotation overlap, and needs keys:manage on both the credential and the person: without it, or if the decider lost members:manage meanwhile, it is refused with `grant_forbidden` (`keys_manage_required` or `actor_access_changed`) and nothing is rotated. A rule share paused by a role change (`email_rule_share`) takes keep, which leaves it paused, or revoke, which ends the share and needs confirm: true (the person it was shared with loses the emails already shared; without it, 409 `confirmation_required`, as for revoking a filter). Neither of the share's two people may keep it (`grant_forbidden`, `share_decision_forbidden`). Access a person gave on their own mailbox (`mailbox_access`) takes keep or revoke, and both need delegations:manage; revoke removes only what that person gave on that mailbox. Nobody the access reaches, nor the person who gave it, may keep it (`grant_forbidden`, `mailbox_access_decision_forbidden`). Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `suspensions.decide` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs members:manage. API keys are refused.",
37786
+ "description": "Keep, narrow or revoke one suspended or flagged item. Keeping it needs the decider to cover it, and so does revoking an API key unless the decider holds it and decides as themself rather than through another app (revoking an uncovered key someone else holds is refused with `grant_forbidden`, `target_not_covered`). Keeping an API key rotated (`rotate: true`, other than the shared Function key) also revokes its older secrets still in a rotation overlap, and needs keys:manage on both the credential and the person: without it, or if the decider lost members:manage meanwhile, it is refused with `grant_forbidden` (`keys_manage_required` or `actor_access_changed`) and nothing is rotated. A rule share paused by a role change (`email_rule_share`) takes keep, which leaves it paused, or revoke, which ends the share and needs confirm: true (the person it was shared with loses the emails already shared; without it, 409 `confirmation_required`, as for revoking a filter). Neither of the share's two people may keep it (`grant_forbidden`, `share_decision_forbidden`). Access a person gave on their own mailbox (`mailbox_access`) takes keep or revoke, and both need delegations:manage; revoke removes only what that person gave on that mailbox. Nobody the access reaches, nor the person who gave it, may keep it (`grant_forbidden`, `mailbox_access_decision_forbidden`). Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `suspensions.decide` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs members:manage. API keys are refused.",
37369
37787
  "tags": ["Roles and members"],
37370
37788
  "security": [{ "BearerAuth": [] }],
37371
37789
  "parameters": [
@@ -39289,6 +39707,7 @@ const openapiDocument = {
39289
39707
  "email_rule_author_unresolvable",
39290
39708
  "email_rule_compiler_unavailable",
39291
39709
  "email_rule_conflict",
39710
+ "email_rule_look_alikes_not_written",
39292
39711
  "email_rule_not_compiled",
39293
39712
  "email_rule_request_limit",
39294
39713
  "email_rule_request_not_pending",
@@ -39304,6 +39723,7 @@ const openapiDocument = {
39304
39723
  "entitlement",
39305
39724
  "expectations_failed",
39306
39725
  "expectations_undecided",
39726
+ "expectations_unreadable",
39307
39727
  "expired",
39308
39728
  "feature_not_enabled",
39309
39729
  "fresh_send_not_authorized",
@@ -39430,6 +39850,7 @@ const openapiDocument = {
39430
39850
  "semantic_search_not_enabled",
39431
39851
  "send_failed",
39432
39852
  "sender_not_authorized",
39853
+ "sending_ip_warmup_cap",
39433
39854
  "sent_email_tail_unavailable",
39434
39855
  "sent_mail_search_unavailable",
39435
39856
  "shopping_binding_conflict",
@@ -42764,12 +43185,12 @@ const openapiDocument = {
42764
43185
  },
42765
43186
  "hold": {
42766
43187
  "type": "object",
42767
- "description": "Only on a held scheduled send: its OAuth grant was revoked after refresh token reuse, so it waits until released (POST /sent-emails/{id}/release) or canceled; canceled after 7 days. Status stays scheduled.",
43188
+ "description": "Only on a scheduled send that is not going out yet; status stays scheduled. credential_reuse_hold: its OAuth grant was revoked after refresh token reuse, so it waits until released (POST /sent-emails/{id}/release) or canceled; canceled after 7 days. sending_ip_warmup_cap: your dedicated sending address is at today's warm-up limit or paused, so it goes out on its own within the hour after the limit resets at 00:00 UTC (fails with sending_ip_warmup_cap after 7 such holds); no release needed. On sent-email reads and conversation messages.",
42768
43189
  "required": ["reason", "since"],
42769
43190
  "properties": {
42770
43191
  "reason": {
42771
43192
  "type": "string",
42772
- "description": "Open: credential_reuse_hold."
43193
+ "description": "Open: credential_reuse_hold, sending_ip_warmup_cap."
42773
43194
  },
42774
43195
  "since": {
42775
43196
  "type": ["string", "null"],
@@ -43359,12 +43780,12 @@ const openapiDocument = {
43359
43780
  },
43360
43781
  "hold": {
43361
43782
  "type": "object",
43362
- "description": "Only on a held scheduled send: its OAuth grant was revoked after refresh token reuse, so it waits until released (POST /sent-emails/{id}/release) or canceled; canceled after 7 days. Status stays scheduled.",
43783
+ "description": "Only on a scheduled send that is not going out yet; status stays scheduled. credential_reuse_hold: its OAuth grant was revoked after refresh token reuse, so it waits until released (POST /sent-emails/{id}/release) or canceled; canceled after 7 days. sending_ip_warmup_cap: your dedicated sending address is at today's warm-up limit or paused, so it goes out on its own within the hour after the limit resets at 00:00 UTC (fails with sending_ip_warmup_cap after 7 such holds); no release needed. On sent-email reads and conversation messages.",
43363
43784
  "required": ["reason", "since"],
43364
43785
  "properties": {
43365
43786
  "reason": {
43366
43787
  "type": "string",
43367
- "description": "Open: credential_reuse_hold."
43788
+ "description": "Open: credential_reuse_hold, sending_ip_warmup_cap."
43368
43789
  },
43369
43790
  "since": {
43370
43791
  "type": ["string", "null"],
@@ -43808,12 +44229,12 @@ const openapiDocument = {
43808
44229
  },
43809
44230
  "hold": {
43810
44231
  "type": "object",
43811
- "description": "Only on a held scheduled send: its OAuth grant was revoked after refresh token reuse, so it waits until released (POST /sent-emails/{id}/release) or canceled; canceled after 7 days. Status stays scheduled.",
44232
+ "description": "Only on a scheduled send that is not going out yet; status stays scheduled. credential_reuse_hold: its OAuth grant was revoked after refresh token reuse, so it waits until released (POST /sent-emails/{id}/release) or canceled; canceled after 7 days. sending_ip_warmup_cap: your dedicated sending address is at today's warm-up limit or paused, so it goes out on its own within the hour after the limit resets at 00:00 UTC (fails with sending_ip_warmup_cap after 7 such holds); no release needed. On sent-email reads and conversation messages.",
43812
44233
  "required": ["reason", "since"],
43813
44234
  "properties": {
43814
44235
  "reason": {
43815
44236
  "type": "string",
43816
- "description": "Open: credential_reuse_hold."
44237
+ "description": "Open: credential_reuse_hold, sending_ip_warmup_cap."
43817
44238
  },
43818
44239
  "since": {
43819
44240
  "type": ["string", "null"],
@@ -46164,7 +46585,10 @@ const openapiDocument = {
46164
46585
  "type": "string",
46165
46586
  "format": "uuid"
46166
46587
  },
46167
- "endpoint_url": { "type": "string" },
46588
+ "endpoint_url": {
46589
+ "type": "string",
46590
+ "description": "Where it was sent: a webhook's scheme and host, followed by \"/... (path and query hidden)\" when its URL has more (GET /endpoints/{id} has the whole URL), function://<id> for a Function, or pull: for a pull subscription."
46591
+ },
46168
46592
  "status": {
46169
46593
  "type": "string",
46170
46594
  "enum": [
@@ -46217,7 +46641,7 @@ const openapiDocument = {
46217
46641
  },
46218
46642
  "response_body": {
46219
46643
  "type": ["string", "null"],
46220
- "description": "The first 2 KB of the body the endpoint answered this attempt with, so a handler that answers 200 and ignores a delivery can be seen doing so. It is whatever came back, which may echo what was sent. Null when no answer came back, and cleared when the message's content is discarded or the sent email is deleted. Withheld from a caller who cannot read message content, since an answer can echo what was sent."
46644
+ "description": "The first 2 KB of the body the endpoint answered this attempt with, so a handler that answers 200 and ignores a delivery can be seen doing so. It is whatever came back, which may echo what was sent. Null when no answer came back, and cleared when the message's content is discarded or the sent email is deleted. Withheld from a caller who cannot read message content, since an answer can echo what was sent. Null for a redirect (3xx), whose body repeats its Location; otherwise the webhook URL's path and query are hidden wherever it repeats them."
46221
46645
  },
46222
46646
  "latest_attempt": {
46223
46647
  "type": "boolean",
@@ -49427,7 +49851,7 @@ const openapiDocument = {
49427
49851
  "questions": {
49428
49852
  "type": "array",
49429
49853
  "items": { "type": "string" },
49430
- "description": "Yes/no questions asked once the conditions match; all must be yes."
49854
+ "description": "Yes/no questions asked once the conditions match, over the email's text and attachment names, never attachment contents. A grant rule needs every one yes; a do_not_share rule matches on any yes."
49431
49855
  }
49432
49856
  },
49433
49857
  "required": [
@@ -49491,7 +49915,7 @@ const openapiDocument = {
49491
49915
  "warnings": {
49492
49916
  "type": "array",
49493
49917
  "items": { "type": "string" },
49494
- "description": "Parts of the description the column contract cannot express, and how the rule handles them. When the rule reads automated or from_mailing_list, it also has a note on what that column reads and what it misses. When the rule has no questions and its conditions hold for all of the caller's recent classified mail (what a test of it covers by default), or for at least 90% of at least 20 emails, the first warning says so. That count is not made for a caller whose access shows headers only, or who could not run a test of the rule, and may also be skipped when the compile ran long or the count fails."
49918
+ "description": "Parts of the description the column contract cannot express, and how the rule handles them. It also has notes on reading automated, from_mailing_list or sender_relationship, a sender address the description does not give, and a grant worded as a prohibition. When the rule has no questions and its conditions hold for all of the caller's recent classified mail (what a test of it covers by default), or for at least 90% of at least 20 emails, the first warning says so. That count is not made for a caller whose access shows headers only, or who could not run a test of the rule, and may also be skipped when the compile ran long or the count fails."
49495
49919
  },
49496
49920
  "compiled_by": {
49497
49921
  "type": "string",
@@ -49552,6 +49976,67 @@ const openapiDocument = {
49552
49976
  "message"
49553
49977
  ]
49554
49978
  },
49979
+ "EmailRuleSampleJudgment": {
49980
+ "type": "object",
49981
+ "properties": {
49982
+ "index": {
49983
+ "type": "integer",
49984
+ "minimum": 0
49985
+ },
49986
+ "sample_id": {
49987
+ "type": ["string", "null"],
49988
+ "format": "uuid"
49989
+ },
49990
+ "label": { "type": ["string", "null"] },
49991
+ "expected": {
49992
+ "type": "string",
49993
+ "enum": ["match", "no_match"]
49994
+ },
49995
+ "outcome": {
49996
+ "type": "string",
49997
+ "enum": [
49998
+ "matched",
49999
+ "near_miss",
50000
+ "no_match",
50001
+ "not_decided"
50002
+ ]
50003
+ },
50004
+ "status": {
50005
+ "type": "string",
50006
+ "enum": [
50007
+ "passed",
50008
+ "failed",
50009
+ "undecided"
50010
+ ]
50011
+ },
50012
+ "question_answers": {
50013
+ "type": "array",
50014
+ "items": {
50015
+ "type": "object",
50016
+ "properties": {
50017
+ "question": { "type": "string" },
50018
+ "answer": { "type": ["boolean", "null"] },
50019
+ "probability": { "type": ["number", "null"] },
50020
+ "error_class": { "type": "string" }
50021
+ },
50022
+ "required": [
50023
+ "question",
50024
+ "answer",
50025
+ "probability"
50026
+ ]
50027
+ }
50028
+ }
50029
+ },
50030
+ "required": [
50031
+ "index",
50032
+ "sample_id",
50033
+ "label",
50034
+ "expected",
50035
+ "outcome",
50036
+ "status",
50037
+ "question_answers"
50038
+ ]
50039
+ },
49555
50040
  "EmailRuleTreeInput": {
49556
50041
  "type": "object",
49557
50042
  "properties": {
@@ -49570,7 +50055,7 @@ const openapiDocument = {
49570
50055
  "description": "A condition {column, op, value?, min_confidence?} (see GET /email-rules/vocabulary) or {all: [...]}, {any: [...]}, {not: ...}."
49571
50056
  },
49572
50057
  "questions": {
49573
- "description": "Yes/no questions asked once the conditions match; all must be yes. Left out, none.",
50058
+ "description": "Yes/no questions asked once the conditions match, over the email's text and attachment names, never attachment contents. A grant rule needs every one yes; a do_not_share rule matches on any yes. Left out, none.",
49574
50059
  "type": "array",
49575
50060
  "items": { "type": "string" }
49576
50061
  }
@@ -49609,6 +50094,107 @@ const openapiDocument = {
49609
50094
  "additionalProperties": false,
49610
50095
  "description": "For a selected rule: inboxes, and domains (each address at them the author reads); each one you can read."
49611
50096
  },
50097
+ "EmailRuleExpectationSampleInput": {
50098
+ "type": "object",
50099
+ "properties": {
50100
+ "expected": {
50101
+ "type": "string",
50102
+ "enum": ["match", "no_match"]
50103
+ },
50104
+ "label": {
50105
+ "type": ["string", "null"],
50106
+ "minLength": 1,
50107
+ "maxLength": 200,
50108
+ "pattern": "^[^\\u0000]*$",
50109
+ "description": "What the email stands for, such as a personal medical bill."
50110
+ },
50111
+ "email": { "$ref": "#/components/schemas/EmailRuleSavedSampleEmail" },
50112
+ "id": {
50113
+ "type": "string",
50114
+ "format": "uuid"
50115
+ },
50116
+ "created_at": { "type": "string" }
50117
+ },
50118
+ "required": ["expected", "email"],
50119
+ "additionalProperties": false
50120
+ },
50121
+ "EmailRuleSavedSampleEmail": {
50122
+ "type": "object",
50123
+ "properties": {
50124
+ "from": {
50125
+ "type": "object",
50126
+ "properties": {
50127
+ "address": {
50128
+ "type": "string",
50129
+ "maxLength": 254,
50130
+ "format": "email",
50131
+ "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
50132
+ },
50133
+ "name": {
50134
+ "type": "string",
50135
+ "maxLength": 200,
50136
+ "pattern": "^[^\\u0000]*$"
50137
+ }
50138
+ },
50139
+ "required": ["address"]
50140
+ },
50141
+ "to": {
50142
+ "minItems": 1,
50143
+ "maxItems": 10,
50144
+ "type": "array",
50145
+ "items": {
50146
+ "type": "string",
50147
+ "maxLength": 254,
50148
+ "format": "email",
50149
+ "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
50150
+ }
50151
+ },
50152
+ "cc": {
50153
+ "maxItems": 10,
50154
+ "type": "array",
50155
+ "items": {
50156
+ "type": "string",
50157
+ "maxLength": 254,
50158
+ "format": "email",
50159
+ "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
50160
+ }
50161
+ },
50162
+ "subject": {
50163
+ "type": "string",
50164
+ "maxLength": 998,
50165
+ "pattern": "^[^\\u0000]*$"
50166
+ },
50167
+ "text": {
50168
+ "type": "string",
50169
+ "minLength": 1,
50170
+ "maxLength": 4e3,
50171
+ "pattern": "^[^\\u0000]*$"
50172
+ },
50173
+ "attachment_types": {
50174
+ "maxItems": 10,
50175
+ "type": "array",
50176
+ "items": {
50177
+ "type": "string",
50178
+ "minLength": 1,
50179
+ "maxLength": 255,
50180
+ "pattern": "^[^\\u0000]*$"
50181
+ }
50182
+ },
50183
+ "automated": { "type": "boolean" },
50184
+ "from_mailing_list": { "type": "boolean" },
50185
+ "spam_score": {
50186
+ "type": "number",
50187
+ "minimum": -1e3,
50188
+ "maximum": 1e3
50189
+ }
50190
+ },
50191
+ "required": [
50192
+ "from",
50193
+ "to",
50194
+ "text"
50195
+ ],
50196
+ "description": "A made-up email, as POST /email-rules/test-sample takes it."
50197
+ },
49612
50198
  "TestEmailRuleRequest": {
49613
50199
  "type": "object",
49614
50200
  "properties": {
@@ -49668,10 +50254,15 @@ const openapiDocument = {
49668
50254
  "type": "string",
49669
50255
  "format": "uuid"
49670
50256
  }
50257
+ },
50258
+ "samples": {
50259
+ "maxItems": 20,
50260
+ "type": "array",
50261
+ "items": { "$ref": "#/components/schemas/EmailRuleExpectationSampleInput" }
49671
50262
  }
49672
50263
  },
49673
50264
  "additionalProperties": false,
49674
- "description": "Emails the rule must match and must not, at most 100 in all, always tested whatever their age: added to email_ids, or to the recent window, which gives up a place for each. The answer's expectations judge each."
50265
+ "description": "Emails the rule must match and must not (at most 100), always tested whatever their age, and made-up emails (samples, at most 20). Given, it replaces a saved rule's own."
49675
50266
  }
49676
50267
  },
49677
50268
  "additionalProperties": false
@@ -49818,7 +50409,7 @@ const openapiDocument = {
49818
50409
  "skipped",
49819
50410
  "not_found"
49820
50411
  ],
49821
- "description": "near_miss: Not matched, but nearly: its questions did not all say yes, or it needs several parts and exactly one failed while at least one part that held is not an exclusion (a top-level not, or is_false). skipped: an email you can see that cannot be tested; skipped_reason says why. not_found: no such email, or not one the rule can read (or not settled, or awaiting sender authentication)."
50412
+ "description": "near_miss: Not matched, but nearly: its conditions held and its questions did not match it, or it needs several parts and exactly one failed while at least one part that held is not an exclusion (a top-level not, or is_false). skipped: an email you can see that cannot be tested; skipped_reason says why. not_found: no such email, or not one the rule can read (or not settled, or awaiting sender authentication)."
49822
50413
  },
49823
50414
  "received_at": {
49824
50415
  "type": "string",
@@ -50028,7 +50619,7 @@ const openapiDocument = {
50028
50619
  "items": { "$ref": "#/components/schemas/EmailRuleScopeWarning" }
50029
50620
  },
50030
50621
  "expectations": {
50031
- "description": "Present when expect was given, or for a saved rule with saved expectations.",
50622
+ "description": "Present when expect was given, or for a saved rule with saved expectations or made-up emails.",
50032
50623
  "type": "object",
50033
50624
  "properties": {
50034
50625
  "passed": {
@@ -50098,6 +50689,11 @@ const openapiDocument = {
50098
50689
  ]
50099
50690
  },
50100
50691
  "description": "match first, then no_match, each in the order given (saved expectations by email id)."
50692
+ },
50693
+ "samples": {
50694
+ "description": "Made-up emails judged (expect.samples, or a saved rule's), in order; counts include them.",
50695
+ "type": "array",
50696
+ "items": { "$ref": "#/components/schemas/EmailRuleSampleJudgment" }
50101
50697
  }
50102
50698
  },
50103
50699
  "required": [
@@ -50116,6 +50712,250 @@ const openapiDocument = {
50116
50712
  "results"
50117
50713
  ]
50118
50714
  },
50715
+ "EmailRuleLookAlike": {
50716
+ "type": "object",
50717
+ "properties": {
50718
+ "kind": {
50719
+ "type": "string",
50720
+ "enum": [
50721
+ "medical",
50722
+ "payroll",
50723
+ "personal_purchase",
50724
+ "bank_tax",
50725
+ "other"
50726
+ ]
50727
+ },
50728
+ "expected": {
50729
+ "type": "string",
50730
+ "enum": ["match", "no_match"],
50731
+ "description": "match: a near case the rule should match; no_match: one it should not."
50732
+ },
50733
+ "why": {
50734
+ "type": "string",
50735
+ "description": "Why the rule should, or should not, match it."
50736
+ },
50737
+ "email": {
50738
+ "type": "object",
50739
+ "properties": {
50740
+ "from": {
50741
+ "type": "object",
50742
+ "properties": {
50743
+ "address": { "type": "string" },
50744
+ "name": { "type": "string" }
50745
+ },
50746
+ "required": ["address"]
50747
+ },
50748
+ "to": {
50749
+ "type": "array",
50750
+ "items": { "type": "string" }
50751
+ },
50752
+ "cc": {
50753
+ "type": "array",
50754
+ "items": { "type": "string" }
50755
+ },
50756
+ "subject": { "type": "string" },
50757
+ "text": { "type": "string" },
50758
+ "attachment_types": {
50759
+ "type": "array",
50760
+ "items": { "type": "string" }
50761
+ },
50762
+ "automated": { "type": "boolean" },
50763
+ "from_mailing_list": { "type": "boolean" }
50764
+ },
50765
+ "required": [
50766
+ "from",
50767
+ "to",
50768
+ "cc",
50769
+ "subject",
50770
+ "text",
50771
+ "attachment_types",
50772
+ "automated",
50773
+ "from_mailing_list"
50774
+ ],
50775
+ "description": "A made-up email, as test-sample takes it."
50776
+ },
50777
+ "outcome": {
50778
+ "type": "string",
50779
+ "enum": [
50780
+ "matched",
50781
+ "near_miss",
50782
+ "no_match",
50783
+ "not_decided"
50784
+ ]
50785
+ },
50786
+ "status": {
50787
+ "type": "string",
50788
+ "enum": [
50789
+ "passed",
50790
+ "failed",
50791
+ "undecided"
50792
+ ],
50793
+ "description": "failed: the rule decided it against expected (shared one it should not, or missed one it should match). undecided: the rule reads a spam score, which it cannot carry, whatever it decided; or not matched on something it could not decide (not classified, a fact or answer missing, or not reached in time)."
50794
+ },
50795
+ "question_answers": {
50796
+ "type": "array",
50797
+ "items": {
50798
+ "type": "object",
50799
+ "properties": {
50800
+ "question": { "type": "string" },
50801
+ "answer": { "type": ["boolean", "null"] },
50802
+ "probability": { "type": ["number", "null"] },
50803
+ "error_class": { "type": "string" }
50804
+ },
50805
+ "required": [
50806
+ "question",
50807
+ "answer",
50808
+ "probability"
50809
+ ]
50810
+ }
50811
+ }
50812
+ },
50813
+ "required": [
50814
+ "kind",
50815
+ "expected",
50816
+ "why",
50817
+ "email",
50818
+ "outcome",
50819
+ "status",
50820
+ "question_answers"
50821
+ ]
50822
+ },
50823
+ "GenerateLookAlikesRequest": {
50824
+ "type": "object",
50825
+ "properties": {
50826
+ "tree": {
50827
+ "description": "An unsaved rule tree, as POST /email-rules/compile returned it or written by hand. Give tree or rule_id, not both.",
50828
+ "$ref": "#/components/schemas/EmailRuleTreeInput"
50829
+ },
50830
+ "english": {
50831
+ "description": "What the tree is meant to match; without it, the tree is described in words.",
50832
+ "type": "string",
50833
+ "minLength": 1,
50834
+ "maxLength": 2e3
50835
+ },
50836
+ "evaluation_scope": {
50837
+ "type": "string",
50838
+ "enum": [
50839
+ "author",
50840
+ "own_mailboxes",
50841
+ "selected"
50842
+ ],
50843
+ "description": "own_mailboxes (the default): mail to your own addresses and agents (for a rule that shares, those mail:share_own lets you share). author: all you can read, inboxes added later included. selected: what scope_selection names."
50844
+ },
50845
+ "scope_selection": { "$ref": "#/components/schemas/EmailRuleScopeSelection" },
50846
+ "rule_id": {
50847
+ "description": "A saved rule you can see: its current version and words.",
50848
+ "type": "string",
50849
+ "format": "uuid"
50850
+ },
50851
+ "request_id": {
50852
+ "description": "A rule request you can see: its rule as asked.",
50853
+ "type": "string",
50854
+ "format": "uuid"
50855
+ },
50856
+ "kind": {
50857
+ "description": "For an unsaved tree: do_not_share tests it as a rule that blocks sharing, so an email it cannot decide counts as matched. A saved rule uses its own.",
50858
+ "type": "string",
50859
+ "enum": ["grant", "do_not_share"]
50860
+ },
50861
+ "count": {
50862
+ "description": "How many, 1 to 8; 6 by default.",
50863
+ "type": "integer",
50864
+ "minimum": 1,
50865
+ "maximum": 8
50866
+ },
50867
+ "kinds": {
50868
+ "description": "Which kinds, taken in turn, all expected no_match; other is a business near miss. Left out, a default mix.",
50869
+ "minItems": 1,
50870
+ "maxItems": 5,
50871
+ "type": "array",
50872
+ "items": {
50873
+ "type": "string",
50874
+ "enum": [
50875
+ "medical",
50876
+ "payroll",
50877
+ "personal_purchase",
50878
+ "bank_tax",
50879
+ "other"
50880
+ ]
50881
+ }
50882
+ },
50883
+ "to": {
50884
+ "description": "The mailbox they arrive in; by default the first address (alphabetically) a selected scope names that a made-up email can be sent to, preferring one the author still reads; else the rule author's (a request's target's) sign-in address when you may see it, else yours.",
50885
+ "type": "string",
50886
+ "maxLength": 254,
50887
+ "format": "email",
50888
+ "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
50889
+ }
50890
+ },
50891
+ "additionalProperties": false
50892
+ },
50893
+ "GenerateLookAlikesResult": {
50894
+ "type": "object",
50895
+ "properties": {
50896
+ "rule": {
50897
+ "type": "object",
50898
+ "properties": {
50899
+ "id": {
50900
+ "type": ["string", "null"],
50901
+ "format": "uuid"
50902
+ },
50903
+ "version": {
50904
+ "type": ["integer", "null"],
50905
+ "exclusiveMinimum": 0
50906
+ },
50907
+ "vocabulary": { "type": "string" }
50908
+ },
50909
+ "required": [
50910
+ "id",
50911
+ "version",
50912
+ "vocabulary"
50913
+ ]
50914
+ },
50915
+ "look_alikes": {
50916
+ "type": "array",
50917
+ "items": { "$ref": "#/components/schemas/EmailRuleLookAlike" }
50918
+ },
50919
+ "counts": {
50920
+ "type": "object",
50921
+ "properties": {
50922
+ "passed": {
50923
+ "type": "integer",
50924
+ "minimum": 0
50925
+ },
50926
+ "failed": {
50927
+ "type": "integer",
50928
+ "minimum": 0
50929
+ },
50930
+ "undecided": {
50931
+ "type": "integer",
50932
+ "minimum": 0
50933
+ },
50934
+ "dropped": {
50935
+ "type": "integer",
50936
+ "minimum": 0,
50937
+ "description": "Asked for but not returned: not written, outside the sample bounds, or of a kind and expected not asked for."
50938
+ }
50939
+ },
50940
+ "required": [
50941
+ "passed",
50942
+ "failed",
50943
+ "undecided",
50944
+ "dropped"
50945
+ ]
50946
+ },
50947
+ "note": {
50948
+ "type": "string",
50949
+ "description": "That the emails are made up and were never sent or stored."
50950
+ }
50951
+ },
50952
+ "required": [
50953
+ "rule",
50954
+ "look_alikes",
50955
+ "counts",
50956
+ "note"
50957
+ ]
50958
+ },
50119
50959
  "TestEmailRuleOnSampleRequest": {
50120
50960
  "type": "object",
50121
50961
  "properties": {
@@ -50266,7 +51106,7 @@ const openapiDocument = {
50266
51106
  "near_miss",
50267
51107
  "no_match"
50268
51108
  ],
50269
- "description": "near_miss: Not matched, but nearly: its questions did not all say yes, or it needs several parts and exactly one failed while at least one part that held is not an exclusion (a top-level not, or is_false)."
51109
+ "description": "near_miss: Not matched, but nearly: its conditions held and its questions did not match it, or it needs several parts and exactly one failed while at least one part that held is not an exclusion (a top-level not, or is_false)."
50270
51110
  },
50271
51111
  "parts": {
50272
51112
  "type": "array",
@@ -50407,6 +51247,10 @@ const openapiDocument = {
50407
51247
  "type": "string",
50408
51248
  "enum": ["grant", "do_not_share"]
50409
51249
  },
51250
+ "protection_note": {
51251
+ "description": "On a do_not_share rule only: it protects nothing yet (not active), a classifier fact narrows it, or its several questions are alternatives.",
51252
+ "type": "string"
51253
+ },
50410
51254
  "evaluation_scope": {
50411
51255
  "description": "Whose mail it runs over, as stored (GET /email-rules/{id}/scope: what that is now).",
50412
51256
  "type": "string",
@@ -50458,8 +51302,13 @@ const openapiDocument = {
50458
51302
  "type": "array",
50459
51303
  "items": { "$ref": "#/components/schemas/EmailRuleScopeWarning" }
50460
51304
  },
51305
+ "expectation_samples": {
51306
+ "description": "Its saved made-up emails, when this write set or added some.",
51307
+ "type": "array",
51308
+ "items": { "$ref": "#/components/schemas/EmailRuleExpectationSample" }
51309
+ },
50461
51310
  "expectations_check": {
50462
- "description": "After a write that saved a version or changed its expectations, if it has some: them, judged on that version from stored classifications without asking its questions. Undecided: one that needs answers not yet kept for that version, is unclassified, skipped or not reached, or that its author does not read. Absent when it could not be judged (the rule changed again, its author cannot be resolved, your access shows headers only, it could not finish in time, or the check failed). Test by rule_id for the rest. With if_expectations_pass, the judgment made before the save, which passed.",
51311
+ "description": "After a write that saved a version or changed its expectations or made-up emails, if it has some: the emails by id judged on that version from stored classifications without asking its questions, and any made-up emails listed as undecided. Undecided: one that needs answers not yet kept for that version, is unclassified, skipped or not reached, or that its author does not read. Absent when it could not be judged (the rule changed again, its author cannot be resolved, your access shows headers only, it could not finish in time, or the check failed), and after a write that changed only made-up emails on a rule with emails by id. Test by rule_id for the rest. With if_expectations_pass, the judgment made before the save, which passed.",
50463
51312
  "type": "object",
50464
51313
  "properties": {
50465
51314
  "version": {
@@ -50471,7 +51320,7 @@ const openapiDocument = {
50471
51320
  "type": ["boolean", "null"]
50472
51321
  },
50473
51322
  "decided": {
50474
- "description": "Whether every expectation was judged (undecided is empty). Absent from a server that predates it.",
51323
+ "description": "Whether every expectation was judged (undecided and undecided_samples are empty). Absent from a server that predates it.",
50475
51324
  "type": "boolean"
50476
51325
  },
50477
51326
  "failed": {
@@ -50488,8 +51337,24 @@ const openapiDocument = {
50488
51337
  "format": "uuid"
50489
51338
  }
50490
51339
  },
51340
+ "failed_samples": {
51341
+ "description": "Made-up emails that failed, by index in the set judged (saved oldest first, then added; or as sent).",
51342
+ "type": "array",
51343
+ "items": {
51344
+ "type": "integer",
51345
+ "minimum": 0
51346
+ }
51347
+ },
51348
+ "undecided_samples": {
51349
+ "description": "Made-up emails not decided, by index. Only a write sent with if_expectations_pass that saves a version or activates the rule judges them.",
51350
+ "type": "array",
51351
+ "items": {
51352
+ "type": "integer",
51353
+ "minimum": 0
51354
+ }
51355
+ },
50491
51356
  "note": {
50492
- "description": "On an update that saved a version that did not pass: that it was saved anyway, is current, and whether it is live.",
51357
+ "description": "On an update that saved a version that did not pass, or a do_not_share rule created active: that it was saved anyway, is current, and whether it is live.",
50493
51358
  "type": "string"
50494
51359
  }
50495
51360
  },
@@ -50640,6 +51505,32 @@ const openapiDocument = {
50640
51505
  "created_at"
50641
51506
  ]
50642
51507
  },
51508
+ "EmailRuleExpectationSample": {
51509
+ "type": "object",
51510
+ "properties": {
51511
+ "id": {
51512
+ "type": "string",
51513
+ "format": "uuid"
51514
+ },
51515
+ "expected": {
51516
+ "type": "string",
51517
+ "enum": ["match", "no_match"]
51518
+ },
51519
+ "label": { "type": ["string", "null"] },
51520
+ "email": { "$ref": "#/components/schemas/EmailRuleSavedSampleEmail" },
51521
+ "created_at": {
51522
+ "type": "string",
51523
+ "format": "date-time"
51524
+ }
51525
+ },
51526
+ "required": [
51527
+ "id",
51528
+ "expected",
51529
+ "label",
51530
+ "email",
51531
+ "created_at"
51532
+ ]
51533
+ },
50643
51534
  "CreateEmailRuleRequest": {
50644
51535
  "type": "object",
50645
51536
  "properties": {
@@ -50655,9 +51546,13 @@ const openapiDocument = {
50655
51546
  "enum": ["grant", "do_not_share"]
50656
51547
  },
50657
51548
  "state": {
50658
- "description": "observe (the default) records what the rule would match and grants or blocks nothing; draft is not evaluated.",
51549
+ "description": "observe (the default) records what the rule would match and grants or blocks nothing; draft is not evaluated; active only for a do_not_share rule.",
50659
51550
  "type": "string",
50660
- "enum": ["draft", "observe"]
51551
+ "enum": [
51552
+ "draft",
51553
+ "observe",
51554
+ "active"
51555
+ ]
50661
51556
  },
50662
51557
  "evaluation_scope": {
50663
51558
  "type": "string",
@@ -50692,6 +51587,13 @@ const openapiDocument = {
50692
51587
  "additionalProperties": false,
50693
51588
  "description": "Emails it must match and must not (at most 100): a test by rule_id judges them unless expect is given, and each version it saves is checked against them."
50694
51589
  },
51590
+ "expectation_samples": {
51591
+ "description": "Made-up emails it must match or not (at most 20).",
51592
+ "minItems": 1,
51593
+ "maxItems": 20,
51594
+ "type": "array",
51595
+ "items": { "$ref": "#/components/schemas/EmailRuleExpectationSampleInput" }
51596
+ },
50695
51597
  "english": {
50696
51598
  "type": "string",
50697
51599
  "minLength": 1,
@@ -50768,6 +51670,10 @@ const openapiDocument = {
50768
51670
  "type": "string",
50769
51671
  "enum": ["grant", "do_not_share"]
50770
51672
  },
51673
+ "protection_note": {
51674
+ "description": "On a do_not_share rule only: it protects nothing yet (not active), a classifier fact narrows it, or its several questions are alternatives.",
51675
+ "type": "string"
51676
+ },
50771
51677
  "evaluation_scope": {
50772
51678
  "description": "Whose mail it runs over, as stored (GET /email-rules/{id}/scope: what that is now).",
50773
51679
  "type": "string",
@@ -50862,6 +51768,10 @@ const openapiDocument = {
50862
51768
  "type": "string",
50863
51769
  "enum": ["grant", "do_not_share"]
50864
51770
  },
51771
+ "protection_note": {
51772
+ "description": "On a do_not_share rule only: it protects nothing yet (not active), a classifier fact narrows it, or its several questions are alternatives.",
51773
+ "type": "string"
51774
+ },
50865
51775
  "evaluation_scope": {
50866
51776
  "description": "Whose mail it runs over, as stored (GET /email-rules/{id}/scope: what that is now).",
50867
51777
  "type": "string",
@@ -50928,6 +51838,11 @@ const openapiDocument = {
50928
51838
  }
50929
51839
  },
50930
51840
  "required": ["match", "no_match"]
51841
+ },
51842
+ "expectation_samples": {
51843
+ "description": "Its saved made-up emails, oldest first.",
51844
+ "type": "array",
51845
+ "items": { "$ref": "#/components/schemas/EmailRuleExpectationSample" }
50931
51846
  }
50932
51847
  },
50933
51848
  "required": [
@@ -51045,6 +51960,19 @@ const openapiDocument = {
51045
51960
  "additionalProperties": false,
51046
51961
  "description": "Replaces its saved expectations (author only); null removes them."
51047
51962
  },
51963
+ "expectation_samples": {
51964
+ "maxItems": 20,
51965
+ "type": ["array", "null"],
51966
+ "items": { "$ref": "#/components/schemas/EmailRuleExpectationSampleInput" },
51967
+ "description": "Replaces its saved made-up emails (author only); null or [] removes them."
51968
+ },
51969
+ "add_expectation_samples": {
51970
+ "description": "Adds made-up emails to those saved (author only), up to 20 in all.",
51971
+ "minItems": 1,
51972
+ "maxItems": 20,
51973
+ "type": "array",
51974
+ "items": { "$ref": "#/components/schemas/EmailRuleExpectationSampleInput" }
51975
+ },
51048
51976
  "if_revision": {
51049
51977
  "description": "The revision you read: if the rule was updated since, nothing is saved (409 email_rule_conflict, revision_moved).",
51050
51978
  "type": "integer",
@@ -51052,7 +51980,7 @@ const openapiDocument = {
51052
51980
  "maximum": 2147483647
51053
51981
  },
51054
51982
  "if_expectations_pass": {
51055
- "description": "true: judge the rule as this write leaves it (new tree, restored version, new scope, or the version it activates) against the expectations it leaves, as POST /email-rules/test would, before saving; it can take as long as a test. Failed: 409 expectations_failed; undecided: 409 expectations_undecided (both with details.expectations_check); none to judge: 400 no_expectations. Refused writes save nothing; a pass saves unless the rule moved meanwhile (409 email_rule_conflict). Only writes that save a version or activate the rule are judged.",
51983
+ "description": "true: judge the rule as this write leaves it (new tree, restored version, new scope, or the version it activates) against the expectations it leaves, as POST /email-rules/test would, before saving; it can take as long as a test. Failed: 409 expectations_failed; on emails the rule cannot read: 409 expectations_unreadable; undecided: 409 expectations_undecided (each with details.expectations_check); none to judge: 400 no_expectations. Refused writes save nothing; a pass saves unless the rule moved meanwhile (409 email_rule_conflict). Only writes that save a version or activate the rule are judged.",
51056
51984
  "type": "boolean"
51057
51985
  },
51058
51986
  "restore_version": {
@@ -51152,7 +52080,7 @@ const openapiDocument = {
51152
52080
  "matched": { "type": "boolean" },
51153
52081
  "near_miss": {
51154
52082
  "type": "boolean",
51155
- "description": "Not matched, but nearly: its questions did not all say yes, or it needs several parts and exactly one failed while at least one part that held is not an exclusion (a top-level not, or is_false). Decisions recorded under the earlier definition counted an exclusion too."
52083
+ "description": "Not matched, but nearly: its conditions held and its questions did not match it, or it needs several parts and exactly one failed while at least one part that held is not an exclusion (a top-level not, or is_false). Decisions recorded under the earlier definition counted an exclusion too."
51156
52084
  },
51157
52085
  "parts": {
51158
52086
  "type": "array",
@@ -51210,6 +52138,28 @@ const openapiDocument = {
51210
52138
  ]
51211
52139
  }
51212
52140
  },
52141
+ "EmailRuleCoveringAuthor": {
52142
+ "type": "object",
52143
+ "properties": {
52144
+ "user_id": {
52145
+ "type": "string",
52146
+ "format": "uuid"
52147
+ },
52148
+ "name": {
52149
+ "description": "Their name; null when they have none set.",
52150
+ "type": ["string", "null"]
52151
+ },
52152
+ "email": {
52153
+ "description": "Sign-in email; null unless you can list member emails or this is you.",
52154
+ "type": ["string", "null"]
52155
+ }
52156
+ },
52157
+ "required": [
52158
+ "user_id",
52159
+ "name",
52160
+ "email"
52161
+ ]
52162
+ },
51213
52163
  "ListEmailRuleResultsResult": {
51214
52164
  "type": "object",
51215
52165
  "properties": {
@@ -51252,7 +52202,7 @@ const openapiDocument = {
51252
52202
  "type": "string",
51253
52203
  "format": "uuid"
51254
52204
  },
51255
- "name": { "type": "string" },
52205
+ "name": { "type": ["string", "null"] },
51256
52206
  "state": {
51257
52207
  "type": "string",
51258
52208
  "enum": [
@@ -51276,7 +52226,7 @@ const openapiDocument = {
51276
52226
  },
51277
52227
  "matched": { "type": "boolean" },
51278
52228
  "near_miss": {
51279
- "description": "Not matched, but nearly: its questions did not all say yes, or it needs several parts and exactly one failed while at least one part that held is not an exclusion (a top-level not, or is_false). False when detail_withheld is true. Decisions recorded under the earlier definition counted an exclusion too.",
52229
+ "description": "Not matched, but nearly: its conditions held and its questions did not match it, or it needs several parts and exactly one failed while at least one part that held is not an exclusion (a top-level not, or is_false). False when detail_withheld is true. Decisions recorded under the earlier definition counted an exclusion too.",
51280
52230
  "type": "boolean"
51281
52231
  },
51282
52232
  "parts": {
@@ -51367,6 +52317,15 @@ const openapiDocument = {
51367
52317
  "description": "On a do-not-share rule whose author is no longer a member of the organization: it blocks nothing now, whatever it decided. Absent otherwise.",
51368
52318
  "type": "boolean",
51369
52319
  "const": true
52320
+ },
52321
+ "read_only": {
52322
+ "description": "Another member's rule that decided on your own mail; dashboard and CLI only. name is null once it no longer covers you.",
52323
+ "type": "boolean",
52324
+ "const": true
52325
+ },
52326
+ "author": {
52327
+ "description": "With read_only: who wrote it.",
52328
+ "$ref": "#/components/schemas/EmailRuleCoveringAuthor"
51370
52329
  }
51371
52330
  },
51372
52331
  "required": [
@@ -51377,12 +52336,17 @@ const openapiDocument = {
51377
52336
  "not_run_reason"
51378
52337
  ]
51379
52338
  },
51380
- "description": "Every rule the caller can see: those that matched this email, then those that did not, then those that have not run on it; newest rule first within each."
52339
+ "description": "Rules you can see, and read_only ones: matched first, then not matched, then not run; newest first within each."
51381
52340
  },
51382
52341
  "hidden": {
51383
52342
  "type": "integer",
51384
52343
  "minimum": 0,
51385
- "description": "How many more rules the caller can see that are not listed."
52344
+ "description": "Rules that decided on this email that you cannot see."
52345
+ },
52346
+ "not_listed": {
52347
+ "description": "Rules you can see past the 50 listed.",
52348
+ "type": "integer",
52349
+ "minimum": 0
51386
52350
  },
51387
52351
  "classification": {
51388
52352
  "type": ["object", "null"],
@@ -51748,6 +52712,14 @@ const openapiDocument = {
51748
52712
  "description": "Why the member asked declined it; null unless they gave a reason when declining.",
51749
52713
  "type": ["string", "null"]
51750
52714
  },
52715
+ "edited_on_accept": {
52716
+ "description": "Once accepted: whether the rule was changed first, as on the share. Null until then, or when unreadable.",
52717
+ "type": ["boolean", "null"]
52718
+ },
52719
+ "notice": {
52720
+ "description": "For the member asked, while pending: what accepting would share. For the member who asked, once accepted with changes: that the request is not what is shared. Else null.",
52721
+ "type": ["string", "null"]
52722
+ },
51751
52723
  "target_can_accept": {
51752
52724
  "description": "Whether the member asked can accept it now under their role. Null unless you can list members and their roles (members:read), or when it could not be read.",
51753
52725
  "type": ["boolean", "null"]
@@ -51905,6 +52877,14 @@ const openapiDocument = {
51905
52877
  "decline_reason": {
51906
52878
  "description": "Why the member asked declined it; null unless they gave a reason when declining.",
51907
52879
  "type": ["string", "null"]
52880
+ },
52881
+ "edited_on_accept": {
52882
+ "description": "Once accepted: whether the rule was changed first, as on the share. Null until then, or when unreadable.",
52883
+ "type": ["boolean", "null"]
52884
+ },
52885
+ "notice": {
52886
+ "description": "For the member asked, while pending: what accepting would share. For the member who asked, once accepted with changes: that the request is not what is shared. Else null.",
52887
+ "type": ["string", "null"]
51908
52888
  }
51909
52889
  },
51910
52890
  "required": [
@@ -51973,6 +52953,10 @@ const openapiDocument = {
51973
52953
  "type": "string",
51974
52954
  "enum": ["grant", "do_not_share"]
51975
52955
  },
52956
+ "protection_note": {
52957
+ "description": "On a do_not_share rule only: it protects nothing yet (not active), a classifier fact narrows it, or its several questions are alternatives.",
52958
+ "type": "string"
52959
+ },
51976
52960
  "evaluation_scope": {
51977
52961
  "description": "Whose mail it runs over, as stored (GET /email-rules/{id}/scope: what that is now).",
51978
52962
  "type": "string",
@@ -52245,6 +53229,13 @@ const openapiDocument = {
52245
53229
  "breadth": {
52246
53230
  "description": "Ignored (the compile answer's breadth).",
52247
53231
  "anyOf": [{ "$ref": "#/components/schemas/EmailRuleBreadth" }, { "type": "null" }]
53232
+ },
53233
+ "expectation_samples": {
53234
+ "description": "Made-up emails the rule must match or not, such as look-alikes it must never match, kept on the rule this accept writes.",
53235
+ "minItems": 1,
53236
+ "maxItems": 20,
53237
+ "type": "array",
53238
+ "items": { "$ref": "#/components/schemas/EmailRuleExpectationSampleInput" }
52248
53239
  }
52249
53240
  },
52250
53241
  "additionalProperties": false
@@ -52254,7 +53245,11 @@ const openapiDocument = {
52254
53245
  "properties": {
52255
53246
  "request": { "$ref": "#/components/schemas/EmailRuleRequest" },
52256
53247
  "rule": { "$ref": "#/components/schemas/EmailRuleWithVersion" },
52257
- "share": { "$ref": "#/components/schemas/EmailRuleShare" }
53248
+ "share": { "$ref": "#/components/schemas/EmailRuleShare" },
53249
+ "notice": {
53250
+ "description": "What is now shared, in words.",
53251
+ "type": "string"
53252
+ }
52258
53253
  },
52259
53254
  "required": [
52260
53255
  "request",
@@ -52544,8 +53539,274 @@ const openapiDocument = {
52544
53539
  },
52545
53540
  "EndEmailRuleShareResult": {
52546
53541
  "type": "object",
52547
- "properties": { "ended": { "type": "boolean" } },
52548
- "required": ["ended"]
53542
+ "properties": {
53543
+ "ended": { "type": "boolean" },
53544
+ "rule_id": { "type": "string" },
53545
+ "note": { "type": "string" }
53546
+ },
53547
+ "required": ["ended"],
53548
+ "additionalProperties": {}
53549
+ },
53550
+ "EmailRuleCoveringMe": {
53551
+ "type": "object",
53552
+ "properties": {
53553
+ "id": {
53554
+ "type": "string",
53555
+ "format": "uuid"
53556
+ },
53557
+ "name": { "type": "string" },
53558
+ "state": {
53559
+ "type": "string",
53560
+ "enum": [
53561
+ "draft",
53562
+ "observe",
53563
+ "active"
53564
+ ],
53565
+ "description": "draft: it decides nothing. observe: it decides and records, and shares nothing. active: it decides and, for a grant rule, shares what it matches."
53566
+ },
53567
+ "kind": {
53568
+ "type": "string",
53569
+ "enum": ["grant", "do_not_share"],
53570
+ "description": "grant: what it matches can be shared. do_not_share: it blocks sharing what it matches."
53571
+ },
53572
+ "author": {
53573
+ "description": "Who wrote it.",
53574
+ "$ref": "#/components/schemas/EmailRuleCoveringAuthor"
53575
+ },
53576
+ "is_author": {
53577
+ "type": "boolean",
53578
+ "description": "Whether you wrote it."
53579
+ },
53580
+ "covers": { "$ref": "#/components/schemas/EmailRuleCoverage" },
53581
+ "current_version": {
53582
+ "type": ["integer", "null"],
53583
+ "exclusiveMinimum": 0
53584
+ },
53585
+ "created_at": {
53586
+ "type": "string",
53587
+ "format": "date-time"
53588
+ }
53589
+ },
53590
+ "required": [
53591
+ "id",
53592
+ "name",
53593
+ "state",
53594
+ "kind",
53595
+ "author",
53596
+ "is_author",
53597
+ "covers",
53598
+ "current_version",
53599
+ "created_at"
53600
+ ]
53601
+ },
53602
+ "EmailRuleCoverage": {
53603
+ "type": "object",
53604
+ "properties": {
53605
+ "addresses": {
53606
+ "type": "array",
53607
+ "items": { "type": "string" },
53608
+ "description": "Your own mailboxes (your member mailbox and your personal agents' addresses) it reaches now, lowercase."
53609
+ },
53610
+ "via": {
53611
+ "type": "string",
53612
+ "enum": [
53613
+ "author_reach",
53614
+ "selected",
53615
+ "own_mailboxes"
53616
+ ],
53617
+ "description": "author_reach: it runs over everything its author can read, which includes these. selected: it names these mailboxes or their domain. own_mailboxes: it is your own rule over your own mail."
53618
+ }
53619
+ },
53620
+ "required": ["addresses", "via"]
53621
+ },
53622
+ "ListEmailRulesCoveringMeResult": {
53623
+ "type": "array",
53624
+ "items": { "$ref": "#/components/schemas/EmailRuleCoveringMe" }
53625
+ },
53626
+ "EmailRuleCoveringMeRead": {
53627
+ "type": "object",
53628
+ "properties": {
53629
+ "id": {
53630
+ "type": "string",
53631
+ "format": "uuid"
53632
+ },
53633
+ "name": {
53634
+ "description": "Null when covers_now is false: its name now may describe something else.",
53635
+ "type": ["string", "null"]
53636
+ },
53637
+ "state": {
53638
+ "type": "string",
53639
+ "enum": [
53640
+ "draft",
53641
+ "observe",
53642
+ "active"
53643
+ ],
53644
+ "description": "draft: it decides nothing. observe: it decides and records, and shares nothing. active: it decides and, for a grant rule, shares what it matches."
53645
+ },
53646
+ "kind": {
53647
+ "type": "string",
53648
+ "enum": ["grant", "do_not_share"],
53649
+ "description": "grant: what it matches can be shared. do_not_share: it blocks sharing what it matches."
53650
+ },
53651
+ "author": {
53652
+ "description": "Who wrote it.",
53653
+ "$ref": "#/components/schemas/EmailRuleCoveringAuthor"
53654
+ },
53655
+ "is_author": {
53656
+ "type": "boolean",
53657
+ "description": "Whether you wrote it."
53658
+ },
53659
+ "covers": {
53660
+ "description": "Null when it no longer covers your mailboxes and you see it because it decided on your mail.",
53661
+ "anyOf": [{ "$ref": "#/components/schemas/EmailRuleCoverage" }, { "type": "null" }]
53662
+ },
53663
+ "current_version": {
53664
+ "type": ["integer", "null"],
53665
+ "exclusiveMinimum": 0,
53666
+ "description": "Null when covers_now is false."
53667
+ },
53668
+ "created_at": {
53669
+ "type": "string",
53670
+ "format": "date-time"
53671
+ },
53672
+ "covers_now": {
53673
+ "type": "boolean",
53674
+ "description": "Whether it covers one of your own mailboxes now. False for a rule you see only because it decided on mail at your own mailbox before."
53675
+ },
53676
+ "current": {
53677
+ "description": "Its current version: what it asks of each email. When covers_now is false, the newest version that decided on your mail instead.",
53678
+ "anyOf": [{ "$ref": "#/components/schemas/EmailRuleCoveringMeVersion" }, { "type": "null" }]
53679
+ },
53680
+ "note": {
53681
+ "description": "Present when covers_now is false: what is withheld, and why.",
53682
+ "type": "string"
53683
+ }
53684
+ },
53685
+ "required": [
53686
+ "id",
53687
+ "name",
53688
+ "state",
53689
+ "kind",
53690
+ "author",
53691
+ "is_author",
53692
+ "covers",
53693
+ "current_version",
53694
+ "created_at",
53695
+ "covers_now",
53696
+ "current"
53697
+ ]
53698
+ },
53699
+ "EmailRuleCoveringMeVersion": {
53700
+ "type": "object",
53701
+ "properties": {
53702
+ "version": {
53703
+ "type": "integer",
53704
+ "exclusiveMinimum": 0
53705
+ },
53706
+ "english": {
53707
+ "type": "string",
53708
+ "description": "What its author asked for, in their words."
53709
+ },
53710
+ "tree": { "$ref": "#/components/schemas/EmailRuleTree" },
53711
+ "explanation": {
53712
+ "type": "string",
53713
+ "description": "What the rule matches, as compiled."
53714
+ },
53715
+ "assumptions": {
53716
+ "type": "array",
53717
+ "items": { "type": "string" }
53718
+ },
53719
+ "compiled_by": { "type": "string" },
53720
+ "vocabulary": { "type": "string" },
53721
+ "created_at": {
53722
+ "type": "string",
53723
+ "format": "date-time"
53724
+ }
53725
+ },
53726
+ "required": [
53727
+ "version",
53728
+ "english",
53729
+ "tree",
53730
+ "explanation",
53731
+ "assumptions",
53732
+ "compiled_by",
53733
+ "vocabulary",
53734
+ "created_at"
53735
+ ]
53736
+ },
53737
+ "GetEmailRuleCoveringMeResult": { "$ref": "#/components/schemas/EmailRuleCoveringMeRead" },
53738
+ "EmailRuleDecisionOnMyMail": {
53739
+ "type": "object",
53740
+ "properties": {
53741
+ "id": {
53742
+ "type": "string",
53743
+ "format": "uuid"
53744
+ },
53745
+ "email_id": {
53746
+ "type": "string",
53747
+ "format": "uuid"
53748
+ },
53749
+ "rule_version": {
53750
+ "type": "integer",
53751
+ "exclusiveMinimum": 0
53752
+ },
53753
+ "classification_id": {
53754
+ "type": "string",
53755
+ "format": "uuid"
53756
+ },
53757
+ "matched": { "type": "boolean" },
53758
+ "near_miss": {
53759
+ "type": "boolean",
53760
+ "description": "Not matched, but nearly. False when detail_withheld is true."
53761
+ },
53762
+ "parts": {
53763
+ "type": "array",
53764
+ "items": {},
53765
+ "description": "How each top-level part of the rule decided."
53766
+ },
53767
+ "question_answers": {
53768
+ "type": "array",
53769
+ "items": {},
53770
+ "description": "The answers to the rule's own questions, when they were asked."
53771
+ },
53772
+ "detail_withheld": {
53773
+ "description": "Present, as true, when the email's content cannot be read: matched still says what the rule decided, and parts and question_answers are empty.",
53774
+ "type": "boolean",
53775
+ "const": true
53776
+ },
53777
+ "created_at": {
53778
+ "type": "string",
53779
+ "format": "date-time"
53780
+ },
53781
+ "from_address": {
53782
+ "description": "The email's sender address, as GET /v1/emails/{id} shows it.",
53783
+ "type": ["string", "null"]
53784
+ },
53785
+ "subject": { "type": ["string", "null"] },
53786
+ "received_at": {
53787
+ "type": ["string", "null"],
53788
+ "format": "date-time",
53789
+ "description": "When the email arrived. Null, with the other two, when you are not shown the email (it awaits sender authentication)."
53790
+ }
53791
+ },
53792
+ "required": [
53793
+ "id",
53794
+ "email_id",
53795
+ "rule_version",
53796
+ "classification_id",
53797
+ "matched",
53798
+ "near_miss",
53799
+ "parts",
53800
+ "question_answers",
53801
+ "created_at",
53802
+ "from_address",
53803
+ "subject",
53804
+ "received_at"
53805
+ ]
53806
+ },
53807
+ "ListEmailRuleDecisionsOnMyMailResult": {
53808
+ "type": "array",
53809
+ "items": { "$ref": "#/components/schemas/EmailRuleDecisionOnMyMail" }
52549
53810
  },
52550
53811
  "GetDomainAgentAuthorizationResult": {
52551
53812
  "type": "object",
@@ -54459,7 +55720,10 @@ const openapiDocument = {
54459
55720
  "format": "uuid",
54460
55721
  "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
54461
55722
  },
54462
- "endpoint_url": { "type": "string" },
55723
+ "endpoint_url": {
55724
+ "type": "string",
55725
+ "description": "Where it was sent: a webhook's scheme and host, followed by \"/... (path and query hidden)\" when its URL has more (GET /endpoints/{id} has the whole URL), function://<id> for a Function, or pull: for a pull subscription."
55726
+ },
54463
55727
  "status": { "type": "string" },
54464
55728
  "attempt_count": { "type": "integer" },
54465
55729
  "duration_ms": { "type": ["integer", "null"] },
@@ -54470,7 +55734,7 @@ const openapiDocument = {
54470
55734
  "description": "The HTTP status the endpoint answered this attempt with: a webhook's own answer, or for a Function what its runtime answered, which is the platform's own error when the Function could not be run. Null when no answer came back (a network error, a timeout, an attempt refused or not sent) and on attempts recorded before this field."
54471
55735
  },
54472
55736
  "response_body": {
54473
- "description": "The first 2 KB of the body the endpoint answered this attempt with, so a handler that answers 200 and ignores a delivery can be seen doing so. It is whatever came back, which may echo what was sent. Null when no answer came back, and cleared when the message's content is discarded or the sent email is deleted. Withheld from a caller who cannot read message content, since an answer can echo what was sent.",
55737
+ "description": "The first 2 KB of the body the endpoint answered this attempt with, so a handler that answers 200 and ignores a delivery can be seen doing so. It is whatever came back, which may echo what was sent. Null when no answer came back, and cleared when the message's content is discarded or the sent email is deleted. Withheld from a caller who cannot read message content, since an answer can echo what was sent. Null for a redirect (3xx), whose body repeats its Location; otherwise the webhook URL's path and query are hidden wherever it repeats them.",
54474
55738
  "type": ["string", "null"]
54475
55739
  },
54476
55740
  "created_at": { "type": "string" },
@@ -54523,7 +55787,7 @@ const openapiDocument = {
54523
55787
  "description": "The HTTP status the endpoint answered this attempt with: a webhook's own answer, or for a Function what its runtime answered, which is the platform's own error when the Function could not be run. Null when no answer came back (a network error, a timeout, an attempt refused or not sent) and on attempts recorded before this field."
54524
55788
  },
54525
55789
  "response_body": {
54526
- "description": "The first 2 KB of the body the endpoint answered this attempt with, so a handler that answers 200 and ignores a delivery can be seen doing so. It is whatever came back, which may echo what was sent. Null when no answer came back, and cleared when the message's content is discarded or the sent email is deleted. Withheld from a caller who cannot read message content, since an answer can echo what was sent.",
55790
+ "description": "The first 2 KB of the body the endpoint answered this attempt with, so a handler that answers 200 and ignores a delivery can be seen doing so. It is whatever came back, which may echo what was sent. Null when no answer came back, and cleared when the message's content is discarded or the sent email is deleted. Withheld from a caller who cannot read message content, since an answer can echo what was sent. Null for a redirect (3xx), whose body repeats its Location; otherwise the webhook URL's path and query are hidden wherever it repeats them.",
54527
55791
  "type": ["string", "null"]
54528
55792
  },
54529
55793
  "retry_scheduled": {
@@ -56427,7 +57691,7 @@ const openapiDocument = {
56427
57691
  }
56428
57692
  },
56429
57693
  "already_revoked": {
56430
- "description": "true when the API key was already revoked, so nothing changed and revoked is 0. Absent otherwise. GET /v1/keys/{id} says when and by whom, to a caller who may read the key.",
57694
+ "description": "true when the API key was already revoked. revoked then counts only the keys it replaced that were revoked now. Absent otherwise. GET /v1/keys/{id} says when and by whom, to a caller who may read the key.",
56431
57695
  "type": "boolean"
56432
57696
  },
56433
57697
  "keep_failed": {
@@ -56455,11 +57719,23 @@ const openapiDocument = {
56455
57719
  "id": { "type": "string" },
56456
57720
  "label": {
56457
57721
  "type": "string",
56458
- "description": "A webhook's URL, a pull subscription's name (or pull when it has none), a route's or filter's pattern, or a wake schedule's command and the address it wakes (command to address). A Function trigger's label is function: look it up by id."
57722
+ "description": "A webhook's scheme and host, followed by \"/... (path and query hidden)\" when its URL has more (GET /v1/endpoints/{id} has the whole URL), a pull subscription's name (or pull when it has none), a route's or filter's pattern, or a wake schedule's command and the address it wakes (command to address). A Function trigger's label is function: look it up by id."
56459
57723
  },
56460
57724
  "org_owned": {
56461
57725
  "type": "boolean",
56462
57726
  "description": "true: the organization owns it, and it keeps working. false: the credential owns it itself, as with everything a third-party app or agent delegate made, a webhook made with a narrowed API key, or a wake schedule made with a key that does not cover super_admin. It keeps working only while someone can still act for it, such as a key of the same rotation that still works, or whoever it was given to or kept it. Revoking a third-party app or agent delegate turns these off, except that a block filter it made keeps blocking, within the reach the app had, unless the person revoking chose to stop it."
57727
+ },
57728
+ "still_acting": {
57729
+ "description": "For an API key: whether anything still acts for it after this revoke (the organization, a key of the same rotation that still works, or whoever took it over). Null when that could not be read.",
57730
+ "type": ["boolean", "null"]
57731
+ },
57732
+ "acting_key_id": {
57733
+ "description": "For an API key: the key that still acts for it, shown to a caller who may read the organization's API keys. Null otherwise, and when no key does.",
57734
+ "type": ["string", "null"]
57735
+ },
57736
+ "delivering": {
57737
+ "description": "For an API key's webhook: whether it is still sent received mail, as GET /v1/endpoints/{id} delivery.delivering says. Null when that could not be read.",
57738
+ "type": ["boolean", "null"]
56463
57739
  }
56464
57740
  },
56465
57741
  "required": [
@@ -56470,6 +57746,28 @@ const openapiDocument = {
56470
57746
  ],
56471
57747
  "additionalProperties": {}
56472
57748
  }
57749
+ },
57750
+ "predecessors_revoked_count": {
57751
+ "description": "For an API key: how many keys it replaced in its rotation, still in their overlap, were revoked with it. Keys that replaced it never are.",
57752
+ "type": "integer",
57753
+ "minimum": -9007199254740991,
57754
+ "maximum": 9007199254740991
57755
+ },
57756
+ "predecessors_left": {
57757
+ "description": "For an API key: how many such keys were left working because you may not revoke them. They work until their overlap ends.",
57758
+ "type": "integer",
57759
+ "minimum": -9007199254740991,
57760
+ "maximum": 9007199254740991
57761
+ },
57762
+ "predecessors_revoked": {
57763
+ "description": "The ids of the keys counted in predecessors_revoked_count, to a caller who may read the organization's API keys.",
57764
+ "type": "array",
57765
+ "items": { "type": "string" }
57766
+ },
57767
+ "predecessors_not_revoked": {
57768
+ "description": "The ids of the keys counted in predecessors_left, to a caller who may read the organization's API keys.",
57769
+ "type": "array",
57770
+ "items": { "type": "string" }
56473
57771
  }
56474
57772
  },
56475
57773
  "required": [
@@ -71295,8 +72593,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
71295
72593
  name: "Primitive Team",
71296
72594
  url: "https://primitive.dev"
71297
72595
  };
71298
- const SDK_VERSION_RANGE = "^3.0.0-preview.7";
71299
- const CLI_VERSION_RANGE = "^3.0.0-preview.7";
72596
+ const SDK_VERSION_RANGE = "^3.0.0-preview.9";
72597
+ const CLI_VERSION_RANGE = "^3.0.0-preview.9";
71300
72598
  const ESBUILD_VERSION_RANGE = "^0.27.0";
71301
72599
  function renderHandler() {
71302
72600
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
@@ -75072,7 +76370,7 @@ var KeysCreateCommand = class KeysCreateCommand extends Command {
75072
76370
  multiple: true
75073
76371
  }),
75074
76372
  rule: Flags.string({
75075
- description: "An email rule id whose matching mail the key reaches (repeatable).",
76373
+ description: "An email rule id whose matching mail the key reaches (repeatable). An --address or --domain given beside it is read in full, not only the rule's mail.",
75076
76374
  multiple: true
75077
76375
  }),
75078
76376
  "all-mailboxes": Flags.boolean({ description: "A key with --permission that reaches every mailbox." }),
@@ -75119,7 +76417,10 @@ var KeysCreateCommand = class KeysCreateCommand extends Command {
75119
76417
  return;
75120
76418
  }
75121
76419
  const key = result.data;
75122
- if (!flags.json) writeWarnings(key.warnings);
76420
+ if (!flags.json) {
76421
+ writeWarnings(key.warnings);
76422
+ writeWarnings(key.address_warnings);
76423
+ }
75123
76424
  printSecret(this, Boolean(flags.json), key, key.key, [
75124
76425
  `Created API key ${key.name ?? "(unnamed)"} (${key.id}), prefix ${key.key_prefix}.`,
75125
76426
  `Permissions: ${describePermissions({
@@ -75345,6 +76646,7 @@ var KeysUpdateCommand = class KeysUpdateCommand extends Command {
75345
76646
  return;
75346
76647
  }
75347
76648
  writeWarnings(result.data.warnings);
76649
+ writeWarnings(result.data.address_warnings);
75348
76650
  const changed = result.data.changed;
75349
76651
  this.log(changed.length > 0 ? `Updated key ${args.id}: ${changed.join(", ")}.` : `Key ${args.id} already matched; nothing changed.`);
75350
76652
  if (after?.ok) {
@@ -75368,8 +76670,84 @@ function standingSummary(standing) {
75368
76670
  const kept = standing.length - keyOwned;
75369
76671
  return `${[...kept > 0 ? [`${kept} item(s) the organization owns keep working.`] : [], ...keyOwned > 0 ? [`${keyOwned} item(s) the key owned itself or carried on for the keys it was rotated from, such as its own webhooks, which keep working only while someone can still act for them, such as a key of the same rotation that still works.`] : []].join(" ")} Review them with --json.`;
75370
76672
  }
76673
+ /** How many standing objects the summary lists one by one. */
76674
+ const STANDING_LIST_LIMIT = 10;
76675
+ const STANDING_KIND_LABELS = {
76676
+ endpoint: "webhook",
76677
+ function_trigger: "Function trigger",
76678
+ pull_subscription: "pull subscription",
76679
+ recipient_route: "route",
76680
+ filter_rule: "filter",
76681
+ wake_schedule: "wake schedule"
76682
+ };
76683
+ /** Whether a revoke answer says, per item, what still acts for it or delivers. */
76684
+ function reportsCascade(standing) {
76685
+ return standing.some((item) => "still_acting" in item || "delivering" in item);
76686
+ }
76687
+ /**
76688
+ * A standing object's label. A Function trigger's label is only "function" and
76689
+ * an unnamed pull subscription's only "pull", so those carry their id. A
76690
+ * webhook's label is its URL's scheme and host (the server hides the path and
76691
+ * query); against an older server, which sent the whole URL, it is cut at
76692
+ * `?` or `#`, which can hold a token.
76693
+ */
76694
+ function standingLabel(item) {
76695
+ const id = typeof item.id === "string" ? item.id : String(item.id);
76696
+ const label = typeof item.label === "string" && item.label ? item.label : id;
76697
+ if (item.kind === "function_trigger") return id;
76698
+ if (item.kind === "pull_subscription") return label === id || label === "pull" ? id : `${label} (${id})`;
76699
+ if (item.kind === "endpoint") {
76700
+ const cut = label.search(/[?#]/);
76701
+ return cut >= 0 ? `${label.slice(0, cut)}?...` : label;
76702
+ }
76703
+ return label;
76704
+ }
76705
+ /** One standing object, with who still acts for it and whether it delivers. */
76706
+ function standingLine(item, leftWorking) {
76707
+ const kind = typeof item.kind === "string" ? STANDING_KIND_LABELS[item.kind] ?? item.kind : "item";
76708
+ let acting;
76709
+ if (item.org_owned !== false) acting = "the organization owns it, so revoking does not stop it";
76710
+ else if (item.still_acting === true) {
76711
+ const by = typeof item.acting_key_id === "string" && item.acting_key_id ? item.acting_key_id : null;
76712
+ acting = !by ? "owned by the revoked key; someone still acts for it, so it keeps working while they do" : leftWorking.has(by) ? `owned by the revoked key; key ${by} still acts for it until that key's overlap ends` : `owned by the revoked key; key ${by} still acts for it, so it keeps working while that key does`;
76713
+ } else if (item.still_acting === false) acting = "owned by the revoked key; nothing acts for it, so it does not work";
76714
+ else if (item.still_acting === null) acting = "owned by the revoked key; whether anything still acts for it could not be read, and it works only while something does";
76715
+ else acting = "owned by the revoked key; it keeps working only while someone can still act for it";
76716
+ const delivering = item.delivering === true ? "; still delivering received mail" : item.delivering === false ? "; not delivering received mail" : item.delivering === null ? "; whether it still delivers could not be read" : "";
76717
+ return ` ${kind} ${standingLabel(item)}: ${acting}${delivering}.`;
76718
+ }
76719
+ /**
76720
+ * What a revoke did beyond the key itself, from the answer: the keys it
76721
+ * replaced that were revoked with it or left working, and, for each item the
76722
+ * key set up or owns, whether anything still acts for it and whether it still
76723
+ * delivers. Lines, empty when the answer has none of these. An answer from a
76724
+ * server that does not report them per item falls back to standingSummary.
76725
+ */
76726
+ function revokeCascadeLines(data) {
76727
+ if (!data || typeof data !== "object") return [];
76728
+ const answer = data;
76729
+ const lines = [];
76730
+ const ids = (value) => Array.isArray(value) ? value.filter((id) => typeof id === "string") : [];
76731
+ const named = (list) => list.length > 0 ? `: ${list.join(", ")}` : "";
76732
+ const revoked = answer.predecessors_revoked_count;
76733
+ if (typeof revoked === "number" && revoked > 0) lines.push(`Also revoked ${revoked === 1 ? "1 key" : `${revoked} keys`} it replaced, still in ${revoked === 1 ? "its" : "their"} rotation overlap${named(ids(answer.predecessors_revoked))}.`);
76734
+ const left = answer.predecessors_left;
76735
+ if (typeof left === "number" && left > 0) lines.push(`${left === 1 ? "1 key" : `${left} keys`} it replaced ${left === 1 ? "is" : "are"} left working until ${left === 1 ? "its" : "their"} overlap ends, because you may not revoke ${left === 1 ? "it" : "them"}${named(ids(answer.predecessors_not_revoked))}; what ${left === 1 ? "it acts" : "they act"} for keeps working until then.`);
76736
+ const leftWorking = new Set(ids(answer.predecessors_not_revoked));
76737
+ const standing = Array.isArray(answer.standing_objects) ? answer.standing_objects.filter((item) => item !== null && typeof item === "object") : [];
76738
+ if (standing.length > 0 && reportsCascade(standing)) {
76739
+ lines.push("What it set up, owns or carried on for the keys it replaced (revoking deletes none of it):");
76740
+ for (const item of standing.slice(0, STANDING_LIST_LIMIT)) lines.push(standingLine(item, leftWorking));
76741
+ const more = standing.length - STANDING_LIST_LIMIT;
76742
+ if (more > 0) lines.push(` ${more} more; --json lists them all.`);
76743
+ } else {
76744
+ const summary = standingSummary(answer.standing_objects);
76745
+ if (summary) lines.push(summary);
76746
+ }
76747
+ return lines;
76748
+ }
75371
76749
  var KeysRevokeCommand = class KeysRevokeCommand extends Command {
75372
- static description = `Revoke an API key. It stops working at once. What the organization owns keeps working; what the key itself owns, such as its own webhooks, keeps working only while someone can still act for it, such as a key of the same rotation that still works. What it set up or owns is listed for review.
76750
+ static description = `Revoke an API key. It stops working at once. What the organization owns keeps working; what the key itself owns, such as its own webhooks, keeps working only while someone can still act for it, such as a key of the same rotation that still works. What it set up or owns is listed for review, each with whether anything still acts for it and, for a webhook, whether it still delivers received mail. Keys it replaced that are still in their rotation overlap are revoked with it, except any you may not revoke; the output counts both, and names them and the key acting for each item when you may read the organization's keys.
75373
76751
 
75374
76752
  Asks for confirmation first; pass --yes to skip the prompt in scripts. Revoking runs directly. If your organization requires approval, the command prints the approval link, opens it where it can and waits for the one-time token; in a script, approve and run it again with --step-up-token.`;
75375
76753
  static summary = "Revoke an API key";
@@ -75418,9 +76796,10 @@ var KeysRevokeCommand = class KeysRevokeCommand extends Command {
75418
76796
  this.log(JSON.stringify(result.data, null, 2));
75419
76797
  return;
75420
76798
  }
75421
- this.log(result.data.already_revoked === true ? `Key ${args.id} was already revoked; nothing changed.` : `Revoked key ${args.id}.`);
75422
- const summary = standingSummary(result.data.standing_objects);
75423
- if (summary) this.log(summary);
76799
+ const replaced = result.data.predecessors_revoked_count;
76800
+ const alsoRevoked = typeof replaced === "number" && replaced > 0;
76801
+ this.log(result.data.already_revoked === true ? alsoRevoked ? `Key ${args.id} was already revoked.` : `Key ${args.id} was already revoked; nothing changed.` : `Revoked key ${args.id}.`);
76802
+ for (const line of revokeCascadeLines(result.data)) this.log(line);
75424
76803
  }
75425
76804
  };
75426
76805
  const hash = (value) => createHash("sha256").update(value).digest("hex");