primcli 3.0.0-preview.6 → 3.0.0-preview.8
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.
- package/README.md +1 -1
- package/dist/{api-command-CRjIK1QL.js → api-command-TUwewvqZ.js} +2840 -444
- package/dist/{json-output-DWrJMakZ.js → json-output-ch756TXV.js} +8 -1
- package/dist/{machine-session-3H-rNH8v.js → machine-session-DNnNhhFr.js} +5 -5
- package/dist/{notify-session-DTyg-sfS.js → notify-session-CeIflLIX.js} +3 -3
- package/dist/oclif/auto-signal-worker.js +3 -3
- package/dist/oclif/auto-signals.js +1 -1
- package/dist/oclif/command-not-found.js +1 -1
- package/dist/oclif/index.js +1709 -381
- package/dist/oclif/proxy-auto-detect.js +1 -1
- package/dist/oclif/receiver-heal.js +2 -2
- package/dist/{repeats-Dj_o6c-X-CP0h4alF.js → repeats-Dj_o6c-X-DNbrLjEF.js} +30 -12
- package/dist/{scoped-chat-CvqdlWuX.js → scoped-chat-Dd3P8Q-m.js} +1 -1
- package/dist/{signal-command-CPxRPbHK.js → signal-command-DZQemE9I.js} +4 -4
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { B as ensureSearchReportsReplyState, Ht as getEmail, J as SENT_EMAIL_EVENT_TYPES, K as replyStateSurfaceForOperation, L as ReplyStateUnsupportedError, R as assertAwaitingFilterRows, W as isAwaitingRejectedError, Wn as sdk_gen_exports, Y as WEBHOOK_EVENT_TYPES, Zt as getOwnGrant, at as automatedRejectedError, ct as expectedAutomatedVerdicts, gn as listEndpoints, it as assertAutomatedVerdict, ot as automatedSurfaceForOperation, q as EMAIL_RULE_REQUEST_EVENT_TYPES, rt as AutomatedFilterUnsupportedError, st as ensureSearchReportsAutomated, tn as getThread, ut as isAutomatedRejectedError, v as parseRetryAfter, z as awaitingRejectedError } from "./repeats-Dj_o6c-X-
|
|
1
|
+
import { B as ensureSearchReportsReplyState, Ht as getEmail, J as SENT_EMAIL_EVENT_TYPES, K as replyStateSurfaceForOperation, L as ReplyStateUnsupportedError, R as assertAwaitingFilterRows, W as isAwaitingRejectedError, Wn as sdk_gen_exports, Y as WEBHOOK_EVENT_TYPES, Zt as getOwnGrant, at as automatedRejectedError, ct as expectedAutomatedVerdicts, gn as listEndpoints, it as assertAutomatedVerdict, ot as automatedSurfaceForOperation, q as EMAIL_RULE_REQUEST_EVENT_TYPES, rt as AutomatedFilterUnsupportedError, st as ensureSearchReportsAutomated, tn as getThread, ut as isAutomatedRejectedError, v as parseRetryAfter, z as awaitingRejectedError } from "./repeats-Dj_o6c-X-DNbrLjEF.js";
|
|
2
2
|
import { C as saveCliCredentials, F as agentProfilesDirectory, H as requireDefaultLoginProfile, N as agentProfileDirectory, R as loadConnectedAgentProfile, S as resolveCliAuth, W as PrimitiveApiClient, _ as deleteCliCredentials, b as loadCliCredentials, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, i as loadCliConfig, j as AGENT_PROFILE_ENV, m as cliAccessTokenExpiresAt, p as acquireCliCredentialsLock, w as saveSignupCredentials, x as normalizeApiBaseUrl, y as detectPrimitiveKeyEnvMisname } from "./cli-config-Ms_0vNjq.js";
|
|
3
3
|
import "./oclif/global-org-flag.js";
|
|
4
|
+
import { a as thrownErrorBody } from "./json-output-ch756TXV.js";
|
|
4
5
|
import { d as readMailJson, h as writeMailJson, m as withMailLock, p as syncMailDirectory, u as privateMailPermissions } from "./shared-mail-files-BAQSGxxz.js";
|
|
5
6
|
import { Args, Command, Errors, Flags } from "@oclif/core";
|
|
6
7
|
import { existsSync, lstatSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
@@ -10556,7 +10557,7 @@ const operationManifest = [
|
|
|
10556
10557
|
"binaryResponse": false,
|
|
10557
10558
|
"bodyRequired": true,
|
|
10558
10559
|
"command": "revoke-credential",
|
|
10559
|
-
"description": "Revoke one credential by kind and id. A Function key is rotated, never revoked (409 rotate_instead). An API key is revoked
|
|
10560
|
+
"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.",
|
|
10560
10561
|
"hasJsonBody": true,
|
|
10561
10562
|
"method": "POST",
|
|
10562
10563
|
"operationId": "revokeCredential",
|
|
@@ -10669,7 +10670,7 @@ const operationManifest = [
|
|
|
10669
10670
|
}
|
|
10670
10671
|
},
|
|
10671
10672
|
"already_revoked": {
|
|
10672
|
-
"description": "true when the API key was already revoked
|
|
10673
|
+
"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.",
|
|
10673
10674
|
"type": "boolean"
|
|
10674
10675
|
},
|
|
10675
10676
|
"keep_failed": {
|
|
@@ -10702,6 +10703,18 @@ const operationManifest = [
|
|
|
10702
10703
|
"org_owned": {
|
|
10703
10704
|
"type": "boolean",
|
|
10704
10705
|
"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."
|
|
10706
|
+
},
|
|
10707
|
+
"still_acting": {
|
|
10708
|
+
"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.",
|
|
10709
|
+
"type": ["boolean", "null"]
|
|
10710
|
+
},
|
|
10711
|
+
"acting_key_id": {
|
|
10712
|
+
"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.",
|
|
10713
|
+
"type": ["string", "null"]
|
|
10714
|
+
},
|
|
10715
|
+
"delivering": {
|
|
10716
|
+
"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.",
|
|
10717
|
+
"type": ["boolean", "null"]
|
|
10705
10718
|
}
|
|
10706
10719
|
},
|
|
10707
10720
|
"required": [
|
|
@@ -10712,6 +10725,28 @@ const operationManifest = [
|
|
|
10712
10725
|
],
|
|
10713
10726
|
"additionalProperties": {}
|
|
10714
10727
|
}
|
|
10728
|
+
},
|
|
10729
|
+
"predecessors_revoked_count": {
|
|
10730
|
+
"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.",
|
|
10731
|
+
"type": "integer",
|
|
10732
|
+
"minimum": -9007199254740991,
|
|
10733
|
+
"maximum": 9007199254740991
|
|
10734
|
+
},
|
|
10735
|
+
"predecessors_left": {
|
|
10736
|
+
"description": "For an API key: how many such keys were left working because you may not revoke them. They work until their overlap ends.",
|
|
10737
|
+
"type": "integer",
|
|
10738
|
+
"minimum": -9007199254740991,
|
|
10739
|
+
"maximum": 9007199254740991
|
|
10740
|
+
},
|
|
10741
|
+
"predecessors_revoked": {
|
|
10742
|
+
"description": "The ids of the keys counted in predecessors_revoked_count, to a caller who may read the organization's API keys.",
|
|
10743
|
+
"type": "array",
|
|
10744
|
+
"items": { "type": "string" }
|
|
10745
|
+
},
|
|
10746
|
+
"predecessors_not_revoked": {
|
|
10747
|
+
"description": "The ids of the keys counted in predecessors_left, to a caller who may read the organization's API keys.",
|
|
10748
|
+
"type": "array",
|
|
10749
|
+
"items": { "type": "string" }
|
|
10715
10750
|
}
|
|
10716
10751
|
},
|
|
10717
10752
|
"required": [
|
|
@@ -10730,7 +10765,7 @@ const operationManifest = [
|
|
|
10730
10765
|
"binaryResponse": false,
|
|
10731
10766
|
"bodyRequired": true,
|
|
10732
10767
|
"command": "rotate-key",
|
|
10733
|
-
"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.",
|
|
10768
|
+
"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.",
|
|
10734
10769
|
"hasJsonBody": true,
|
|
10735
10770
|
"method": "POST",
|
|
10736
10771
|
"operationId": "rotateKey",
|
|
@@ -10904,7 +10939,7 @@ const operationManifest = [
|
|
|
10904
10939
|
"binaryResponse": false,
|
|
10905
10940
|
"bodyRequired": true,
|
|
10906
10941
|
"command": "update-key",
|
|
10907
|
-
"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.",
|
|
10942
|
+
"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.",
|
|
10908
10943
|
"hasJsonBody": true,
|
|
10909
10944
|
"method": "PATCH",
|
|
10910
10945
|
"operationId": "updateKey",
|
|
@@ -12916,7 +12951,7 @@ const operationManifest = [
|
|
|
12916
12951
|
"binaryResponse": false,
|
|
12917
12952
|
"bodyRequired": false,
|
|
12918
12953
|
"command": "accept-email-rule-request",
|
|
12919
|
-
"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.
|
|
12954
|
+
"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.",
|
|
12920
12955
|
"hasJsonBody": true,
|
|
12921
12956
|
"method": "POST",
|
|
12922
12957
|
"operationId": "acceptEmailRuleRequest",
|
|
@@ -13014,6 +13049,131 @@ const operationManifest = [
|
|
|
13014
13049
|
"type": "string",
|
|
13015
13050
|
"minLength": 1,
|
|
13016
13051
|
"maxLength": 64
|
|
13052
|
+
},
|
|
13053
|
+
"breadth": {
|
|
13054
|
+
"description": "Ignored (the compile answer's breadth).",
|
|
13055
|
+
"anyOf": [{
|
|
13056
|
+
"type": "object",
|
|
13057
|
+
"properties": {
|
|
13058
|
+
"checked": {
|
|
13059
|
+
"type": "integer",
|
|
13060
|
+
"minimum": 0,
|
|
13061
|
+
"description": "Classified emails the conditions were decided for: of the newest 200 from the last 30 days in your own mailboxes (for a grant rule, those you may share), the ones you can still read."
|
|
13062
|
+
},
|
|
13063
|
+
"matched": {
|
|
13064
|
+
"type": "integer",
|
|
13065
|
+
"minimum": 0,
|
|
13066
|
+
"description": "Of those, how many they hold for (for a do-not-share rule, one they cannot decide counts)."
|
|
13067
|
+
}
|
|
13068
|
+
},
|
|
13069
|
+
"required": ["checked", "matched"]
|
|
13070
|
+
}, { "type": "null" }]
|
|
13071
|
+
},
|
|
13072
|
+
"expectation_samples": {
|
|
13073
|
+
"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.",
|
|
13074
|
+
"minItems": 1,
|
|
13075
|
+
"maxItems": 20,
|
|
13076
|
+
"type": "array",
|
|
13077
|
+
"items": {
|
|
13078
|
+
"type": "object",
|
|
13079
|
+
"properties": {
|
|
13080
|
+
"expected": {
|
|
13081
|
+
"type": "string",
|
|
13082
|
+
"enum": ["match", "no_match"]
|
|
13083
|
+
},
|
|
13084
|
+
"label": {
|
|
13085
|
+
"type": ["string", "null"],
|
|
13086
|
+
"minLength": 1,
|
|
13087
|
+
"maxLength": 200,
|
|
13088
|
+
"pattern": "^[^\\u0000]*$",
|
|
13089
|
+
"description": "What the email stands for, such as a personal medical bill."
|
|
13090
|
+
},
|
|
13091
|
+
"email": {
|
|
13092
|
+
"type": "object",
|
|
13093
|
+
"properties": {
|
|
13094
|
+
"from": {
|
|
13095
|
+
"type": "object",
|
|
13096
|
+
"properties": {
|
|
13097
|
+
"address": {
|
|
13098
|
+
"type": "string",
|
|
13099
|
+
"maxLength": 254,
|
|
13100
|
+
"format": "email",
|
|
13101
|
+
"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,}$"
|
|
13102
|
+
},
|
|
13103
|
+
"name": {
|
|
13104
|
+
"type": "string",
|
|
13105
|
+
"maxLength": 200,
|
|
13106
|
+
"pattern": "^[^\\u0000]*$"
|
|
13107
|
+
}
|
|
13108
|
+
},
|
|
13109
|
+
"required": ["address"]
|
|
13110
|
+
},
|
|
13111
|
+
"to": {
|
|
13112
|
+
"minItems": 1,
|
|
13113
|
+
"maxItems": 10,
|
|
13114
|
+
"type": "array",
|
|
13115
|
+
"items": {
|
|
13116
|
+
"type": "string",
|
|
13117
|
+
"maxLength": 254,
|
|
13118
|
+
"format": "email",
|
|
13119
|
+
"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,}$"
|
|
13120
|
+
}
|
|
13121
|
+
},
|
|
13122
|
+
"cc": {
|
|
13123
|
+
"maxItems": 10,
|
|
13124
|
+
"type": "array",
|
|
13125
|
+
"items": {
|
|
13126
|
+
"type": "string",
|
|
13127
|
+
"maxLength": 254,
|
|
13128
|
+
"format": "email",
|
|
13129
|
+
"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,}$"
|
|
13130
|
+
}
|
|
13131
|
+
},
|
|
13132
|
+
"subject": {
|
|
13133
|
+
"type": "string",
|
|
13134
|
+
"maxLength": 998,
|
|
13135
|
+
"pattern": "^[^\\u0000]*$"
|
|
13136
|
+
},
|
|
13137
|
+
"text": {
|
|
13138
|
+
"type": "string",
|
|
13139
|
+
"minLength": 1,
|
|
13140
|
+
"maxLength": 4e3,
|
|
13141
|
+
"pattern": "^[^\\u0000]*$"
|
|
13142
|
+
},
|
|
13143
|
+
"attachment_types": {
|
|
13144
|
+
"maxItems": 10,
|
|
13145
|
+
"type": "array",
|
|
13146
|
+
"items": {
|
|
13147
|
+
"type": "string",
|
|
13148
|
+
"minLength": 1,
|
|
13149
|
+
"maxLength": 255,
|
|
13150
|
+
"pattern": "^[^\\u0000]*$"
|
|
13151
|
+
}
|
|
13152
|
+
},
|
|
13153
|
+
"automated": { "type": "boolean" },
|
|
13154
|
+
"from_mailing_list": { "type": "boolean" },
|
|
13155
|
+
"spam_score": {
|
|
13156
|
+
"type": "number",
|
|
13157
|
+
"minimum": -1e3,
|
|
13158
|
+
"maximum": 1e3
|
|
13159
|
+
}
|
|
13160
|
+
},
|
|
13161
|
+
"required": [
|
|
13162
|
+
"from",
|
|
13163
|
+
"to",
|
|
13164
|
+
"text"
|
|
13165
|
+
],
|
|
13166
|
+
"description": "A made-up email, as POST /email-rules/test-sample takes it."
|
|
13167
|
+
},
|
|
13168
|
+
"id": {
|
|
13169
|
+
"type": "string",
|
|
13170
|
+
"format": "uuid"
|
|
13171
|
+
},
|
|
13172
|
+
"created_at": { "type": "string" }
|
|
13173
|
+
},
|
|
13174
|
+
"required": ["expected", "email"],
|
|
13175
|
+
"additionalProperties": false
|
|
13176
|
+
}
|
|
13017
13177
|
}
|
|
13018
13178
|
},
|
|
13019
13179
|
"additionalProperties": false
|
|
@@ -13168,6 +13328,14 @@ const operationManifest = [
|
|
|
13168
13328
|
"decline_reason": {
|
|
13169
13329
|
"description": "Why the member asked declined it; null unless they gave a reason when declining.",
|
|
13170
13330
|
"type": ["string", "null"]
|
|
13331
|
+
},
|
|
13332
|
+
"edited_on_accept": {
|
|
13333
|
+
"description": "Once accepted: whether the rule was changed first, as on the share. Null until then, or when unreadable.",
|
|
13334
|
+
"type": ["boolean", "null"]
|
|
13335
|
+
},
|
|
13336
|
+
"notice": {
|
|
13337
|
+
"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.",
|
|
13338
|
+
"type": ["string", "null"]
|
|
13171
13339
|
}
|
|
13172
13340
|
},
|
|
13173
13341
|
"required": [
|
|
@@ -13214,6 +13382,10 @@ const operationManifest = [
|
|
|
13214
13382
|
"type": "string",
|
|
13215
13383
|
"enum": ["grant", "do_not_share"]
|
|
13216
13384
|
},
|
|
13385
|
+
"protection_note": {
|
|
13386
|
+
"description": "On a do_not_share rule only: it protects nothing yet (not active), or a classifier fact narrows it.",
|
|
13387
|
+
"type": "string"
|
|
13388
|
+
},
|
|
13217
13389
|
"evaluation_scope": {
|
|
13218
13390
|
"description": "Whose mail it runs over, as stored (GET /email-rules/{id}/scope: what that is now).",
|
|
13219
13391
|
"type": "string",
|
|
@@ -13244,10 +13416,37 @@ const operationManifest = [
|
|
|
13244
13416
|
"type": ["integer", "null"],
|
|
13245
13417
|
"exclusiveMinimum": 0
|
|
13246
13418
|
},
|
|
13419
|
+
"revision": {
|
|
13420
|
+
"description": "Moves on by one with every update. Send it as if_revision.",
|
|
13421
|
+
"type": "integer",
|
|
13422
|
+
"exclusiveMinimum": 0
|
|
13423
|
+
},
|
|
13247
13424
|
"author_user_id": {
|
|
13248
13425
|
"type": "string",
|
|
13249
13426
|
"format": "uuid"
|
|
13250
13427
|
},
|
|
13428
|
+
"author": {
|
|
13429
|
+
"type": "object",
|
|
13430
|
+
"properties": {
|
|
13431
|
+
"user_id": {
|
|
13432
|
+
"type": "string",
|
|
13433
|
+
"format": "uuid"
|
|
13434
|
+
},
|
|
13435
|
+
"name": {
|
|
13436
|
+
"description": "Their name; null unless you can list members or this is you, and when they have none set.",
|
|
13437
|
+
"type": ["string", "null"]
|
|
13438
|
+
},
|
|
13439
|
+
"email": {
|
|
13440
|
+
"description": "Sign-in email; null unless you can list member emails or this is you.",
|
|
13441
|
+
"type": ["string", "null"]
|
|
13442
|
+
}
|
|
13443
|
+
},
|
|
13444
|
+
"required": [
|
|
13445
|
+
"user_id",
|
|
13446
|
+
"name",
|
|
13447
|
+
"email"
|
|
13448
|
+
]
|
|
13449
|
+
},
|
|
13251
13450
|
"is_author": {
|
|
13252
13451
|
"type": "boolean",
|
|
13253
13452
|
"description": "Whether you are its author, in your current membership. Only its author edits it; others who see it may only disable or delete it (while shared, only if they manage members)."
|
|
@@ -13260,91 +13459,94 @@ const operationManifest = [
|
|
|
13260
13459
|
"type": "string",
|
|
13261
13460
|
"format": "date-time"
|
|
13262
13461
|
},
|
|
13263
|
-
"current": {
|
|
13264
|
-
"
|
|
13265
|
-
"
|
|
13266
|
-
"
|
|
13267
|
-
|
|
13268
|
-
"
|
|
13269
|
-
|
|
13270
|
-
|
|
13271
|
-
"tree": {
|
|
13272
|
-
"type": "object",
|
|
13273
|
-
"properties": {
|
|
13274
|
-
"version": {
|
|
13275
|
-
"type": "number",
|
|
13276
|
-
"const": 1
|
|
13277
|
-
},
|
|
13278
|
-
"vocabulary": {
|
|
13279
|
-
"type": "string",
|
|
13280
|
-
"description": "Vocabulary version, such as v1."
|
|
13281
|
-
},
|
|
13282
|
-
"when": {
|
|
13283
|
-
"type": "object",
|
|
13284
|
-
"propertyNames": { "type": "string" },
|
|
13285
|
-
"additionalProperties": {},
|
|
13286
|
-
"description": "A condition {column, op, value?, min_confidence?} (see GET /email-rules/vocabulary) or {all: [...]}, {any: [...]}, {not: ...}."
|
|
13287
|
-
},
|
|
13288
|
-
"questions": {
|
|
13289
|
-
"type": "array",
|
|
13290
|
-
"items": { "type": "string" },
|
|
13291
|
-
"description": "Yes/no questions asked once the conditions match; all must be yes."
|
|
13292
|
-
}
|
|
13462
|
+
"current": {
|
|
13463
|
+
"description": "Its current version: its text (english, tree, explanation, assumptions, warnings) and who compiled it. current_version is its number. GET /email-rules lists rules without it.",
|
|
13464
|
+
"anyOf": [{
|
|
13465
|
+
"type": "object",
|
|
13466
|
+
"properties": {
|
|
13467
|
+
"version": {
|
|
13468
|
+
"type": "integer",
|
|
13469
|
+
"exclusiveMinimum": 0
|
|
13293
13470
|
},
|
|
13294
|
-
"
|
|
13295
|
-
|
|
13296
|
-
"
|
|
13297
|
-
"
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
|
|
13301
|
-
|
|
13302
|
-
|
|
13303
|
-
|
|
13304
|
-
|
|
13305
|
-
|
|
13306
|
-
|
|
13307
|
-
|
|
13308
|
-
|
|
13309
|
-
|
|
13310
|
-
|
|
13311
|
-
|
|
13312
|
-
|
|
13313
|
-
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13317
|
-
"format": "date-time"
|
|
13318
|
-
},
|
|
13319
|
-
"sql": {
|
|
13320
|
-
"description": "Present only when include_sql was true.",
|
|
13321
|
-
"type": "object",
|
|
13322
|
-
"properties": {
|
|
13323
|
-
"expression": {
|
|
13324
|
-
"type": "string",
|
|
13325
|
-
"description": "A parameterized boolean expression over one row per email."
|
|
13471
|
+
"english": { "type": "string" },
|
|
13472
|
+
"tree": {
|
|
13473
|
+
"type": "object",
|
|
13474
|
+
"properties": {
|
|
13475
|
+
"version": {
|
|
13476
|
+
"type": "number",
|
|
13477
|
+
"const": 1
|
|
13478
|
+
},
|
|
13479
|
+
"vocabulary": {
|
|
13480
|
+
"type": "string",
|
|
13481
|
+
"description": "Vocabulary version, such as v1."
|
|
13482
|
+
},
|
|
13483
|
+
"when": {
|
|
13484
|
+
"type": "object",
|
|
13485
|
+
"propertyNames": { "type": "string" },
|
|
13486
|
+
"additionalProperties": {},
|
|
13487
|
+
"description": "A condition {column, op, value?, min_confidence?} (see GET /email-rules/vocabulary) or {all: [...]}, {any: [...]}, {not: ...}."
|
|
13488
|
+
},
|
|
13489
|
+
"questions": {
|
|
13490
|
+
"type": "array",
|
|
13491
|
+
"items": { "type": "string" },
|
|
13492
|
+
"description": "Yes/no questions asked once the conditions match; all must be yes."
|
|
13493
|
+
}
|
|
13326
13494
|
},
|
|
13327
|
-
"
|
|
13328
|
-
"
|
|
13329
|
-
"
|
|
13330
|
-
"
|
|
13331
|
-
|
|
13495
|
+
"required": [
|
|
13496
|
+
"version",
|
|
13497
|
+
"vocabulary",
|
|
13498
|
+
"when",
|
|
13499
|
+
"questions"
|
|
13500
|
+
]
|
|
13332
13501
|
},
|
|
13333
|
-
"
|
|
13334
|
-
|
|
13335
|
-
|
|
13336
|
-
|
|
13337
|
-
|
|
13338
|
-
|
|
13339
|
-
|
|
13340
|
-
|
|
13341
|
-
|
|
13342
|
-
|
|
13343
|
-
|
|
13344
|
-
|
|
13345
|
-
|
|
13346
|
-
|
|
13347
|
-
|
|
13502
|
+
"explanation": { "type": "string" },
|
|
13503
|
+
"assumptions": {
|
|
13504
|
+
"type": "array",
|
|
13505
|
+
"items": { "type": "string" }
|
|
13506
|
+
},
|
|
13507
|
+
"warnings": {
|
|
13508
|
+
"type": "array",
|
|
13509
|
+
"items": { "type": "string" }
|
|
13510
|
+
},
|
|
13511
|
+
"compiled_by": {
|
|
13512
|
+
"type": "string",
|
|
13513
|
+
"description": "Who produced the tree, as stated when saved (not verified), such as rule-compiler-v1 or member."
|
|
13514
|
+
},
|
|
13515
|
+
"vocabulary": { "type": "string" },
|
|
13516
|
+
"created_at": {
|
|
13517
|
+
"type": "string",
|
|
13518
|
+
"format": "date-time"
|
|
13519
|
+
},
|
|
13520
|
+
"sql": {
|
|
13521
|
+
"description": "Present only when include_sql was true.",
|
|
13522
|
+
"type": "object",
|
|
13523
|
+
"properties": {
|
|
13524
|
+
"expression": {
|
|
13525
|
+
"type": "string",
|
|
13526
|
+
"description": "A parameterized boolean expression over one row per email."
|
|
13527
|
+
},
|
|
13528
|
+
"params": {
|
|
13529
|
+
"type": "array",
|
|
13530
|
+
"items": {},
|
|
13531
|
+
"description": "The values bound to $1, $2 and so on."
|
|
13532
|
+
}
|
|
13533
|
+
},
|
|
13534
|
+
"required": ["expression", "params"]
|
|
13535
|
+
}
|
|
13536
|
+
},
|
|
13537
|
+
"required": [
|
|
13538
|
+
"version",
|
|
13539
|
+
"english",
|
|
13540
|
+
"tree",
|
|
13541
|
+
"explanation",
|
|
13542
|
+
"assumptions",
|
|
13543
|
+
"warnings",
|
|
13544
|
+
"compiled_by",
|
|
13545
|
+
"vocabulary",
|
|
13546
|
+
"created_at"
|
|
13547
|
+
]
|
|
13548
|
+
}, { "type": "null" }]
|
|
13549
|
+
}
|
|
13348
13550
|
},
|
|
13349
13551
|
"required": [
|
|
13350
13552
|
"id",
|
|
@@ -13514,6 +13716,10 @@ const operationManifest = [
|
|
|
13514
13716
|
"rule",
|
|
13515
13717
|
"rule_id"
|
|
13516
13718
|
]
|
|
13719
|
+
},
|
|
13720
|
+
"notice": {
|
|
13721
|
+
"description": "What is now shared, in words.",
|
|
13722
|
+
"type": "string"
|
|
13517
13723
|
}
|
|
13518
13724
|
},
|
|
13519
13725
|
"required": [
|
|
@@ -13613,7 +13819,7 @@ const operationManifest = [
|
|
|
13613
13819
|
"binaryResponse": false,
|
|
13614
13820
|
"bodyRequired": true,
|
|
13615
13821
|
"command": "compile-email-rule",
|
|
13616
|
-
"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
|
|
13822
|
+
"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. 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.",
|
|
13617
13823
|
"hasJsonBody": true,
|
|
13618
13824
|
"method": "POST",
|
|
13619
13825
|
"operationId": "compileEmailRule",
|
|
@@ -13686,7 +13892,7 @@ const operationManifest = [
|
|
|
13686
13892
|
},
|
|
13687
13893
|
"explanation": {
|
|
13688
13894
|
"type": "string",
|
|
13689
|
-
"description": "What the rule matches, in plain English."
|
|
13895
|
+
"description": "What the rule matches, in plain English: only which emails, not what it grants or shares, which follows from its state, its scope and the keys, webhooks and people it reaches. The compiler leaves out a sentence that plainly says what the rule hands over."
|
|
13690
13896
|
},
|
|
13691
13897
|
"assumptions": {
|
|
13692
13898
|
"type": "array",
|
|
@@ -13696,12 +13902,31 @@ const operationManifest = [
|
|
|
13696
13902
|
"warnings": {
|
|
13697
13903
|
"type": "array",
|
|
13698
13904
|
"items": { "type": "string" },
|
|
13699
|
-
"description": "Parts of the description the column contract cannot express, and how the rule handles them.
|
|
13905
|
+
"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."
|
|
13700
13906
|
},
|
|
13701
13907
|
"compiled_by": {
|
|
13702
13908
|
"type": "string",
|
|
13703
13909
|
"description": "An opaque name for the version of the rule compiler that produced the rule, such as rule-compiler-v1. Send it back as compiled_by when you save the rule."
|
|
13704
13910
|
},
|
|
13911
|
+
"breadth": {
|
|
13912
|
+
"description": "How many of your recent emails its conditions hold for, counted over your own mailboxes whatever scope you save it with, warning or not. Null when not counted in full: a rule with questions, headers-only access, no test of it possible as you, or a count that failed or ran out of time. Writes ignore it, so a compile answer (without include_sql) can be sent back unchanged.",
|
|
13913
|
+
"anyOf": [{
|
|
13914
|
+
"type": "object",
|
|
13915
|
+
"properties": {
|
|
13916
|
+
"checked": {
|
|
13917
|
+
"type": "integer",
|
|
13918
|
+
"minimum": 0,
|
|
13919
|
+
"description": "Classified emails the conditions were decided for: of the newest 200 from the last 30 days in your own mailboxes (for a grant rule, those you may share), the ones you can still read."
|
|
13920
|
+
},
|
|
13921
|
+
"matched": {
|
|
13922
|
+
"type": "integer",
|
|
13923
|
+
"minimum": 0,
|
|
13924
|
+
"description": "Of those, how many they hold for (for a do-not-share rule, one they cannot decide counts)."
|
|
13925
|
+
}
|
|
13926
|
+
},
|
|
13927
|
+
"required": ["checked", "matched"]
|
|
13928
|
+
}, { "type": "null" }]
|
|
13929
|
+
},
|
|
13705
13930
|
"sql": {
|
|
13706
13931
|
"description": "Present only when include_sql was true.",
|
|
13707
13932
|
"type": "object",
|
|
@@ -13736,7 +13961,7 @@ const operationManifest = [
|
|
|
13736
13961
|
"binaryResponse": false,
|
|
13737
13962
|
"bodyRequired": true,
|
|
13738
13963
|
"command": "create-email-rule",
|
|
13739
|
-
"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.",
|
|
13964
|
+
"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.",
|
|
13740
13965
|
"hasJsonBody": true,
|
|
13741
13966
|
"method": "POST",
|
|
13742
13967
|
"operationId": "createEmailRule",
|
|
@@ -13767,9 +13992,13 @@ const operationManifest = [
|
|
|
13767
13992
|
"enum": ["grant", "do_not_share"]
|
|
13768
13993
|
},
|
|
13769
13994
|
"state": {
|
|
13770
|
-
"description": "observe (the default) records what the rule would match and grants or blocks nothing; draft is not evaluated.",
|
|
13995
|
+
"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.",
|
|
13771
13996
|
"type": "string",
|
|
13772
|
-
"enum": [
|
|
13997
|
+
"enum": [
|
|
13998
|
+
"draft",
|
|
13999
|
+
"observe",
|
|
14000
|
+
"active"
|
|
14001
|
+
]
|
|
13773
14002
|
},
|
|
13774
14003
|
"evaluation_scope": {
|
|
13775
14004
|
"type": "string",
|
|
@@ -13830,6 +14059,112 @@ const operationManifest = [
|
|
|
13830
14059
|
"additionalProperties": false,
|
|
13831
14060
|
"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."
|
|
13832
14061
|
},
|
|
14062
|
+
"expectation_samples": {
|
|
14063
|
+
"description": "Made-up emails it must match or not (at most 20).",
|
|
14064
|
+
"minItems": 1,
|
|
14065
|
+
"maxItems": 20,
|
|
14066
|
+
"type": "array",
|
|
14067
|
+
"items": {
|
|
14068
|
+
"type": "object",
|
|
14069
|
+
"properties": {
|
|
14070
|
+
"expected": {
|
|
14071
|
+
"type": "string",
|
|
14072
|
+
"enum": ["match", "no_match"]
|
|
14073
|
+
},
|
|
14074
|
+
"label": {
|
|
14075
|
+
"type": ["string", "null"],
|
|
14076
|
+
"minLength": 1,
|
|
14077
|
+
"maxLength": 200,
|
|
14078
|
+
"pattern": "^[^\\u0000]*$",
|
|
14079
|
+
"description": "What the email stands for, such as a personal medical bill."
|
|
14080
|
+
},
|
|
14081
|
+
"email": {
|
|
14082
|
+
"type": "object",
|
|
14083
|
+
"properties": {
|
|
14084
|
+
"from": {
|
|
14085
|
+
"type": "object",
|
|
14086
|
+
"properties": {
|
|
14087
|
+
"address": {
|
|
14088
|
+
"type": "string",
|
|
14089
|
+
"maxLength": 254,
|
|
14090
|
+
"format": "email",
|
|
14091
|
+
"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,}$"
|
|
14092
|
+
},
|
|
14093
|
+
"name": {
|
|
14094
|
+
"type": "string",
|
|
14095
|
+
"maxLength": 200,
|
|
14096
|
+
"pattern": "^[^\\u0000]*$"
|
|
14097
|
+
}
|
|
14098
|
+
},
|
|
14099
|
+
"required": ["address"]
|
|
14100
|
+
},
|
|
14101
|
+
"to": {
|
|
14102
|
+
"minItems": 1,
|
|
14103
|
+
"maxItems": 10,
|
|
14104
|
+
"type": "array",
|
|
14105
|
+
"items": {
|
|
14106
|
+
"type": "string",
|
|
14107
|
+
"maxLength": 254,
|
|
14108
|
+
"format": "email",
|
|
14109
|
+
"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,}$"
|
|
14110
|
+
}
|
|
14111
|
+
},
|
|
14112
|
+
"cc": {
|
|
14113
|
+
"maxItems": 10,
|
|
14114
|
+
"type": "array",
|
|
14115
|
+
"items": {
|
|
14116
|
+
"type": "string",
|
|
14117
|
+
"maxLength": 254,
|
|
14118
|
+
"format": "email",
|
|
14119
|
+
"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,}$"
|
|
14120
|
+
}
|
|
14121
|
+
},
|
|
14122
|
+
"subject": {
|
|
14123
|
+
"type": "string",
|
|
14124
|
+
"maxLength": 998,
|
|
14125
|
+
"pattern": "^[^\\u0000]*$"
|
|
14126
|
+
},
|
|
14127
|
+
"text": {
|
|
14128
|
+
"type": "string",
|
|
14129
|
+
"minLength": 1,
|
|
14130
|
+
"maxLength": 4e3,
|
|
14131
|
+
"pattern": "^[^\\u0000]*$"
|
|
14132
|
+
},
|
|
14133
|
+
"attachment_types": {
|
|
14134
|
+
"maxItems": 10,
|
|
14135
|
+
"type": "array",
|
|
14136
|
+
"items": {
|
|
14137
|
+
"type": "string",
|
|
14138
|
+
"minLength": 1,
|
|
14139
|
+
"maxLength": 255,
|
|
14140
|
+
"pattern": "^[^\\u0000]*$"
|
|
14141
|
+
}
|
|
14142
|
+
},
|
|
14143
|
+
"automated": { "type": "boolean" },
|
|
14144
|
+
"from_mailing_list": { "type": "boolean" },
|
|
14145
|
+
"spam_score": {
|
|
14146
|
+
"type": "number",
|
|
14147
|
+
"minimum": -1e3,
|
|
14148
|
+
"maximum": 1e3
|
|
14149
|
+
}
|
|
14150
|
+
},
|
|
14151
|
+
"required": [
|
|
14152
|
+
"from",
|
|
14153
|
+
"to",
|
|
14154
|
+
"text"
|
|
14155
|
+
],
|
|
14156
|
+
"description": "A made-up email, as POST /email-rules/test-sample takes it."
|
|
14157
|
+
},
|
|
14158
|
+
"id": {
|
|
14159
|
+
"type": "string",
|
|
14160
|
+
"format": "uuid"
|
|
14161
|
+
},
|
|
14162
|
+
"created_at": { "type": "string" }
|
|
14163
|
+
},
|
|
14164
|
+
"required": ["expected", "email"],
|
|
14165
|
+
"additionalProperties": false
|
|
14166
|
+
}
|
|
14167
|
+
},
|
|
13833
14168
|
"english": {
|
|
13834
14169
|
"type": "string",
|
|
13835
14170
|
"minLength": 1,
|
|
@@ -13897,6 +14232,25 @@ const operationManifest = [
|
|
|
13897
14232
|
"type": "string",
|
|
13898
14233
|
"minLength": 1,
|
|
13899
14234
|
"maxLength": 64
|
|
14235
|
+
},
|
|
14236
|
+
"breadth": {
|
|
14237
|
+
"description": "Ignored (the compile answer's breadth).",
|
|
14238
|
+
"anyOf": [{
|
|
14239
|
+
"type": "object",
|
|
14240
|
+
"properties": {
|
|
14241
|
+
"checked": {
|
|
14242
|
+
"type": "integer",
|
|
14243
|
+
"minimum": 0,
|
|
14244
|
+
"description": "Classified emails the conditions were decided for: of the newest 200 from the last 30 days in your own mailboxes (for a grant rule, those you may share), the ones you can still read."
|
|
14245
|
+
},
|
|
14246
|
+
"matched": {
|
|
14247
|
+
"type": "integer",
|
|
14248
|
+
"minimum": 0,
|
|
14249
|
+
"description": "Of those, how many they hold for (for a do-not-share rule, one they cannot decide counts)."
|
|
14250
|
+
}
|
|
14251
|
+
},
|
|
14252
|
+
"required": ["checked", "matched"]
|
|
14253
|
+
}, { "type": "null" }]
|
|
13900
14254
|
}
|
|
13901
14255
|
},
|
|
13902
14256
|
"required": [
|
|
@@ -13927,6 +14281,10 @@ const operationManifest = [
|
|
|
13927
14281
|
"type": "string",
|
|
13928
14282
|
"enum": ["grant", "do_not_share"]
|
|
13929
14283
|
},
|
|
14284
|
+
"protection_note": {
|
|
14285
|
+
"description": "On a do_not_share rule only: it protects nothing yet (not active), or a classifier fact narrows it.",
|
|
14286
|
+
"type": "string"
|
|
14287
|
+
},
|
|
13930
14288
|
"evaluation_scope": {
|
|
13931
14289
|
"description": "Whose mail it runs over, as stored (GET /email-rules/{id}/scope: what that is now).",
|
|
13932
14290
|
"type": "string",
|
|
@@ -13957,10 +14315,37 @@ const operationManifest = [
|
|
|
13957
14315
|
"type": ["integer", "null"],
|
|
13958
14316
|
"exclusiveMinimum": 0
|
|
13959
14317
|
},
|
|
14318
|
+
"revision": {
|
|
14319
|
+
"description": "Moves on by one with every update. Send it as if_revision.",
|
|
14320
|
+
"type": "integer",
|
|
14321
|
+
"exclusiveMinimum": 0
|
|
14322
|
+
},
|
|
13960
14323
|
"author_user_id": {
|
|
13961
14324
|
"type": "string",
|
|
13962
14325
|
"format": "uuid"
|
|
13963
14326
|
},
|
|
14327
|
+
"author": {
|
|
14328
|
+
"type": "object",
|
|
14329
|
+
"properties": {
|
|
14330
|
+
"user_id": {
|
|
14331
|
+
"type": "string",
|
|
14332
|
+
"format": "uuid"
|
|
14333
|
+
},
|
|
14334
|
+
"name": {
|
|
14335
|
+
"description": "Their name; null unless you can list members or this is you, and when they have none set.",
|
|
14336
|
+
"type": ["string", "null"]
|
|
14337
|
+
},
|
|
14338
|
+
"email": {
|
|
14339
|
+
"description": "Sign-in email; null unless you can list member emails or this is you.",
|
|
14340
|
+
"type": ["string", "null"]
|
|
14341
|
+
}
|
|
14342
|
+
},
|
|
14343
|
+
"required": [
|
|
14344
|
+
"user_id",
|
|
14345
|
+
"name",
|
|
14346
|
+
"email"
|
|
14347
|
+
]
|
|
14348
|
+
},
|
|
13964
14349
|
"is_author": {
|
|
13965
14350
|
"type": "boolean",
|
|
13966
14351
|
"description": "Whether you are its author, in your current membership. Only its author edits it; others who see it may only disable or delete it (while shared, only if they manage members)."
|
|
@@ -13973,91 +14358,94 @@ const operationManifest = [
|
|
|
13973
14358
|
"type": "string",
|
|
13974
14359
|
"format": "date-time"
|
|
13975
14360
|
},
|
|
13976
|
-
"current": {
|
|
13977
|
-
"
|
|
13978
|
-
"
|
|
13979
|
-
"
|
|
13980
|
-
|
|
13981
|
-
"
|
|
13982
|
-
|
|
13983
|
-
|
|
13984
|
-
"tree": {
|
|
13985
|
-
"type": "object",
|
|
13986
|
-
"properties": {
|
|
13987
|
-
"version": {
|
|
13988
|
-
"type": "number",
|
|
13989
|
-
"const": 1
|
|
13990
|
-
},
|
|
13991
|
-
"vocabulary": {
|
|
13992
|
-
"type": "string",
|
|
13993
|
-
"description": "Vocabulary version, such as v1."
|
|
13994
|
-
},
|
|
13995
|
-
"when": {
|
|
13996
|
-
"type": "object",
|
|
13997
|
-
"propertyNames": { "type": "string" },
|
|
13998
|
-
"additionalProperties": {},
|
|
13999
|
-
"description": "A condition {column, op, value?, min_confidence?} (see GET /email-rules/vocabulary) or {all: [...]}, {any: [...]}, {not: ...}."
|
|
14000
|
-
},
|
|
14001
|
-
"questions": {
|
|
14002
|
-
"type": "array",
|
|
14003
|
-
"items": { "type": "string" },
|
|
14004
|
-
"description": "Yes/no questions asked once the conditions match; all must be yes."
|
|
14005
|
-
}
|
|
14361
|
+
"current": {
|
|
14362
|
+
"description": "Its current version: its text (english, tree, explanation, assumptions, warnings) and who compiled it. current_version is its number. GET /email-rules lists rules without it.",
|
|
14363
|
+
"anyOf": [{
|
|
14364
|
+
"type": "object",
|
|
14365
|
+
"properties": {
|
|
14366
|
+
"version": {
|
|
14367
|
+
"type": "integer",
|
|
14368
|
+
"exclusiveMinimum": 0
|
|
14006
14369
|
},
|
|
14007
|
-
"
|
|
14008
|
-
|
|
14009
|
-
"
|
|
14010
|
-
"
|
|
14011
|
-
|
|
14012
|
-
|
|
14013
|
-
|
|
14014
|
-
|
|
14015
|
-
|
|
14016
|
-
|
|
14017
|
-
|
|
14018
|
-
|
|
14019
|
-
|
|
14020
|
-
|
|
14021
|
-
|
|
14022
|
-
|
|
14023
|
-
|
|
14024
|
-
|
|
14025
|
-
|
|
14026
|
-
|
|
14027
|
-
|
|
14028
|
-
|
|
14029
|
-
|
|
14030
|
-
"format": "date-time"
|
|
14031
|
-
},
|
|
14032
|
-
"sql": {
|
|
14033
|
-
"description": "Present only when include_sql was true.",
|
|
14034
|
-
"type": "object",
|
|
14035
|
-
"properties": {
|
|
14036
|
-
"expression": {
|
|
14037
|
-
"type": "string",
|
|
14038
|
-
"description": "A parameterized boolean expression over one row per email."
|
|
14370
|
+
"english": { "type": "string" },
|
|
14371
|
+
"tree": {
|
|
14372
|
+
"type": "object",
|
|
14373
|
+
"properties": {
|
|
14374
|
+
"version": {
|
|
14375
|
+
"type": "number",
|
|
14376
|
+
"const": 1
|
|
14377
|
+
},
|
|
14378
|
+
"vocabulary": {
|
|
14379
|
+
"type": "string",
|
|
14380
|
+
"description": "Vocabulary version, such as v1."
|
|
14381
|
+
},
|
|
14382
|
+
"when": {
|
|
14383
|
+
"type": "object",
|
|
14384
|
+
"propertyNames": { "type": "string" },
|
|
14385
|
+
"additionalProperties": {},
|
|
14386
|
+
"description": "A condition {column, op, value?, min_confidence?} (see GET /email-rules/vocabulary) or {all: [...]}, {any: [...]}, {not: ...}."
|
|
14387
|
+
},
|
|
14388
|
+
"questions": {
|
|
14389
|
+
"type": "array",
|
|
14390
|
+
"items": { "type": "string" },
|
|
14391
|
+
"description": "Yes/no questions asked once the conditions match; all must be yes."
|
|
14392
|
+
}
|
|
14039
14393
|
},
|
|
14040
|
-
"
|
|
14041
|
-
"
|
|
14042
|
-
"
|
|
14043
|
-
"
|
|
14044
|
-
|
|
14394
|
+
"required": [
|
|
14395
|
+
"version",
|
|
14396
|
+
"vocabulary",
|
|
14397
|
+
"when",
|
|
14398
|
+
"questions"
|
|
14399
|
+
]
|
|
14045
14400
|
},
|
|
14046
|
-
"
|
|
14047
|
-
|
|
14048
|
-
|
|
14049
|
-
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
14055
|
-
|
|
14056
|
-
|
|
14057
|
-
|
|
14058
|
-
|
|
14059
|
-
|
|
14060
|
-
|
|
14401
|
+
"explanation": { "type": "string" },
|
|
14402
|
+
"assumptions": {
|
|
14403
|
+
"type": "array",
|
|
14404
|
+
"items": { "type": "string" }
|
|
14405
|
+
},
|
|
14406
|
+
"warnings": {
|
|
14407
|
+
"type": "array",
|
|
14408
|
+
"items": { "type": "string" }
|
|
14409
|
+
},
|
|
14410
|
+
"compiled_by": {
|
|
14411
|
+
"type": "string",
|
|
14412
|
+
"description": "Who produced the tree, as stated when saved (not verified), such as rule-compiler-v1 or member."
|
|
14413
|
+
},
|
|
14414
|
+
"vocabulary": { "type": "string" },
|
|
14415
|
+
"created_at": {
|
|
14416
|
+
"type": "string",
|
|
14417
|
+
"format": "date-time"
|
|
14418
|
+
},
|
|
14419
|
+
"sql": {
|
|
14420
|
+
"description": "Present only when include_sql was true.",
|
|
14421
|
+
"type": "object",
|
|
14422
|
+
"properties": {
|
|
14423
|
+
"expression": {
|
|
14424
|
+
"type": "string",
|
|
14425
|
+
"description": "A parameterized boolean expression over one row per email."
|
|
14426
|
+
},
|
|
14427
|
+
"params": {
|
|
14428
|
+
"type": "array",
|
|
14429
|
+
"items": {},
|
|
14430
|
+
"description": "The values bound to $1, $2 and so on."
|
|
14431
|
+
}
|
|
14432
|
+
},
|
|
14433
|
+
"required": ["expression", "params"]
|
|
14434
|
+
}
|
|
14435
|
+
},
|
|
14436
|
+
"required": [
|
|
14437
|
+
"version",
|
|
14438
|
+
"english",
|
|
14439
|
+
"tree",
|
|
14440
|
+
"explanation",
|
|
14441
|
+
"assumptions",
|
|
14442
|
+
"warnings",
|
|
14443
|
+
"compiled_by",
|
|
14444
|
+
"vocabulary",
|
|
14445
|
+
"created_at"
|
|
14446
|
+
]
|
|
14447
|
+
}, { "type": "null" }]
|
|
14448
|
+
},
|
|
14061
14449
|
"scope_warnings": {
|
|
14062
14450
|
"description": "Names in scope_selection that match nothing this organization receives mail at (a typo, or an inbox not yet in use). Present only when there are some.",
|
|
14063
14451
|
"type": "array",
|
|
@@ -14088,8 +14476,114 @@ const operationManifest = [
|
|
|
14088
14476
|
]
|
|
14089
14477
|
}
|
|
14090
14478
|
},
|
|
14479
|
+
"expectation_samples": {
|
|
14480
|
+
"description": "Its saved made-up emails, when this write set or added some.",
|
|
14481
|
+
"type": "array",
|
|
14482
|
+
"items": {
|
|
14483
|
+
"type": "object",
|
|
14484
|
+
"properties": {
|
|
14485
|
+
"id": {
|
|
14486
|
+
"type": "string",
|
|
14487
|
+
"format": "uuid"
|
|
14488
|
+
},
|
|
14489
|
+
"expected": {
|
|
14490
|
+
"type": "string",
|
|
14491
|
+
"enum": ["match", "no_match"]
|
|
14492
|
+
},
|
|
14493
|
+
"label": { "type": ["string", "null"] },
|
|
14494
|
+
"email": {
|
|
14495
|
+
"type": "object",
|
|
14496
|
+
"properties": {
|
|
14497
|
+
"from": {
|
|
14498
|
+
"type": "object",
|
|
14499
|
+
"properties": {
|
|
14500
|
+
"address": {
|
|
14501
|
+
"type": "string",
|
|
14502
|
+
"maxLength": 254,
|
|
14503
|
+
"format": "email",
|
|
14504
|
+
"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,}$"
|
|
14505
|
+
},
|
|
14506
|
+
"name": {
|
|
14507
|
+
"type": "string",
|
|
14508
|
+
"maxLength": 200,
|
|
14509
|
+
"pattern": "^[^\\u0000]*$"
|
|
14510
|
+
}
|
|
14511
|
+
},
|
|
14512
|
+
"required": ["address"]
|
|
14513
|
+
},
|
|
14514
|
+
"to": {
|
|
14515
|
+
"minItems": 1,
|
|
14516
|
+
"maxItems": 10,
|
|
14517
|
+
"type": "array",
|
|
14518
|
+
"items": {
|
|
14519
|
+
"type": "string",
|
|
14520
|
+
"maxLength": 254,
|
|
14521
|
+
"format": "email",
|
|
14522
|
+
"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,}$"
|
|
14523
|
+
}
|
|
14524
|
+
},
|
|
14525
|
+
"cc": {
|
|
14526
|
+
"maxItems": 10,
|
|
14527
|
+
"type": "array",
|
|
14528
|
+
"items": {
|
|
14529
|
+
"type": "string",
|
|
14530
|
+
"maxLength": 254,
|
|
14531
|
+
"format": "email",
|
|
14532
|
+
"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,}$"
|
|
14533
|
+
}
|
|
14534
|
+
},
|
|
14535
|
+
"subject": {
|
|
14536
|
+
"type": "string",
|
|
14537
|
+
"maxLength": 998,
|
|
14538
|
+
"pattern": "^[^\\u0000]*$"
|
|
14539
|
+
},
|
|
14540
|
+
"text": {
|
|
14541
|
+
"type": "string",
|
|
14542
|
+
"minLength": 1,
|
|
14543
|
+
"maxLength": 4e3,
|
|
14544
|
+
"pattern": "^[^\\u0000]*$"
|
|
14545
|
+
},
|
|
14546
|
+
"attachment_types": {
|
|
14547
|
+
"maxItems": 10,
|
|
14548
|
+
"type": "array",
|
|
14549
|
+
"items": {
|
|
14550
|
+
"type": "string",
|
|
14551
|
+
"minLength": 1,
|
|
14552
|
+
"maxLength": 255,
|
|
14553
|
+
"pattern": "^[^\\u0000]*$"
|
|
14554
|
+
}
|
|
14555
|
+
},
|
|
14556
|
+
"automated": { "type": "boolean" },
|
|
14557
|
+
"from_mailing_list": { "type": "boolean" },
|
|
14558
|
+
"spam_score": {
|
|
14559
|
+
"type": "number",
|
|
14560
|
+
"minimum": -1e3,
|
|
14561
|
+
"maximum": 1e3
|
|
14562
|
+
}
|
|
14563
|
+
},
|
|
14564
|
+
"required": [
|
|
14565
|
+
"from",
|
|
14566
|
+
"to",
|
|
14567
|
+
"text"
|
|
14568
|
+
],
|
|
14569
|
+
"description": "A made-up email, as POST /email-rules/test-sample takes it."
|
|
14570
|
+
},
|
|
14571
|
+
"created_at": {
|
|
14572
|
+
"type": "string",
|
|
14573
|
+
"format": "date-time"
|
|
14574
|
+
}
|
|
14575
|
+
},
|
|
14576
|
+
"required": [
|
|
14577
|
+
"id",
|
|
14578
|
+
"expected",
|
|
14579
|
+
"label",
|
|
14580
|
+
"email",
|
|
14581
|
+
"created_at"
|
|
14582
|
+
]
|
|
14583
|
+
}
|
|
14584
|
+
},
|
|
14091
14585
|
"expectations_check": {
|
|
14092
|
-
"description": "After a write that saved a version or changed its expectations, if it has some:
|
|
14586
|
+
"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.",
|
|
14093
14587
|
"type": "object",
|
|
14094
14588
|
"properties": {
|
|
14095
14589
|
"version": {
|
|
@@ -14101,7 +14595,7 @@ const operationManifest = [
|
|
|
14101
14595
|
"type": ["boolean", "null"]
|
|
14102
14596
|
},
|
|
14103
14597
|
"decided": {
|
|
14104
|
-
"description": "Whether every expectation was judged (undecided
|
|
14598
|
+
"description": "Whether every expectation was judged (undecided and undecided_samples are empty). Absent from a server that predates it.",
|
|
14105
14599
|
"type": "boolean"
|
|
14106
14600
|
},
|
|
14107
14601
|
"failed": {
|
|
@@ -14117,6 +14611,26 @@ const operationManifest = [
|
|
|
14117
14611
|
"type": "string",
|
|
14118
14612
|
"format": "uuid"
|
|
14119
14613
|
}
|
|
14614
|
+
},
|
|
14615
|
+
"failed_samples": {
|
|
14616
|
+
"description": "Made-up emails that failed, by index in the set judged (saved oldest first, then added; or as sent).",
|
|
14617
|
+
"type": "array",
|
|
14618
|
+
"items": {
|
|
14619
|
+
"type": "integer",
|
|
14620
|
+
"minimum": 0
|
|
14621
|
+
}
|
|
14622
|
+
},
|
|
14623
|
+
"undecided_samples": {
|
|
14624
|
+
"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.",
|
|
14625
|
+
"type": "array",
|
|
14626
|
+
"items": {
|
|
14627
|
+
"type": "integer",
|
|
14628
|
+
"minimum": 0
|
|
14629
|
+
}
|
|
14630
|
+
},
|
|
14631
|
+
"note": {
|
|
14632
|
+
"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.",
|
|
14633
|
+
"type": "string"
|
|
14120
14634
|
}
|
|
14121
14635
|
},
|
|
14122
14636
|
"required": [
|
|
@@ -14125,6 +14639,41 @@ const operationManifest = [
|
|
|
14125
14639
|
"failed",
|
|
14126
14640
|
"undecided"
|
|
14127
14641
|
]
|
|
14642
|
+
},
|
|
14643
|
+
"restored_from": {
|
|
14644
|
+
"description": "When this write saved an earlier version again (restore_version): which, and that the scope is not part of a version.",
|
|
14645
|
+
"type": "object",
|
|
14646
|
+
"properties": {
|
|
14647
|
+
"version": {
|
|
14648
|
+
"type": "integer",
|
|
14649
|
+
"exclusiveMinimum": 0
|
|
14650
|
+
},
|
|
14651
|
+
"note": { "type": "string" }
|
|
14652
|
+
},
|
|
14653
|
+
"required": ["version", "note"]
|
|
14654
|
+
},
|
|
14655
|
+
"affects": {
|
|
14656
|
+
"description": "After an update that saved a version: API keys listing the rule, the webhooks they own, and shares this write ended. The rule keeps its state. Absent if not read.",
|
|
14657
|
+
"type": "object",
|
|
14658
|
+
"properties": {
|
|
14659
|
+
"keys": {
|
|
14660
|
+
"type": "integer",
|
|
14661
|
+
"minimum": 0
|
|
14662
|
+
},
|
|
14663
|
+
"endpoints": {
|
|
14664
|
+
"type": "integer",
|
|
14665
|
+
"minimum": 0
|
|
14666
|
+
},
|
|
14667
|
+
"shares_ended": {
|
|
14668
|
+
"type": "integer",
|
|
14669
|
+
"minimum": 0
|
|
14670
|
+
}
|
|
14671
|
+
},
|
|
14672
|
+
"required": [
|
|
14673
|
+
"keys",
|
|
14674
|
+
"endpoints",
|
|
14675
|
+
"shares_ended"
|
|
14676
|
+
]
|
|
14128
14677
|
}
|
|
14129
14678
|
},
|
|
14130
14679
|
"required": [
|
|
@@ -14258,6 +14807,25 @@ const operationManifest = [
|
|
|
14258
14807
|
"type": "string",
|
|
14259
14808
|
"minLength": 1,
|
|
14260
14809
|
"maxLength": 64
|
|
14810
|
+
},
|
|
14811
|
+
"breadth": {
|
|
14812
|
+
"description": "Ignored (the compile answer's breadth).",
|
|
14813
|
+
"anyOf": [{
|
|
14814
|
+
"type": "object",
|
|
14815
|
+
"properties": {
|
|
14816
|
+
"checked": {
|
|
14817
|
+
"type": "integer",
|
|
14818
|
+
"minimum": 0,
|
|
14819
|
+
"description": "Classified emails the conditions were decided for: of the newest 200 from the last 30 days in your own mailboxes (for a grant rule, those you may share), the ones you can still read."
|
|
14820
|
+
},
|
|
14821
|
+
"matched": {
|
|
14822
|
+
"type": "integer",
|
|
14823
|
+
"minimum": 0,
|
|
14824
|
+
"description": "Of those, how many they hold for (for a do-not-share rule, one they cannot decide counts)."
|
|
14825
|
+
}
|
|
14826
|
+
},
|
|
14827
|
+
"required": ["checked", "matched"]
|
|
14828
|
+
}, { "type": "null" }]
|
|
14261
14829
|
}
|
|
14262
14830
|
},
|
|
14263
14831
|
"required": [
|
|
@@ -14415,6 +14983,14 @@ const operationManifest = [
|
|
|
14415
14983
|
"description": "Why the member asked declined it; null unless they gave a reason when declining.",
|
|
14416
14984
|
"type": ["string", "null"]
|
|
14417
14985
|
},
|
|
14986
|
+
"edited_on_accept": {
|
|
14987
|
+
"description": "Once accepted: whether the rule was changed first, as on the share. Null until then, or when unreadable.",
|
|
14988
|
+
"type": ["boolean", "null"]
|
|
14989
|
+
},
|
|
14990
|
+
"notice": {
|
|
14991
|
+
"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.",
|
|
14992
|
+
"type": ["string", "null"]
|
|
14993
|
+
},
|
|
14418
14994
|
"target_can_accept": {
|
|
14419
14995
|
"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.",
|
|
14420
14996
|
"type": ["boolean", "null"]
|
|
@@ -14635,6 +15211,14 @@ const operationManifest = [
|
|
|
14635
15211
|
"decline_reason": {
|
|
14636
15212
|
"description": "Why the member asked declined it; null unless they gave a reason when declining.",
|
|
14637
15213
|
"type": ["string", "null"]
|
|
15214
|
+
},
|
|
15215
|
+
"edited_on_accept": {
|
|
15216
|
+
"description": "Once accepted: whether the rule was changed first, as on the share. Null until then, or when unreadable.",
|
|
15217
|
+
"type": ["boolean", "null"]
|
|
15218
|
+
},
|
|
15219
|
+
"notice": {
|
|
15220
|
+
"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.",
|
|
15221
|
+
"type": ["string", "null"]
|
|
14638
15222
|
}
|
|
14639
15223
|
},
|
|
14640
15224
|
"required": [
|
|
@@ -14792,11 +15376,325 @@ const operationManifest = [
|
|
|
14792
15376
|
"tag": "Email Rules",
|
|
14793
15377
|
"tagCommand": "email-rules"
|
|
14794
15378
|
},
|
|
15379
|
+
{
|
|
15380
|
+
"binaryResponse": false,
|
|
15381
|
+
"bodyRequired": true,
|
|
15382
|
+
"command": "generate-look-alikes",
|
|
15383
|
+
"description": "Writes made-up emails that come close to what a grant rule describes but are private (a personal medical bill, pay for named people, a personal purchase, personal banking or tax, or another near case), and reports what the rule decides for each, as test-sample would. failed means the rule matched it, so it would be shared. Nothing is stored and no mail is read. Keep the ones it must never match 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.",
|
|
15384
|
+
"hasJsonBody": true,
|
|
15385
|
+
"method": "POST",
|
|
15386
|
+
"operationId": "generateLookAlikes",
|
|
15387
|
+
"path": "/email-rules/look-alikes",
|
|
15388
|
+
"pathParams": [],
|
|
15389
|
+
"queryParams": [],
|
|
15390
|
+
"headerParams": [{
|
|
15391
|
+
"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.",
|
|
15392
|
+
"enum": null,
|
|
15393
|
+
"name": "Idempotency-Key",
|
|
15394
|
+
"required": false,
|
|
15395
|
+
"type": "string"
|
|
15396
|
+
}],
|
|
15397
|
+
"permissions": ["mail:read"],
|
|
15398
|
+
"access": null,
|
|
15399
|
+
"requestSchema": {
|
|
15400
|
+
"type": "object",
|
|
15401
|
+
"properties": {
|
|
15402
|
+
"tree": {
|
|
15403
|
+
"type": "object",
|
|
15404
|
+
"properties": {
|
|
15405
|
+
"version": {
|
|
15406
|
+
"type": "number",
|
|
15407
|
+
"const": 1
|
|
15408
|
+
},
|
|
15409
|
+
"vocabulary": {
|
|
15410
|
+
"type": "string",
|
|
15411
|
+
"description": "Vocabulary version, such as v1."
|
|
15412
|
+
},
|
|
15413
|
+
"when": {
|
|
15414
|
+
"type": "object",
|
|
15415
|
+
"propertyNames": { "type": "string" },
|
|
15416
|
+
"additionalProperties": {},
|
|
15417
|
+
"description": "A condition {column, op, value?, min_confidence?} (see GET /email-rules/vocabulary) or {all: [...]}, {any: [...]}, {not: ...}."
|
|
15418
|
+
},
|
|
15419
|
+
"questions": {
|
|
15420
|
+
"description": "Yes/no questions asked once the conditions match; all must be yes. Left out, none.",
|
|
15421
|
+
"type": "array",
|
|
15422
|
+
"items": { "type": "string" }
|
|
15423
|
+
}
|
|
15424
|
+
},
|
|
15425
|
+
"required": [
|
|
15426
|
+
"version",
|
|
15427
|
+
"vocabulary",
|
|
15428
|
+
"when"
|
|
15429
|
+
],
|
|
15430
|
+
"additionalProperties": false
|
|
15431
|
+
},
|
|
15432
|
+
"english": {
|
|
15433
|
+
"description": "What the tree is meant to match; without it, the tree is described in words.",
|
|
15434
|
+
"type": "string",
|
|
15435
|
+
"minLength": 1,
|
|
15436
|
+
"maxLength": 2e3
|
|
15437
|
+
},
|
|
15438
|
+
"evaluation_scope": {
|
|
15439
|
+
"type": "string",
|
|
15440
|
+
"enum": [
|
|
15441
|
+
"author",
|
|
15442
|
+
"own_mailboxes",
|
|
15443
|
+
"selected"
|
|
15444
|
+
],
|
|
15445
|
+
"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."
|
|
15446
|
+
},
|
|
15447
|
+
"scope_selection": {
|
|
15448
|
+
"type": "object",
|
|
15449
|
+
"properties": {
|
|
15450
|
+
"addresses": {
|
|
15451
|
+
"maxItems": 100,
|
|
15452
|
+
"type": "array",
|
|
15453
|
+
"items": {
|
|
15454
|
+
"type": "string",
|
|
15455
|
+
"minLength": 3,
|
|
15456
|
+
"maxLength": 320,
|
|
15457
|
+
"pattern": "^[^\\s@]+@[^\\s@]+$"
|
|
15458
|
+
}
|
|
15459
|
+
},
|
|
15460
|
+
"domains": {
|
|
15461
|
+
"maxItems": 50,
|
|
15462
|
+
"type": "array",
|
|
15463
|
+
"items": {
|
|
15464
|
+
"type": "string",
|
|
15465
|
+
"minLength": 1,
|
|
15466
|
+
"maxLength": 253,
|
|
15467
|
+
"pattern": "^[^\\s@]+\\.[^\\s@]+$"
|
|
15468
|
+
}
|
|
15469
|
+
}
|
|
15470
|
+
},
|
|
15471
|
+
"additionalProperties": false,
|
|
15472
|
+
"description": "For a selected rule: inboxes, and domains (each address at them the author reads); each one you can read."
|
|
15473
|
+
},
|
|
15474
|
+
"rule_id": {
|
|
15475
|
+
"description": "A saved rule you can see: its current version and words.",
|
|
15476
|
+
"type": "string",
|
|
15477
|
+
"format": "uuid"
|
|
15478
|
+
},
|
|
15479
|
+
"request_id": {
|
|
15480
|
+
"description": "A rule request you can see: its rule as asked.",
|
|
15481
|
+
"type": "string",
|
|
15482
|
+
"format": "uuid"
|
|
15483
|
+
},
|
|
15484
|
+
"kind": {
|
|
15485
|
+
"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.",
|
|
15486
|
+
"type": "string",
|
|
15487
|
+
"enum": ["grant", "do_not_share"]
|
|
15488
|
+
},
|
|
15489
|
+
"count": {
|
|
15490
|
+
"description": "How many, 1 to 8; 6 by default.",
|
|
15491
|
+
"type": "integer",
|
|
15492
|
+
"minimum": 1,
|
|
15493
|
+
"maximum": 8
|
|
15494
|
+
},
|
|
15495
|
+
"kinds": {
|
|
15496
|
+
"description": "Which kinds, taken in turn; all by default, the private ones first.",
|
|
15497
|
+
"minItems": 1,
|
|
15498
|
+
"maxItems": 5,
|
|
15499
|
+
"type": "array",
|
|
15500
|
+
"items": {
|
|
15501
|
+
"type": "string",
|
|
15502
|
+
"enum": [
|
|
15503
|
+
"medical",
|
|
15504
|
+
"payroll",
|
|
15505
|
+
"personal_purchase",
|
|
15506
|
+
"bank_tax",
|
|
15507
|
+
"other"
|
|
15508
|
+
]
|
|
15509
|
+
}
|
|
15510
|
+
},
|
|
15511
|
+
"to": {
|
|
15512
|
+
"description": "The mailbox they arrive in; by default the rule author's (a request's target's) sign-in address when you may see it, else yours.",
|
|
15513
|
+
"type": "string",
|
|
15514
|
+
"maxLength": 254,
|
|
15515
|
+
"format": "email",
|
|
15516
|
+
"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,}$"
|
|
15517
|
+
}
|
|
15518
|
+
},
|
|
15519
|
+
"additionalProperties": false
|
|
15520
|
+
},
|
|
15521
|
+
"responseSchema": {
|
|
15522
|
+
"type": "object",
|
|
15523
|
+
"properties": {
|
|
15524
|
+
"rule": {
|
|
15525
|
+
"type": "object",
|
|
15526
|
+
"properties": {
|
|
15527
|
+
"id": {
|
|
15528
|
+
"type": ["string", "null"],
|
|
15529
|
+
"format": "uuid"
|
|
15530
|
+
},
|
|
15531
|
+
"version": {
|
|
15532
|
+
"type": ["integer", "null"],
|
|
15533
|
+
"exclusiveMinimum": 0
|
|
15534
|
+
},
|
|
15535
|
+
"vocabulary": { "type": "string" }
|
|
15536
|
+
},
|
|
15537
|
+
"required": [
|
|
15538
|
+
"id",
|
|
15539
|
+
"version",
|
|
15540
|
+
"vocabulary"
|
|
15541
|
+
]
|
|
15542
|
+
},
|
|
15543
|
+
"look_alikes": {
|
|
15544
|
+
"type": "array",
|
|
15545
|
+
"items": {
|
|
15546
|
+
"type": "object",
|
|
15547
|
+
"properties": {
|
|
15548
|
+
"kind": {
|
|
15549
|
+
"type": "string",
|
|
15550
|
+
"enum": [
|
|
15551
|
+
"medical",
|
|
15552
|
+
"payroll",
|
|
15553
|
+
"personal_purchase",
|
|
15554
|
+
"bank_tax",
|
|
15555
|
+
"other"
|
|
15556
|
+
]
|
|
15557
|
+
},
|
|
15558
|
+
"why": {
|
|
15559
|
+
"type": "string",
|
|
15560
|
+
"description": "Why the rule should not match it."
|
|
15561
|
+
},
|
|
15562
|
+
"email": {
|
|
15563
|
+
"type": "object",
|
|
15564
|
+
"properties": {
|
|
15565
|
+
"from": {
|
|
15566
|
+
"type": "object",
|
|
15567
|
+
"properties": {
|
|
15568
|
+
"address": { "type": "string" },
|
|
15569
|
+
"name": { "type": "string" }
|
|
15570
|
+
},
|
|
15571
|
+
"required": ["address"]
|
|
15572
|
+
},
|
|
15573
|
+
"to": {
|
|
15574
|
+
"type": "array",
|
|
15575
|
+
"items": { "type": "string" }
|
|
15576
|
+
},
|
|
15577
|
+
"cc": {
|
|
15578
|
+
"type": "array",
|
|
15579
|
+
"items": { "type": "string" }
|
|
15580
|
+
},
|
|
15581
|
+
"subject": { "type": "string" },
|
|
15582
|
+
"text": { "type": "string" },
|
|
15583
|
+
"attachment_types": {
|
|
15584
|
+
"type": "array",
|
|
15585
|
+
"items": { "type": "string" }
|
|
15586
|
+
},
|
|
15587
|
+
"automated": { "type": "boolean" },
|
|
15588
|
+
"from_mailing_list": { "type": "boolean" }
|
|
15589
|
+
},
|
|
15590
|
+
"required": [
|
|
15591
|
+
"from",
|
|
15592
|
+
"to",
|
|
15593
|
+
"cc",
|
|
15594
|
+
"subject",
|
|
15595
|
+
"text",
|
|
15596
|
+
"attachment_types",
|
|
15597
|
+
"automated",
|
|
15598
|
+
"from_mailing_list"
|
|
15599
|
+
],
|
|
15600
|
+
"description": "A made-up email, as test-sample takes it."
|
|
15601
|
+
},
|
|
15602
|
+
"outcome": {
|
|
15603
|
+
"type": "string",
|
|
15604
|
+
"enum": [
|
|
15605
|
+
"matched",
|
|
15606
|
+
"near_miss",
|
|
15607
|
+
"no_match",
|
|
15608
|
+
"not_decided"
|
|
15609
|
+
]
|
|
15610
|
+
},
|
|
15611
|
+
"status": {
|
|
15612
|
+
"type": "string",
|
|
15613
|
+
"enum": [
|
|
15614
|
+
"passed",
|
|
15615
|
+
"failed",
|
|
15616
|
+
"undecided"
|
|
15617
|
+
],
|
|
15618
|
+
"description": "failed: the rule matched it, so it would be shared. 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)."
|
|
15619
|
+
},
|
|
15620
|
+
"question_answers": {
|
|
15621
|
+
"type": "array",
|
|
15622
|
+
"items": {
|
|
15623
|
+
"type": "object",
|
|
15624
|
+
"properties": {
|
|
15625
|
+
"question": { "type": "string" },
|
|
15626
|
+
"answer": { "type": ["boolean", "null"] },
|
|
15627
|
+
"probability": { "type": ["number", "null"] },
|
|
15628
|
+
"error_class": { "type": "string" }
|
|
15629
|
+
},
|
|
15630
|
+
"required": [
|
|
15631
|
+
"question",
|
|
15632
|
+
"answer",
|
|
15633
|
+
"probability"
|
|
15634
|
+
]
|
|
15635
|
+
}
|
|
15636
|
+
}
|
|
15637
|
+
},
|
|
15638
|
+
"required": [
|
|
15639
|
+
"kind",
|
|
15640
|
+
"why",
|
|
15641
|
+
"email",
|
|
15642
|
+
"outcome",
|
|
15643
|
+
"status",
|
|
15644
|
+
"question_answers"
|
|
15645
|
+
]
|
|
15646
|
+
}
|
|
15647
|
+
},
|
|
15648
|
+
"counts": {
|
|
15649
|
+
"type": "object",
|
|
15650
|
+
"properties": {
|
|
15651
|
+
"passed": {
|
|
15652
|
+
"type": "integer",
|
|
15653
|
+
"minimum": 0
|
|
15654
|
+
},
|
|
15655
|
+
"failed": {
|
|
15656
|
+
"type": "integer",
|
|
15657
|
+
"minimum": 0
|
|
15658
|
+
},
|
|
15659
|
+
"undecided": {
|
|
15660
|
+
"type": "integer",
|
|
15661
|
+
"minimum": 0
|
|
15662
|
+
},
|
|
15663
|
+
"dropped": {
|
|
15664
|
+
"type": "integer",
|
|
15665
|
+
"minimum": 0,
|
|
15666
|
+
"description": "Asked for but not returned: not written, outside the sample bounds, or of a kind not asked for."
|
|
15667
|
+
}
|
|
15668
|
+
},
|
|
15669
|
+
"required": [
|
|
15670
|
+
"passed",
|
|
15671
|
+
"failed",
|
|
15672
|
+
"undecided",
|
|
15673
|
+
"dropped"
|
|
15674
|
+
]
|
|
15675
|
+
},
|
|
15676
|
+
"note": {
|
|
15677
|
+
"type": "string",
|
|
15678
|
+
"description": "That the emails are made up and were never sent or stored."
|
|
15679
|
+
}
|
|
15680
|
+
},
|
|
15681
|
+
"required": [
|
|
15682
|
+
"rule",
|
|
15683
|
+
"look_alikes",
|
|
15684
|
+
"counts",
|
|
15685
|
+
"note"
|
|
15686
|
+
]
|
|
15687
|
+
},
|
|
15688
|
+
"sdkName": "generateLookAlikes",
|
|
15689
|
+
"summary": "Write look-alike emails a rule should not match",
|
|
15690
|
+
"tag": "Email Rules",
|
|
15691
|
+
"tagCommand": "email-rules"
|
|
15692
|
+
},
|
|
14795
15693
|
{
|
|
14796
15694
|
"binaryResponse": false,
|
|
14797
15695
|
"bodyRequired": false,
|
|
14798
15696
|
"command": "get-email-rule",
|
|
14799
|
-
"description": "A rule, its scope (evaluation_scope, and scope_selection for a selected rule), its current version and its saved expectations. What the scope comes to for its author now is GET /email-rules/{id}/scope. The SQL the version renders to is included only when include_sql is true. 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.",
|
|
15697
|
+
"description": "A rule, its scope (evaluation_scope, and scope_selection for a selected rule), its current version and its saved expectations. The current version's text (english, tree, explanation, assumptions, warnings) is under current, and current_version is its number. What the scope comes to for its author now is GET /email-rules/{id}/scope. The SQL the version renders to is included only when include_sql is true. 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.",
|
|
14800
15698
|
"hasJsonBody": false,
|
|
14801
15699
|
"method": "GET",
|
|
14802
15700
|
"operationId": "getEmailRule",
|
|
@@ -14840,6 +15738,10 @@ const operationManifest = [
|
|
|
14840
15738
|
"type": "string",
|
|
14841
15739
|
"enum": ["grant", "do_not_share"]
|
|
14842
15740
|
},
|
|
15741
|
+
"protection_note": {
|
|
15742
|
+
"description": "On a do_not_share rule only: it protects nothing yet (not active), or a classifier fact narrows it.",
|
|
15743
|
+
"type": "string"
|
|
15744
|
+
},
|
|
14843
15745
|
"evaluation_scope": {
|
|
14844
15746
|
"description": "Whose mail it runs over, as stored (GET /email-rules/{id}/scope: what that is now).",
|
|
14845
15747
|
"type": "string",
|
|
@@ -14870,10 +15772,37 @@ const operationManifest = [
|
|
|
14870
15772
|
"type": ["integer", "null"],
|
|
14871
15773
|
"exclusiveMinimum": 0
|
|
14872
15774
|
},
|
|
15775
|
+
"revision": {
|
|
15776
|
+
"description": "Moves on by one with every update. Send it as if_revision.",
|
|
15777
|
+
"type": "integer",
|
|
15778
|
+
"exclusiveMinimum": 0
|
|
15779
|
+
},
|
|
14873
15780
|
"author_user_id": {
|
|
14874
15781
|
"type": "string",
|
|
14875
15782
|
"format": "uuid"
|
|
14876
15783
|
},
|
|
15784
|
+
"author": {
|
|
15785
|
+
"type": "object",
|
|
15786
|
+
"properties": {
|
|
15787
|
+
"user_id": {
|
|
15788
|
+
"type": "string",
|
|
15789
|
+
"format": "uuid"
|
|
15790
|
+
},
|
|
15791
|
+
"name": {
|
|
15792
|
+
"description": "Their name; null unless you can list members or this is you, and when they have none set.",
|
|
15793
|
+
"type": ["string", "null"]
|
|
15794
|
+
},
|
|
15795
|
+
"email": {
|
|
15796
|
+
"description": "Sign-in email; null unless you can list member emails or this is you.",
|
|
15797
|
+
"type": ["string", "null"]
|
|
15798
|
+
}
|
|
15799
|
+
},
|
|
15800
|
+
"required": [
|
|
15801
|
+
"user_id",
|
|
15802
|
+
"name",
|
|
15803
|
+
"email"
|
|
15804
|
+
]
|
|
15805
|
+
},
|
|
14877
15806
|
"is_author": {
|
|
14878
15807
|
"type": "boolean",
|
|
14879
15808
|
"description": "Whether you are its author, in your current membership. Only its author edits it; others who see it may only disable or delete it (while shared, only if they manage members)."
|
|
@@ -14886,91 +15815,94 @@ const operationManifest = [
|
|
|
14886
15815
|
"type": "string",
|
|
14887
15816
|
"format": "date-time"
|
|
14888
15817
|
},
|
|
14889
|
-
"current": {
|
|
14890
|
-
"
|
|
14891
|
-
"
|
|
14892
|
-
"
|
|
14893
|
-
|
|
14894
|
-
"
|
|
14895
|
-
|
|
14896
|
-
|
|
14897
|
-
"tree": {
|
|
14898
|
-
"type": "object",
|
|
14899
|
-
"properties": {
|
|
14900
|
-
"version": {
|
|
14901
|
-
"type": "number",
|
|
14902
|
-
"const": 1
|
|
14903
|
-
},
|
|
14904
|
-
"vocabulary": {
|
|
14905
|
-
"type": "string",
|
|
14906
|
-
"description": "Vocabulary version, such as v1."
|
|
14907
|
-
},
|
|
14908
|
-
"when": {
|
|
14909
|
-
"type": "object",
|
|
14910
|
-
"propertyNames": { "type": "string" },
|
|
14911
|
-
"additionalProperties": {},
|
|
14912
|
-
"description": "A condition {column, op, value?, min_confidence?} (see GET /email-rules/vocabulary) or {all: [...]}, {any: [...]}, {not: ...}."
|
|
14913
|
-
},
|
|
14914
|
-
"questions": {
|
|
14915
|
-
"type": "array",
|
|
14916
|
-
"items": { "type": "string" },
|
|
14917
|
-
"description": "Yes/no questions asked once the conditions match; all must be yes."
|
|
14918
|
-
}
|
|
15818
|
+
"current": {
|
|
15819
|
+
"description": "Its current version: its text (english, tree, explanation, assumptions, warnings) and who compiled it. current_version is its number. GET /email-rules lists rules without it.",
|
|
15820
|
+
"anyOf": [{
|
|
15821
|
+
"type": "object",
|
|
15822
|
+
"properties": {
|
|
15823
|
+
"version": {
|
|
15824
|
+
"type": "integer",
|
|
15825
|
+
"exclusiveMinimum": 0
|
|
14919
15826
|
},
|
|
14920
|
-
"
|
|
14921
|
-
|
|
14922
|
-
"
|
|
14923
|
-
"
|
|
14924
|
-
|
|
14925
|
-
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
|
|
14929
|
-
|
|
14930
|
-
|
|
14931
|
-
|
|
14932
|
-
|
|
14933
|
-
|
|
14934
|
-
|
|
14935
|
-
|
|
14936
|
-
|
|
14937
|
-
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
14941
|
-
|
|
14942
|
-
|
|
14943
|
-
"format": "date-time"
|
|
14944
|
-
},
|
|
14945
|
-
"sql": {
|
|
14946
|
-
"description": "Present only when include_sql was true.",
|
|
14947
|
-
"type": "object",
|
|
14948
|
-
"properties": {
|
|
14949
|
-
"expression": {
|
|
14950
|
-
"type": "string",
|
|
14951
|
-
"description": "A parameterized boolean expression over one row per email."
|
|
15827
|
+
"english": { "type": "string" },
|
|
15828
|
+
"tree": {
|
|
15829
|
+
"type": "object",
|
|
15830
|
+
"properties": {
|
|
15831
|
+
"version": {
|
|
15832
|
+
"type": "number",
|
|
15833
|
+
"const": 1
|
|
15834
|
+
},
|
|
15835
|
+
"vocabulary": {
|
|
15836
|
+
"type": "string",
|
|
15837
|
+
"description": "Vocabulary version, such as v1."
|
|
15838
|
+
},
|
|
15839
|
+
"when": {
|
|
15840
|
+
"type": "object",
|
|
15841
|
+
"propertyNames": { "type": "string" },
|
|
15842
|
+
"additionalProperties": {},
|
|
15843
|
+
"description": "A condition {column, op, value?, min_confidence?} (see GET /email-rules/vocabulary) or {all: [...]}, {any: [...]}, {not: ...}."
|
|
15844
|
+
},
|
|
15845
|
+
"questions": {
|
|
15846
|
+
"type": "array",
|
|
15847
|
+
"items": { "type": "string" },
|
|
15848
|
+
"description": "Yes/no questions asked once the conditions match; all must be yes."
|
|
15849
|
+
}
|
|
14952
15850
|
},
|
|
14953
|
-
"
|
|
14954
|
-
"
|
|
14955
|
-
"
|
|
14956
|
-
"
|
|
14957
|
-
|
|
15851
|
+
"required": [
|
|
15852
|
+
"version",
|
|
15853
|
+
"vocabulary",
|
|
15854
|
+
"when",
|
|
15855
|
+
"questions"
|
|
15856
|
+
]
|
|
14958
15857
|
},
|
|
14959
|
-
"
|
|
14960
|
-
|
|
14961
|
-
|
|
14962
|
-
|
|
14963
|
-
|
|
14964
|
-
|
|
14965
|
-
|
|
14966
|
-
|
|
14967
|
-
|
|
14968
|
-
|
|
14969
|
-
|
|
14970
|
-
|
|
14971
|
-
|
|
14972
|
-
|
|
14973
|
-
|
|
15858
|
+
"explanation": { "type": "string" },
|
|
15859
|
+
"assumptions": {
|
|
15860
|
+
"type": "array",
|
|
15861
|
+
"items": { "type": "string" }
|
|
15862
|
+
},
|
|
15863
|
+
"warnings": {
|
|
15864
|
+
"type": "array",
|
|
15865
|
+
"items": { "type": "string" }
|
|
15866
|
+
},
|
|
15867
|
+
"compiled_by": {
|
|
15868
|
+
"type": "string",
|
|
15869
|
+
"description": "Who produced the tree, as stated when saved (not verified), such as rule-compiler-v1 or member."
|
|
15870
|
+
},
|
|
15871
|
+
"vocabulary": { "type": "string" },
|
|
15872
|
+
"created_at": {
|
|
15873
|
+
"type": "string",
|
|
15874
|
+
"format": "date-time"
|
|
15875
|
+
},
|
|
15876
|
+
"sql": {
|
|
15877
|
+
"description": "Present only when include_sql was true.",
|
|
15878
|
+
"type": "object",
|
|
15879
|
+
"properties": {
|
|
15880
|
+
"expression": {
|
|
15881
|
+
"type": "string",
|
|
15882
|
+
"description": "A parameterized boolean expression over one row per email."
|
|
15883
|
+
},
|
|
15884
|
+
"params": {
|
|
15885
|
+
"type": "array",
|
|
15886
|
+
"items": {},
|
|
15887
|
+
"description": "The values bound to $1, $2 and so on."
|
|
15888
|
+
}
|
|
15889
|
+
},
|
|
15890
|
+
"required": ["expression", "params"]
|
|
15891
|
+
}
|
|
15892
|
+
},
|
|
15893
|
+
"required": [
|
|
15894
|
+
"version",
|
|
15895
|
+
"english",
|
|
15896
|
+
"tree",
|
|
15897
|
+
"explanation",
|
|
15898
|
+
"assumptions",
|
|
15899
|
+
"warnings",
|
|
15900
|
+
"compiled_by",
|
|
15901
|
+
"vocabulary",
|
|
15902
|
+
"created_at"
|
|
15903
|
+
]
|
|
15904
|
+
}, { "type": "null" }]
|
|
15905
|
+
},
|
|
14974
15906
|
"expectations": {
|
|
14975
15907
|
"description": "Its saved expectations, each list by email id.",
|
|
14976
15908
|
"type": "object",
|
|
@@ -14991,6 +15923,112 @@ const operationManifest = [
|
|
|
14991
15923
|
}
|
|
14992
15924
|
},
|
|
14993
15925
|
"required": ["match", "no_match"]
|
|
15926
|
+
},
|
|
15927
|
+
"expectation_samples": {
|
|
15928
|
+
"description": "Its saved made-up emails, oldest first.",
|
|
15929
|
+
"type": "array",
|
|
15930
|
+
"items": {
|
|
15931
|
+
"type": "object",
|
|
15932
|
+
"properties": {
|
|
15933
|
+
"id": {
|
|
15934
|
+
"type": "string",
|
|
15935
|
+
"format": "uuid"
|
|
15936
|
+
},
|
|
15937
|
+
"expected": {
|
|
15938
|
+
"type": "string",
|
|
15939
|
+
"enum": ["match", "no_match"]
|
|
15940
|
+
},
|
|
15941
|
+
"label": { "type": ["string", "null"] },
|
|
15942
|
+
"email": {
|
|
15943
|
+
"type": "object",
|
|
15944
|
+
"properties": {
|
|
15945
|
+
"from": {
|
|
15946
|
+
"type": "object",
|
|
15947
|
+
"properties": {
|
|
15948
|
+
"address": {
|
|
15949
|
+
"type": "string",
|
|
15950
|
+
"maxLength": 254,
|
|
15951
|
+
"format": "email",
|
|
15952
|
+
"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,}$"
|
|
15953
|
+
},
|
|
15954
|
+
"name": {
|
|
15955
|
+
"type": "string",
|
|
15956
|
+
"maxLength": 200,
|
|
15957
|
+
"pattern": "^[^\\u0000]*$"
|
|
15958
|
+
}
|
|
15959
|
+
},
|
|
15960
|
+
"required": ["address"]
|
|
15961
|
+
},
|
|
15962
|
+
"to": {
|
|
15963
|
+
"minItems": 1,
|
|
15964
|
+
"maxItems": 10,
|
|
15965
|
+
"type": "array",
|
|
15966
|
+
"items": {
|
|
15967
|
+
"type": "string",
|
|
15968
|
+
"maxLength": 254,
|
|
15969
|
+
"format": "email",
|
|
15970
|
+
"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,}$"
|
|
15971
|
+
}
|
|
15972
|
+
},
|
|
15973
|
+
"cc": {
|
|
15974
|
+
"maxItems": 10,
|
|
15975
|
+
"type": "array",
|
|
15976
|
+
"items": {
|
|
15977
|
+
"type": "string",
|
|
15978
|
+
"maxLength": 254,
|
|
15979
|
+
"format": "email",
|
|
15980
|
+
"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,}$"
|
|
15981
|
+
}
|
|
15982
|
+
},
|
|
15983
|
+
"subject": {
|
|
15984
|
+
"type": "string",
|
|
15985
|
+
"maxLength": 998,
|
|
15986
|
+
"pattern": "^[^\\u0000]*$"
|
|
15987
|
+
},
|
|
15988
|
+
"text": {
|
|
15989
|
+
"type": "string",
|
|
15990
|
+
"minLength": 1,
|
|
15991
|
+
"maxLength": 4e3,
|
|
15992
|
+
"pattern": "^[^\\u0000]*$"
|
|
15993
|
+
},
|
|
15994
|
+
"attachment_types": {
|
|
15995
|
+
"maxItems": 10,
|
|
15996
|
+
"type": "array",
|
|
15997
|
+
"items": {
|
|
15998
|
+
"type": "string",
|
|
15999
|
+
"minLength": 1,
|
|
16000
|
+
"maxLength": 255,
|
|
16001
|
+
"pattern": "^[^\\u0000]*$"
|
|
16002
|
+
}
|
|
16003
|
+
},
|
|
16004
|
+
"automated": { "type": "boolean" },
|
|
16005
|
+
"from_mailing_list": { "type": "boolean" },
|
|
16006
|
+
"spam_score": {
|
|
16007
|
+
"type": "number",
|
|
16008
|
+
"minimum": -1e3,
|
|
16009
|
+
"maximum": 1e3
|
|
16010
|
+
}
|
|
16011
|
+
},
|
|
16012
|
+
"required": [
|
|
16013
|
+
"from",
|
|
16014
|
+
"to",
|
|
16015
|
+
"text"
|
|
16016
|
+
],
|
|
16017
|
+
"description": "A made-up email, as POST /email-rules/test-sample takes it."
|
|
16018
|
+
},
|
|
16019
|
+
"created_at": {
|
|
16020
|
+
"type": "string",
|
|
16021
|
+
"format": "date-time"
|
|
16022
|
+
}
|
|
16023
|
+
},
|
|
16024
|
+
"required": [
|
|
16025
|
+
"id",
|
|
16026
|
+
"expected",
|
|
16027
|
+
"label",
|
|
16028
|
+
"email",
|
|
16029
|
+
"created_at"
|
|
16030
|
+
]
|
|
16031
|
+
}
|
|
14994
16032
|
}
|
|
14995
16033
|
},
|
|
14996
16034
|
"required": [
|
|
@@ -15014,7 +16052,7 @@ const operationManifest = [
|
|
|
15014
16052
|
"binaryResponse": false,
|
|
15015
16053
|
"bodyRequired": false,
|
|
15016
16054
|
"command": "get-email-rule-request",
|
|
15017
|
-
"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.
|
|
16055
|
+
"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.",
|
|
15018
16056
|
"hasJsonBody": false,
|
|
15019
16057
|
"method": "GET",
|
|
15020
16058
|
"operationId": "getEmailRuleRequest",
|
|
@@ -15178,6 +16216,14 @@ const operationManifest = [
|
|
|
15178
16216
|
"decline_reason": {
|
|
15179
16217
|
"description": "Why the member asked declined it; null unless they gave a reason when declining.",
|
|
15180
16218
|
"type": ["string", "null"]
|
|
16219
|
+
},
|
|
16220
|
+
"edited_on_accept": {
|
|
16221
|
+
"description": "Once accepted: whether the rule was changed first, as on the share. Null until then, or when unreadable.",
|
|
16222
|
+
"type": ["boolean", "null"]
|
|
16223
|
+
},
|
|
16224
|
+
"notice": {
|
|
16225
|
+
"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.",
|
|
16226
|
+
"type": ["string", "null"]
|
|
15181
16227
|
}
|
|
15182
16228
|
},
|
|
15183
16229
|
"required": [
|
|
@@ -16060,6 +17106,14 @@ const operationManifest = [
|
|
|
16060
17106
|
"decline_reason": {
|
|
16061
17107
|
"description": "Why the member asked declined it; null unless they gave a reason when declining.",
|
|
16062
17108
|
"type": ["string", "null"]
|
|
17109
|
+
},
|
|
17110
|
+
"edited_on_accept": {
|
|
17111
|
+
"description": "Once accepted: whether the rule was changed first, as on the share. Null until then, or when unreadable.",
|
|
17112
|
+
"type": ["boolean", "null"]
|
|
17113
|
+
},
|
|
17114
|
+
"notice": {
|
|
17115
|
+
"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.",
|
|
17116
|
+
"type": ["string", "null"]
|
|
16063
17117
|
}
|
|
16064
17118
|
},
|
|
16065
17119
|
"required": [
|
|
@@ -17144,7 +18198,7 @@ const operationManifest = [
|
|
|
17144
18198
|
"binaryResponse": false,
|
|
17145
18199
|
"bodyRequired": false,
|
|
17146
18200
|
"command": "list-email-rules",
|
|
17147
|
-
"description": "The rules the caller can see, newest first: their own, or every member's rules for a caller who reads the whole organization's mail. Each carries its scope (evaluation_scope, and scope_selection for a selected rule). With matched_email_id, only the rules that matched that 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.",
|
|
18201
|
+
"description": "The rules the caller can see, newest first: their own, or every member's rules for a caller who reads the whole organization's mail. Each carries its scope (evaluation_scope, and scope_selection for a selected rule) and current_version, but not the version's text: read that under current from GET /email-rules/{id}. With matched_email_id, only the rules that matched that 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.",
|
|
17148
18202
|
"hasJsonBody": false,
|
|
17149
18203
|
"method": "GET",
|
|
17150
18204
|
"operationId": "listEmailRules",
|
|
@@ -17203,6 +18257,10 @@ const operationManifest = [
|
|
|
17203
18257
|
"type": "string",
|
|
17204
18258
|
"enum": ["grant", "do_not_share"]
|
|
17205
18259
|
},
|
|
18260
|
+
"protection_note": {
|
|
18261
|
+
"description": "On a do_not_share rule only: it protects nothing yet (not active), or a classifier fact narrows it.",
|
|
18262
|
+
"type": "string"
|
|
18263
|
+
},
|
|
17206
18264
|
"evaluation_scope": {
|
|
17207
18265
|
"description": "Whose mail it runs over, as stored (GET /email-rules/{id}/scope: what that is now).",
|
|
17208
18266
|
"type": "string",
|
|
@@ -17233,10 +18291,37 @@ const operationManifest = [
|
|
|
17233
18291
|
"type": ["integer", "null"],
|
|
17234
18292
|
"exclusiveMinimum": 0
|
|
17235
18293
|
},
|
|
18294
|
+
"revision": {
|
|
18295
|
+
"description": "Moves on by one with every update. Send it as if_revision.",
|
|
18296
|
+
"type": "integer",
|
|
18297
|
+
"exclusiveMinimum": 0
|
|
18298
|
+
},
|
|
17236
18299
|
"author_user_id": {
|
|
17237
18300
|
"type": "string",
|
|
17238
18301
|
"format": "uuid"
|
|
17239
18302
|
},
|
|
18303
|
+
"author": {
|
|
18304
|
+
"type": "object",
|
|
18305
|
+
"properties": {
|
|
18306
|
+
"user_id": {
|
|
18307
|
+
"type": "string",
|
|
18308
|
+
"format": "uuid"
|
|
18309
|
+
},
|
|
18310
|
+
"name": {
|
|
18311
|
+
"description": "Their name; null unless you can list members or this is you, and when they have none set.",
|
|
18312
|
+
"type": ["string", "null"]
|
|
18313
|
+
},
|
|
18314
|
+
"email": {
|
|
18315
|
+
"description": "Sign-in email; null unless you can list member emails or this is you.",
|
|
18316
|
+
"type": ["string", "null"]
|
|
18317
|
+
}
|
|
18318
|
+
},
|
|
18319
|
+
"required": [
|
|
18320
|
+
"user_id",
|
|
18321
|
+
"name",
|
|
18322
|
+
"email"
|
|
18323
|
+
]
|
|
18324
|
+
},
|
|
17240
18325
|
"is_author": {
|
|
17241
18326
|
"type": "boolean",
|
|
17242
18327
|
"description": "Whether you are its author, in your current membership. Only its author edits it; others who see it may only disable or delete it (while shared, only if they manage members)."
|
|
@@ -17608,10 +18693,114 @@ const operationManifest = [
|
|
|
17608
18693
|
"type": "string",
|
|
17609
18694
|
"format": "uuid"
|
|
17610
18695
|
}
|
|
18696
|
+
},
|
|
18697
|
+
"samples": {
|
|
18698
|
+
"maxItems": 20,
|
|
18699
|
+
"type": "array",
|
|
18700
|
+
"items": {
|
|
18701
|
+
"type": "object",
|
|
18702
|
+
"properties": {
|
|
18703
|
+
"expected": {
|
|
18704
|
+
"type": "string",
|
|
18705
|
+
"enum": ["match", "no_match"]
|
|
18706
|
+
},
|
|
18707
|
+
"label": {
|
|
18708
|
+
"type": ["string", "null"],
|
|
18709
|
+
"minLength": 1,
|
|
18710
|
+
"maxLength": 200,
|
|
18711
|
+
"pattern": "^[^\\u0000]*$",
|
|
18712
|
+
"description": "What the email stands for, such as a personal medical bill."
|
|
18713
|
+
},
|
|
18714
|
+
"email": {
|
|
18715
|
+
"type": "object",
|
|
18716
|
+
"properties": {
|
|
18717
|
+
"from": {
|
|
18718
|
+
"type": "object",
|
|
18719
|
+
"properties": {
|
|
18720
|
+
"address": {
|
|
18721
|
+
"type": "string",
|
|
18722
|
+
"maxLength": 254,
|
|
18723
|
+
"format": "email",
|
|
18724
|
+
"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,}$"
|
|
18725
|
+
},
|
|
18726
|
+
"name": {
|
|
18727
|
+
"type": "string",
|
|
18728
|
+
"maxLength": 200,
|
|
18729
|
+
"pattern": "^[^\\u0000]*$"
|
|
18730
|
+
}
|
|
18731
|
+
},
|
|
18732
|
+
"required": ["address"]
|
|
18733
|
+
},
|
|
18734
|
+
"to": {
|
|
18735
|
+
"minItems": 1,
|
|
18736
|
+
"maxItems": 10,
|
|
18737
|
+
"type": "array",
|
|
18738
|
+
"items": {
|
|
18739
|
+
"type": "string",
|
|
18740
|
+
"maxLength": 254,
|
|
18741
|
+
"format": "email",
|
|
18742
|
+
"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,}$"
|
|
18743
|
+
}
|
|
18744
|
+
},
|
|
18745
|
+
"cc": {
|
|
18746
|
+
"maxItems": 10,
|
|
18747
|
+
"type": "array",
|
|
18748
|
+
"items": {
|
|
18749
|
+
"type": "string",
|
|
18750
|
+
"maxLength": 254,
|
|
18751
|
+
"format": "email",
|
|
18752
|
+
"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,}$"
|
|
18753
|
+
}
|
|
18754
|
+
},
|
|
18755
|
+
"subject": {
|
|
18756
|
+
"type": "string",
|
|
18757
|
+
"maxLength": 998,
|
|
18758
|
+
"pattern": "^[^\\u0000]*$"
|
|
18759
|
+
},
|
|
18760
|
+
"text": {
|
|
18761
|
+
"type": "string",
|
|
18762
|
+
"minLength": 1,
|
|
18763
|
+
"maxLength": 4e3,
|
|
18764
|
+
"pattern": "^[^\\u0000]*$"
|
|
18765
|
+
},
|
|
18766
|
+
"attachment_types": {
|
|
18767
|
+
"maxItems": 10,
|
|
18768
|
+
"type": "array",
|
|
18769
|
+
"items": {
|
|
18770
|
+
"type": "string",
|
|
18771
|
+
"minLength": 1,
|
|
18772
|
+
"maxLength": 255,
|
|
18773
|
+
"pattern": "^[^\\u0000]*$"
|
|
18774
|
+
}
|
|
18775
|
+
},
|
|
18776
|
+
"automated": { "type": "boolean" },
|
|
18777
|
+
"from_mailing_list": { "type": "boolean" },
|
|
18778
|
+
"spam_score": {
|
|
18779
|
+
"type": "number",
|
|
18780
|
+
"minimum": -1e3,
|
|
18781
|
+
"maximum": 1e3
|
|
18782
|
+
}
|
|
18783
|
+
},
|
|
18784
|
+
"required": [
|
|
18785
|
+
"from",
|
|
18786
|
+
"to",
|
|
18787
|
+
"text"
|
|
18788
|
+
],
|
|
18789
|
+
"description": "A made-up email, as POST /email-rules/test-sample takes it."
|
|
18790
|
+
},
|
|
18791
|
+
"id": {
|
|
18792
|
+
"type": "string",
|
|
18793
|
+
"format": "uuid"
|
|
18794
|
+
},
|
|
18795
|
+
"created_at": { "type": "string" }
|
|
18796
|
+
},
|
|
18797
|
+
"required": ["expected", "email"],
|
|
18798
|
+
"additionalProperties": false
|
|
18799
|
+
}
|
|
17611
18800
|
}
|
|
17612
18801
|
},
|
|
17613
18802
|
"additionalProperties": false,
|
|
17614
|
-
"description": "Emails the rule must match and must not
|
|
18803
|
+
"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."
|
|
17615
18804
|
}
|
|
17616
18805
|
},
|
|
17617
18806
|
"additionalProperties": false
|
|
@@ -17993,7 +19182,7 @@ const operationManifest = [
|
|
|
17993
19182
|
}
|
|
17994
19183
|
},
|
|
17995
19184
|
"expectations": {
|
|
17996
|
-
"description": "Present when expect was given, or for a saved rule with saved expectations.",
|
|
19185
|
+
"description": "Present when expect was given, or for a saved rule with saved expectations or made-up emails.",
|
|
17997
19186
|
"type": "object",
|
|
17998
19187
|
"properties": {
|
|
17999
19188
|
"passed": {
|
|
@@ -18063,6 +19252,71 @@ const operationManifest = [
|
|
|
18063
19252
|
]
|
|
18064
19253
|
},
|
|
18065
19254
|
"description": "match first, then no_match, each in the order given (saved expectations by email id)."
|
|
19255
|
+
},
|
|
19256
|
+
"samples": {
|
|
19257
|
+
"description": "Made-up emails judged (expect.samples, or a saved rule's), in order; counts include them.",
|
|
19258
|
+
"type": "array",
|
|
19259
|
+
"items": {
|
|
19260
|
+
"type": "object",
|
|
19261
|
+
"properties": {
|
|
19262
|
+
"index": {
|
|
19263
|
+
"type": "integer",
|
|
19264
|
+
"minimum": 0
|
|
19265
|
+
},
|
|
19266
|
+
"sample_id": {
|
|
19267
|
+
"type": ["string", "null"],
|
|
19268
|
+
"format": "uuid"
|
|
19269
|
+
},
|
|
19270
|
+
"label": { "type": ["string", "null"] },
|
|
19271
|
+
"expected": {
|
|
19272
|
+
"type": "string",
|
|
19273
|
+
"enum": ["match", "no_match"]
|
|
19274
|
+
},
|
|
19275
|
+
"outcome": {
|
|
19276
|
+
"type": "string",
|
|
19277
|
+
"enum": [
|
|
19278
|
+
"matched",
|
|
19279
|
+
"near_miss",
|
|
19280
|
+
"no_match",
|
|
19281
|
+
"not_decided"
|
|
19282
|
+
]
|
|
19283
|
+
},
|
|
19284
|
+
"status": {
|
|
19285
|
+
"type": "string",
|
|
19286
|
+
"enum": [
|
|
19287
|
+
"passed",
|
|
19288
|
+
"failed",
|
|
19289
|
+
"undecided"
|
|
19290
|
+
]
|
|
19291
|
+
},
|
|
19292
|
+
"question_answers": {
|
|
19293
|
+
"type": "array",
|
|
19294
|
+
"items": {
|
|
19295
|
+
"type": "object",
|
|
19296
|
+
"properties": {
|
|
19297
|
+
"question": { "type": "string" },
|
|
19298
|
+
"answer": { "type": ["boolean", "null"] },
|
|
19299
|
+
"probability": { "type": ["number", "null"] },
|
|
19300
|
+
"error_class": { "type": "string" }
|
|
19301
|
+
},
|
|
19302
|
+
"required": [
|
|
19303
|
+
"question",
|
|
19304
|
+
"answer",
|
|
19305
|
+
"probability"
|
|
19306
|
+
]
|
|
19307
|
+
}
|
|
19308
|
+
}
|
|
19309
|
+
},
|
|
19310
|
+
"required": [
|
|
19311
|
+
"index",
|
|
19312
|
+
"sample_id",
|
|
19313
|
+
"label",
|
|
19314
|
+
"expected",
|
|
19315
|
+
"outcome",
|
|
19316
|
+
"status",
|
|
19317
|
+
"question_answers"
|
|
19318
|
+
]
|
|
19319
|
+
}
|
|
18066
19320
|
}
|
|
18067
19321
|
},
|
|
18068
19322
|
"required": [
|
|
@@ -18437,7 +19691,7 @@ const operationManifest = [
|
|
|
18437
19691
|
"binaryResponse": false,
|
|
18438
19692
|
"bodyRequired": true,
|
|
18439
19693
|
"command": "update-email-rule",
|
|
18440
|
-
"description": "Renames the rule, saves a new version (english and tree together, with an explanation or one the server writes; earlier versions stay), changes its state (draft is off; observe; active), whose mail it runs over (evaluation_scope, with scope_selection when selected) or its saved expectations (each saved version and new set is judged in expectations_check). Anyone but its author who sees it may only disable it (state draft), and while it is shared only if they manage members from the dashboard. Without evaluation_scope the scope stays; a scope change saves a new version with the same conditions, and what was granted stays. A shared rule keeps its scope; a kind never changes. Send if_version, the current_version you read, so a write never replaces a version saved since. 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.",
|
|
19694
|
+
"description": "Renames the rule, saves a new version (english and tree together, with an explanation or one the server writes; earlier versions stay) or an earlier one again (restore_version), changes its state (draft is off; observe; active), whose mail it runs over (evaluation_scope, with scope_selection when selected) or its saved expectations (each saved version and new set is judged in expectations_check). Anyone but its author who sees it may only disable it (state draft), and while it is shared only if they manage members from the dashboard. Without evaluation_scope the scope stays; a scope change saves a new version with the same conditions, and what was granted stays. A shared rule keeps its scope; a kind never changes. A saved version takes effect at once (an active rule stays active); affects counts what it reaches. Send if_version, the current_version you read, so a write never replaces a version saved since, or if_revision to refuse it after any update. Without if_expectations_pass a version is saved even when its expectations_check fails. 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.",
|
|
18441
19695
|
"hasJsonBody": true,
|
|
18442
19696
|
"method": "PATCH",
|
|
18443
19697
|
"operationId": "updateEmailRule",
|
|
@@ -18539,6 +19793,25 @@ const operationManifest = [
|
|
|
18539
19793
|
"minLength": 1,
|
|
18540
19794
|
"maxLength": 64
|
|
18541
19795
|
},
|
|
19796
|
+
"breadth": {
|
|
19797
|
+
"description": "Ignored (the compile answer's breadth).",
|
|
19798
|
+
"anyOf": [{
|
|
19799
|
+
"type": "object",
|
|
19800
|
+
"properties": {
|
|
19801
|
+
"checked": {
|
|
19802
|
+
"type": "integer",
|
|
19803
|
+
"minimum": 0,
|
|
19804
|
+
"description": "Classified emails the conditions were decided for: of the newest 200 from the last 30 days in your own mailboxes (for a grant rule, those you may share), the ones you can still read."
|
|
19805
|
+
},
|
|
19806
|
+
"matched": {
|
|
19807
|
+
"type": "integer",
|
|
19808
|
+
"minimum": 0,
|
|
19809
|
+
"description": "Of those, how many they hold for (for a do-not-share rule, one they cannot decide counts)."
|
|
19810
|
+
}
|
|
19811
|
+
},
|
|
19812
|
+
"required": ["checked", "matched"]
|
|
19813
|
+
}, { "type": "null" }]
|
|
19814
|
+
},
|
|
18542
19815
|
"evaluation_scope": {
|
|
18543
19816
|
"type": "string",
|
|
18544
19817
|
"enum": [
|
|
@@ -18576,7 +19849,7 @@ const operationManifest = [
|
|
|
18576
19849
|
"description": "For a selected rule: inboxes, and domains (each address at them the author reads); each one you can read."
|
|
18577
19850
|
},
|
|
18578
19851
|
"if_version": {
|
|
18579
|
-
"description": "The current_version you read
|
|
19852
|
+
"description": "The current_version you read: if it has moved, nothing is saved (409 email_rule_conflict, version_moved). It guards versions only; if_revision guards every update.",
|
|
18580
19853
|
"type": "integer",
|
|
18581
19854
|
"minimum": 1
|
|
18582
19855
|
},
|
|
@@ -18602,6 +19875,233 @@ const operationManifest = [
|
|
|
18602
19875
|
},
|
|
18603
19876
|
"additionalProperties": false,
|
|
18604
19877
|
"description": "Replaces its saved expectations (author only); null removes them."
|
|
19878
|
+
},
|
|
19879
|
+
"expectation_samples": {
|
|
19880
|
+
"maxItems": 20,
|
|
19881
|
+
"type": ["array", "null"],
|
|
19882
|
+
"items": {
|
|
19883
|
+
"type": "object",
|
|
19884
|
+
"properties": {
|
|
19885
|
+
"expected": {
|
|
19886
|
+
"type": "string",
|
|
19887
|
+
"enum": ["match", "no_match"]
|
|
19888
|
+
},
|
|
19889
|
+
"label": {
|
|
19890
|
+
"type": ["string", "null"],
|
|
19891
|
+
"minLength": 1,
|
|
19892
|
+
"maxLength": 200,
|
|
19893
|
+
"pattern": "^[^\\u0000]*$",
|
|
19894
|
+
"description": "What the email stands for, such as a personal medical bill."
|
|
19895
|
+
},
|
|
19896
|
+
"email": {
|
|
19897
|
+
"type": "object",
|
|
19898
|
+
"properties": {
|
|
19899
|
+
"from": {
|
|
19900
|
+
"type": "object",
|
|
19901
|
+
"properties": {
|
|
19902
|
+
"address": {
|
|
19903
|
+
"type": "string",
|
|
19904
|
+
"maxLength": 254,
|
|
19905
|
+
"format": "email",
|
|
19906
|
+
"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,}$"
|
|
19907
|
+
},
|
|
19908
|
+
"name": {
|
|
19909
|
+
"type": "string",
|
|
19910
|
+
"maxLength": 200,
|
|
19911
|
+
"pattern": "^[^\\u0000]*$"
|
|
19912
|
+
}
|
|
19913
|
+
},
|
|
19914
|
+
"required": ["address"]
|
|
19915
|
+
},
|
|
19916
|
+
"to": {
|
|
19917
|
+
"minItems": 1,
|
|
19918
|
+
"maxItems": 10,
|
|
19919
|
+
"type": "array",
|
|
19920
|
+
"items": {
|
|
19921
|
+
"type": "string",
|
|
19922
|
+
"maxLength": 254,
|
|
19923
|
+
"format": "email",
|
|
19924
|
+
"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,}$"
|
|
19925
|
+
}
|
|
19926
|
+
},
|
|
19927
|
+
"cc": {
|
|
19928
|
+
"maxItems": 10,
|
|
19929
|
+
"type": "array",
|
|
19930
|
+
"items": {
|
|
19931
|
+
"type": "string",
|
|
19932
|
+
"maxLength": 254,
|
|
19933
|
+
"format": "email",
|
|
19934
|
+
"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,}$"
|
|
19935
|
+
}
|
|
19936
|
+
},
|
|
19937
|
+
"subject": {
|
|
19938
|
+
"type": "string",
|
|
19939
|
+
"maxLength": 998,
|
|
19940
|
+
"pattern": "^[^\\u0000]*$"
|
|
19941
|
+
},
|
|
19942
|
+
"text": {
|
|
19943
|
+
"type": "string",
|
|
19944
|
+
"minLength": 1,
|
|
19945
|
+
"maxLength": 4e3,
|
|
19946
|
+
"pattern": "^[^\\u0000]*$"
|
|
19947
|
+
},
|
|
19948
|
+
"attachment_types": {
|
|
19949
|
+
"maxItems": 10,
|
|
19950
|
+
"type": "array",
|
|
19951
|
+
"items": {
|
|
19952
|
+
"type": "string",
|
|
19953
|
+
"minLength": 1,
|
|
19954
|
+
"maxLength": 255,
|
|
19955
|
+
"pattern": "^[^\\u0000]*$"
|
|
19956
|
+
}
|
|
19957
|
+
},
|
|
19958
|
+
"automated": { "type": "boolean" },
|
|
19959
|
+
"from_mailing_list": { "type": "boolean" },
|
|
19960
|
+
"spam_score": {
|
|
19961
|
+
"type": "number",
|
|
19962
|
+
"minimum": -1e3,
|
|
19963
|
+
"maximum": 1e3
|
|
19964
|
+
}
|
|
19965
|
+
},
|
|
19966
|
+
"required": [
|
|
19967
|
+
"from",
|
|
19968
|
+
"to",
|
|
19969
|
+
"text"
|
|
19970
|
+
],
|
|
19971
|
+
"description": "A made-up email, as POST /email-rules/test-sample takes it."
|
|
19972
|
+
},
|
|
19973
|
+
"id": {
|
|
19974
|
+
"type": "string",
|
|
19975
|
+
"format": "uuid"
|
|
19976
|
+
},
|
|
19977
|
+
"created_at": { "type": "string" }
|
|
19978
|
+
},
|
|
19979
|
+
"required": ["expected", "email"],
|
|
19980
|
+
"additionalProperties": false
|
|
19981
|
+
},
|
|
19982
|
+
"description": "Replaces its saved made-up emails (author only); null or [] removes them."
|
|
19983
|
+
},
|
|
19984
|
+
"add_expectation_samples": {
|
|
19985
|
+
"description": "Adds made-up emails to those saved (author only), up to 20 in all.",
|
|
19986
|
+
"minItems": 1,
|
|
19987
|
+
"maxItems": 20,
|
|
19988
|
+
"type": "array",
|
|
19989
|
+
"items": {
|
|
19990
|
+
"type": "object",
|
|
19991
|
+
"properties": {
|
|
19992
|
+
"expected": {
|
|
19993
|
+
"type": "string",
|
|
19994
|
+
"enum": ["match", "no_match"]
|
|
19995
|
+
},
|
|
19996
|
+
"label": {
|
|
19997
|
+
"type": ["string", "null"],
|
|
19998
|
+
"minLength": 1,
|
|
19999
|
+
"maxLength": 200,
|
|
20000
|
+
"pattern": "^[^\\u0000]*$",
|
|
20001
|
+
"description": "What the email stands for, such as a personal medical bill."
|
|
20002
|
+
},
|
|
20003
|
+
"email": {
|
|
20004
|
+
"type": "object",
|
|
20005
|
+
"properties": {
|
|
20006
|
+
"from": {
|
|
20007
|
+
"type": "object",
|
|
20008
|
+
"properties": {
|
|
20009
|
+
"address": {
|
|
20010
|
+
"type": "string",
|
|
20011
|
+
"maxLength": 254,
|
|
20012
|
+
"format": "email",
|
|
20013
|
+
"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,}$"
|
|
20014
|
+
},
|
|
20015
|
+
"name": {
|
|
20016
|
+
"type": "string",
|
|
20017
|
+
"maxLength": 200,
|
|
20018
|
+
"pattern": "^[^\\u0000]*$"
|
|
20019
|
+
}
|
|
20020
|
+
},
|
|
20021
|
+
"required": ["address"]
|
|
20022
|
+
},
|
|
20023
|
+
"to": {
|
|
20024
|
+
"minItems": 1,
|
|
20025
|
+
"maxItems": 10,
|
|
20026
|
+
"type": "array",
|
|
20027
|
+
"items": {
|
|
20028
|
+
"type": "string",
|
|
20029
|
+
"maxLength": 254,
|
|
20030
|
+
"format": "email",
|
|
20031
|
+
"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,}$"
|
|
20032
|
+
}
|
|
20033
|
+
},
|
|
20034
|
+
"cc": {
|
|
20035
|
+
"maxItems": 10,
|
|
20036
|
+
"type": "array",
|
|
20037
|
+
"items": {
|
|
20038
|
+
"type": "string",
|
|
20039
|
+
"maxLength": 254,
|
|
20040
|
+
"format": "email",
|
|
20041
|
+
"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,}$"
|
|
20042
|
+
}
|
|
20043
|
+
},
|
|
20044
|
+
"subject": {
|
|
20045
|
+
"type": "string",
|
|
20046
|
+
"maxLength": 998,
|
|
20047
|
+
"pattern": "^[^\\u0000]*$"
|
|
20048
|
+
},
|
|
20049
|
+
"text": {
|
|
20050
|
+
"type": "string",
|
|
20051
|
+
"minLength": 1,
|
|
20052
|
+
"maxLength": 4e3,
|
|
20053
|
+
"pattern": "^[^\\u0000]*$"
|
|
20054
|
+
},
|
|
20055
|
+
"attachment_types": {
|
|
20056
|
+
"maxItems": 10,
|
|
20057
|
+
"type": "array",
|
|
20058
|
+
"items": {
|
|
20059
|
+
"type": "string",
|
|
20060
|
+
"minLength": 1,
|
|
20061
|
+
"maxLength": 255,
|
|
20062
|
+
"pattern": "^[^\\u0000]*$"
|
|
20063
|
+
}
|
|
20064
|
+
},
|
|
20065
|
+
"automated": { "type": "boolean" },
|
|
20066
|
+
"from_mailing_list": { "type": "boolean" },
|
|
20067
|
+
"spam_score": {
|
|
20068
|
+
"type": "number",
|
|
20069
|
+
"minimum": -1e3,
|
|
20070
|
+
"maximum": 1e3
|
|
20071
|
+
}
|
|
20072
|
+
},
|
|
20073
|
+
"required": [
|
|
20074
|
+
"from",
|
|
20075
|
+
"to",
|
|
20076
|
+
"text"
|
|
20077
|
+
],
|
|
20078
|
+
"description": "A made-up email, as POST /email-rules/test-sample takes it."
|
|
20079
|
+
},
|
|
20080
|
+
"id": {
|
|
20081
|
+
"type": "string",
|
|
20082
|
+
"format": "uuid"
|
|
20083
|
+
},
|
|
20084
|
+
"created_at": { "type": "string" }
|
|
20085
|
+
},
|
|
20086
|
+
"required": ["expected", "email"],
|
|
20087
|
+
"additionalProperties": false
|
|
20088
|
+
}
|
|
20089
|
+
},
|
|
20090
|
+
"if_revision": {
|
|
20091
|
+
"description": "The revision you read: if the rule was updated since, nothing is saved (409 email_rule_conflict, revision_moved).",
|
|
20092
|
+
"type": "integer",
|
|
20093
|
+
"minimum": 1,
|
|
20094
|
+
"maximum": 2147483647
|
|
20095
|
+
},
|
|
20096
|
+
"if_expectations_pass": {
|
|
20097
|
+
"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.",
|
|
20098
|
+
"type": "boolean"
|
|
20099
|
+
},
|
|
20100
|
+
"restore_version": {
|
|
20101
|
+
"description": "Save version n again as the next version (author only): its english, tree, explanation, assumptions, warnings and compiled_by. A version carries no scope: the rule keeps its own unless evaluation_scope is sent. 404 version_not_found; 400 already_current or version_not_renderable. Not with a version's own fields.",
|
|
20102
|
+
"type": "integer",
|
|
20103
|
+
"minimum": 1,
|
|
20104
|
+
"maximum": 2147483647
|
|
18605
20105
|
}
|
|
18606
20106
|
},
|
|
18607
20107
|
"additionalProperties": false
|
|
@@ -18627,6 +20127,10 @@ const operationManifest = [
|
|
|
18627
20127
|
"type": "string",
|
|
18628
20128
|
"enum": ["grant", "do_not_share"]
|
|
18629
20129
|
},
|
|
20130
|
+
"protection_note": {
|
|
20131
|
+
"description": "On a do_not_share rule only: it protects nothing yet (not active), or a classifier fact narrows it.",
|
|
20132
|
+
"type": "string"
|
|
20133
|
+
},
|
|
18630
20134
|
"evaluation_scope": {
|
|
18631
20135
|
"description": "Whose mail it runs over, as stored (GET /email-rules/{id}/scope: what that is now).",
|
|
18632
20136
|
"type": "string",
|
|
@@ -18657,10 +20161,37 @@ const operationManifest = [
|
|
|
18657
20161
|
"type": ["integer", "null"],
|
|
18658
20162
|
"exclusiveMinimum": 0
|
|
18659
20163
|
},
|
|
20164
|
+
"revision": {
|
|
20165
|
+
"description": "Moves on by one with every update. Send it as if_revision.",
|
|
20166
|
+
"type": "integer",
|
|
20167
|
+
"exclusiveMinimum": 0
|
|
20168
|
+
},
|
|
18660
20169
|
"author_user_id": {
|
|
18661
20170
|
"type": "string",
|
|
18662
20171
|
"format": "uuid"
|
|
18663
20172
|
},
|
|
20173
|
+
"author": {
|
|
20174
|
+
"type": "object",
|
|
20175
|
+
"properties": {
|
|
20176
|
+
"user_id": {
|
|
20177
|
+
"type": "string",
|
|
20178
|
+
"format": "uuid"
|
|
20179
|
+
},
|
|
20180
|
+
"name": {
|
|
20181
|
+
"description": "Their name; null unless you can list members or this is you, and when they have none set.",
|
|
20182
|
+
"type": ["string", "null"]
|
|
20183
|
+
},
|
|
20184
|
+
"email": {
|
|
20185
|
+
"description": "Sign-in email; null unless you can list member emails or this is you.",
|
|
20186
|
+
"type": ["string", "null"]
|
|
20187
|
+
}
|
|
20188
|
+
},
|
|
20189
|
+
"required": [
|
|
20190
|
+
"user_id",
|
|
20191
|
+
"name",
|
|
20192
|
+
"email"
|
|
20193
|
+
]
|
|
20194
|
+
},
|
|
18664
20195
|
"is_author": {
|
|
18665
20196
|
"type": "boolean",
|
|
18666
20197
|
"description": "Whether you are its author, in your current membership. Only its author edits it; others who see it may only disable or delete it (while shared, only if they manage members)."
|
|
@@ -18673,91 +20204,94 @@ const operationManifest = [
|
|
|
18673
20204
|
"type": "string",
|
|
18674
20205
|
"format": "date-time"
|
|
18675
20206
|
},
|
|
18676
|
-
"current": {
|
|
18677
|
-
"
|
|
18678
|
-
"
|
|
18679
|
-
"
|
|
18680
|
-
|
|
18681
|
-
"
|
|
18682
|
-
|
|
18683
|
-
|
|
18684
|
-
"tree": {
|
|
18685
|
-
"type": "object",
|
|
18686
|
-
"properties": {
|
|
18687
|
-
"version": {
|
|
18688
|
-
"type": "number",
|
|
18689
|
-
"const": 1
|
|
18690
|
-
},
|
|
18691
|
-
"vocabulary": {
|
|
18692
|
-
"type": "string",
|
|
18693
|
-
"description": "Vocabulary version, such as v1."
|
|
18694
|
-
},
|
|
18695
|
-
"when": {
|
|
18696
|
-
"type": "object",
|
|
18697
|
-
"propertyNames": { "type": "string" },
|
|
18698
|
-
"additionalProperties": {},
|
|
18699
|
-
"description": "A condition {column, op, value?, min_confidence?} (see GET /email-rules/vocabulary) or {all: [...]}, {any: [...]}, {not: ...}."
|
|
18700
|
-
},
|
|
18701
|
-
"questions": {
|
|
18702
|
-
"type": "array",
|
|
18703
|
-
"items": { "type": "string" },
|
|
18704
|
-
"description": "Yes/no questions asked once the conditions match; all must be yes."
|
|
18705
|
-
}
|
|
20207
|
+
"current": {
|
|
20208
|
+
"description": "Its current version: its text (english, tree, explanation, assumptions, warnings) and who compiled it. current_version is its number. GET /email-rules lists rules without it.",
|
|
20209
|
+
"anyOf": [{
|
|
20210
|
+
"type": "object",
|
|
20211
|
+
"properties": {
|
|
20212
|
+
"version": {
|
|
20213
|
+
"type": "integer",
|
|
20214
|
+
"exclusiveMinimum": 0
|
|
18706
20215
|
},
|
|
18707
|
-
"
|
|
18708
|
-
|
|
18709
|
-
"
|
|
18710
|
-
"
|
|
18711
|
-
|
|
18712
|
-
|
|
18713
|
-
|
|
18714
|
-
|
|
18715
|
-
|
|
18716
|
-
|
|
18717
|
-
|
|
18718
|
-
|
|
18719
|
-
|
|
18720
|
-
|
|
18721
|
-
|
|
18722
|
-
|
|
18723
|
-
|
|
18724
|
-
|
|
18725
|
-
|
|
18726
|
-
|
|
18727
|
-
|
|
18728
|
-
|
|
18729
|
-
|
|
18730
|
-
"format": "date-time"
|
|
18731
|
-
},
|
|
18732
|
-
"sql": {
|
|
18733
|
-
"description": "Present only when include_sql was true.",
|
|
18734
|
-
"type": "object",
|
|
18735
|
-
"properties": {
|
|
18736
|
-
"expression": {
|
|
18737
|
-
"type": "string",
|
|
18738
|
-
"description": "A parameterized boolean expression over one row per email."
|
|
20216
|
+
"english": { "type": "string" },
|
|
20217
|
+
"tree": {
|
|
20218
|
+
"type": "object",
|
|
20219
|
+
"properties": {
|
|
20220
|
+
"version": {
|
|
20221
|
+
"type": "number",
|
|
20222
|
+
"const": 1
|
|
20223
|
+
},
|
|
20224
|
+
"vocabulary": {
|
|
20225
|
+
"type": "string",
|
|
20226
|
+
"description": "Vocabulary version, such as v1."
|
|
20227
|
+
},
|
|
20228
|
+
"when": {
|
|
20229
|
+
"type": "object",
|
|
20230
|
+
"propertyNames": { "type": "string" },
|
|
20231
|
+
"additionalProperties": {},
|
|
20232
|
+
"description": "A condition {column, op, value?, min_confidence?} (see GET /email-rules/vocabulary) or {all: [...]}, {any: [...]}, {not: ...}."
|
|
20233
|
+
},
|
|
20234
|
+
"questions": {
|
|
20235
|
+
"type": "array",
|
|
20236
|
+
"items": { "type": "string" },
|
|
20237
|
+
"description": "Yes/no questions asked once the conditions match; all must be yes."
|
|
20238
|
+
}
|
|
18739
20239
|
},
|
|
18740
|
-
"
|
|
18741
|
-
"
|
|
18742
|
-
"
|
|
18743
|
-
"
|
|
18744
|
-
|
|
20240
|
+
"required": [
|
|
20241
|
+
"version",
|
|
20242
|
+
"vocabulary",
|
|
20243
|
+
"when",
|
|
20244
|
+
"questions"
|
|
20245
|
+
]
|
|
18745
20246
|
},
|
|
18746
|
-
"
|
|
18747
|
-
|
|
18748
|
-
|
|
18749
|
-
|
|
18750
|
-
|
|
18751
|
-
|
|
18752
|
-
|
|
18753
|
-
|
|
18754
|
-
|
|
18755
|
-
|
|
18756
|
-
|
|
18757
|
-
|
|
18758
|
-
|
|
18759
|
-
|
|
18760
|
-
|
|
20247
|
+
"explanation": { "type": "string" },
|
|
20248
|
+
"assumptions": {
|
|
20249
|
+
"type": "array",
|
|
20250
|
+
"items": { "type": "string" }
|
|
20251
|
+
},
|
|
20252
|
+
"warnings": {
|
|
20253
|
+
"type": "array",
|
|
20254
|
+
"items": { "type": "string" }
|
|
20255
|
+
},
|
|
20256
|
+
"compiled_by": {
|
|
20257
|
+
"type": "string",
|
|
20258
|
+
"description": "Who produced the tree, as stated when saved (not verified), such as rule-compiler-v1 or member."
|
|
20259
|
+
},
|
|
20260
|
+
"vocabulary": { "type": "string" },
|
|
20261
|
+
"created_at": {
|
|
20262
|
+
"type": "string",
|
|
20263
|
+
"format": "date-time"
|
|
20264
|
+
},
|
|
20265
|
+
"sql": {
|
|
20266
|
+
"description": "Present only when include_sql was true.",
|
|
20267
|
+
"type": "object",
|
|
20268
|
+
"properties": {
|
|
20269
|
+
"expression": {
|
|
20270
|
+
"type": "string",
|
|
20271
|
+
"description": "A parameterized boolean expression over one row per email."
|
|
20272
|
+
},
|
|
20273
|
+
"params": {
|
|
20274
|
+
"type": "array",
|
|
20275
|
+
"items": {},
|
|
20276
|
+
"description": "The values bound to $1, $2 and so on."
|
|
20277
|
+
}
|
|
20278
|
+
},
|
|
20279
|
+
"required": ["expression", "params"]
|
|
20280
|
+
}
|
|
20281
|
+
},
|
|
20282
|
+
"required": [
|
|
20283
|
+
"version",
|
|
20284
|
+
"english",
|
|
20285
|
+
"tree",
|
|
20286
|
+
"explanation",
|
|
20287
|
+
"assumptions",
|
|
20288
|
+
"warnings",
|
|
20289
|
+
"compiled_by",
|
|
20290
|
+
"vocabulary",
|
|
20291
|
+
"created_at"
|
|
20292
|
+
]
|
|
20293
|
+
}, { "type": "null" }]
|
|
20294
|
+
},
|
|
18761
20295
|
"scope_warnings": {
|
|
18762
20296
|
"description": "Names in scope_selection that match nothing this organization receives mail at (a typo, or an inbox not yet in use). Present only when there are some.",
|
|
18763
20297
|
"type": "array",
|
|
@@ -18788,8 +20322,114 @@ const operationManifest = [
|
|
|
18788
20322
|
]
|
|
18789
20323
|
}
|
|
18790
20324
|
},
|
|
20325
|
+
"expectation_samples": {
|
|
20326
|
+
"description": "Its saved made-up emails, when this write set or added some.",
|
|
20327
|
+
"type": "array",
|
|
20328
|
+
"items": {
|
|
20329
|
+
"type": "object",
|
|
20330
|
+
"properties": {
|
|
20331
|
+
"id": {
|
|
20332
|
+
"type": "string",
|
|
20333
|
+
"format": "uuid"
|
|
20334
|
+
},
|
|
20335
|
+
"expected": {
|
|
20336
|
+
"type": "string",
|
|
20337
|
+
"enum": ["match", "no_match"]
|
|
20338
|
+
},
|
|
20339
|
+
"label": { "type": ["string", "null"] },
|
|
20340
|
+
"email": {
|
|
20341
|
+
"type": "object",
|
|
20342
|
+
"properties": {
|
|
20343
|
+
"from": {
|
|
20344
|
+
"type": "object",
|
|
20345
|
+
"properties": {
|
|
20346
|
+
"address": {
|
|
20347
|
+
"type": "string",
|
|
20348
|
+
"maxLength": 254,
|
|
20349
|
+
"format": "email",
|
|
20350
|
+
"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,}$"
|
|
20351
|
+
},
|
|
20352
|
+
"name": {
|
|
20353
|
+
"type": "string",
|
|
20354
|
+
"maxLength": 200,
|
|
20355
|
+
"pattern": "^[^\\u0000]*$"
|
|
20356
|
+
}
|
|
20357
|
+
},
|
|
20358
|
+
"required": ["address"]
|
|
20359
|
+
},
|
|
20360
|
+
"to": {
|
|
20361
|
+
"minItems": 1,
|
|
20362
|
+
"maxItems": 10,
|
|
20363
|
+
"type": "array",
|
|
20364
|
+
"items": {
|
|
20365
|
+
"type": "string",
|
|
20366
|
+
"maxLength": 254,
|
|
20367
|
+
"format": "email",
|
|
20368
|
+
"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,}$"
|
|
20369
|
+
}
|
|
20370
|
+
},
|
|
20371
|
+
"cc": {
|
|
20372
|
+
"maxItems": 10,
|
|
20373
|
+
"type": "array",
|
|
20374
|
+
"items": {
|
|
20375
|
+
"type": "string",
|
|
20376
|
+
"maxLength": 254,
|
|
20377
|
+
"format": "email",
|
|
20378
|
+
"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,}$"
|
|
20379
|
+
}
|
|
20380
|
+
},
|
|
20381
|
+
"subject": {
|
|
20382
|
+
"type": "string",
|
|
20383
|
+
"maxLength": 998,
|
|
20384
|
+
"pattern": "^[^\\u0000]*$"
|
|
20385
|
+
},
|
|
20386
|
+
"text": {
|
|
20387
|
+
"type": "string",
|
|
20388
|
+
"minLength": 1,
|
|
20389
|
+
"maxLength": 4e3,
|
|
20390
|
+
"pattern": "^[^\\u0000]*$"
|
|
20391
|
+
},
|
|
20392
|
+
"attachment_types": {
|
|
20393
|
+
"maxItems": 10,
|
|
20394
|
+
"type": "array",
|
|
20395
|
+
"items": {
|
|
20396
|
+
"type": "string",
|
|
20397
|
+
"minLength": 1,
|
|
20398
|
+
"maxLength": 255,
|
|
20399
|
+
"pattern": "^[^\\u0000]*$"
|
|
20400
|
+
}
|
|
20401
|
+
},
|
|
20402
|
+
"automated": { "type": "boolean" },
|
|
20403
|
+
"from_mailing_list": { "type": "boolean" },
|
|
20404
|
+
"spam_score": {
|
|
20405
|
+
"type": "number",
|
|
20406
|
+
"minimum": -1e3,
|
|
20407
|
+
"maximum": 1e3
|
|
20408
|
+
}
|
|
20409
|
+
},
|
|
20410
|
+
"required": [
|
|
20411
|
+
"from",
|
|
20412
|
+
"to",
|
|
20413
|
+
"text"
|
|
20414
|
+
],
|
|
20415
|
+
"description": "A made-up email, as POST /email-rules/test-sample takes it."
|
|
20416
|
+
},
|
|
20417
|
+
"created_at": {
|
|
20418
|
+
"type": "string",
|
|
20419
|
+
"format": "date-time"
|
|
20420
|
+
}
|
|
20421
|
+
},
|
|
20422
|
+
"required": [
|
|
20423
|
+
"id",
|
|
20424
|
+
"expected",
|
|
20425
|
+
"label",
|
|
20426
|
+
"email",
|
|
20427
|
+
"created_at"
|
|
20428
|
+
]
|
|
20429
|
+
}
|
|
20430
|
+
},
|
|
18791
20431
|
"expectations_check": {
|
|
18792
|
-
"description": "After a write that saved a version or changed its expectations, if it has some:
|
|
20432
|
+
"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.",
|
|
18793
20433
|
"type": "object",
|
|
18794
20434
|
"properties": {
|
|
18795
20435
|
"version": {
|
|
@@ -18801,7 +20441,7 @@ const operationManifest = [
|
|
|
18801
20441
|
"type": ["boolean", "null"]
|
|
18802
20442
|
},
|
|
18803
20443
|
"decided": {
|
|
18804
|
-
"description": "Whether every expectation was judged (undecided
|
|
20444
|
+
"description": "Whether every expectation was judged (undecided and undecided_samples are empty). Absent from a server that predates it.",
|
|
18805
20445
|
"type": "boolean"
|
|
18806
20446
|
},
|
|
18807
20447
|
"failed": {
|
|
@@ -18817,6 +20457,26 @@ const operationManifest = [
|
|
|
18817
20457
|
"type": "string",
|
|
18818
20458
|
"format": "uuid"
|
|
18819
20459
|
}
|
|
20460
|
+
},
|
|
20461
|
+
"failed_samples": {
|
|
20462
|
+
"description": "Made-up emails that failed, by index in the set judged (saved oldest first, then added; or as sent).",
|
|
20463
|
+
"type": "array",
|
|
20464
|
+
"items": {
|
|
20465
|
+
"type": "integer",
|
|
20466
|
+
"minimum": 0
|
|
20467
|
+
}
|
|
20468
|
+
},
|
|
20469
|
+
"undecided_samples": {
|
|
20470
|
+
"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.",
|
|
20471
|
+
"type": "array",
|
|
20472
|
+
"items": {
|
|
20473
|
+
"type": "integer",
|
|
20474
|
+
"minimum": 0
|
|
20475
|
+
}
|
|
20476
|
+
},
|
|
20477
|
+
"note": {
|
|
20478
|
+
"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.",
|
|
20479
|
+
"type": "string"
|
|
18820
20480
|
}
|
|
18821
20481
|
},
|
|
18822
20482
|
"required": [
|
|
@@ -18825,6 +20485,41 @@ const operationManifest = [
|
|
|
18825
20485
|
"failed",
|
|
18826
20486
|
"undecided"
|
|
18827
20487
|
]
|
|
20488
|
+
},
|
|
20489
|
+
"restored_from": {
|
|
20490
|
+
"description": "When this write saved an earlier version again (restore_version): which, and that the scope is not part of a version.",
|
|
20491
|
+
"type": "object",
|
|
20492
|
+
"properties": {
|
|
20493
|
+
"version": {
|
|
20494
|
+
"type": "integer",
|
|
20495
|
+
"exclusiveMinimum": 0
|
|
20496
|
+
},
|
|
20497
|
+
"note": { "type": "string" }
|
|
20498
|
+
},
|
|
20499
|
+
"required": ["version", "note"]
|
|
20500
|
+
},
|
|
20501
|
+
"affects": {
|
|
20502
|
+
"description": "After an update that saved a version: API keys listing the rule, the webhooks they own, and shares this write ended. The rule keeps its state. Absent if not read.",
|
|
20503
|
+
"type": "object",
|
|
20504
|
+
"properties": {
|
|
20505
|
+
"keys": {
|
|
20506
|
+
"type": "integer",
|
|
20507
|
+
"minimum": 0
|
|
20508
|
+
},
|
|
20509
|
+
"endpoints": {
|
|
20510
|
+
"type": "integer",
|
|
20511
|
+
"minimum": 0
|
|
20512
|
+
},
|
|
20513
|
+
"shares_ended": {
|
|
20514
|
+
"type": "integer",
|
|
20515
|
+
"minimum": 0
|
|
20516
|
+
}
|
|
20517
|
+
},
|
|
20518
|
+
"required": [
|
|
20519
|
+
"keys",
|
|
20520
|
+
"endpoints",
|
|
20521
|
+
"shares_ended"
|
|
20522
|
+
]
|
|
18828
20523
|
}
|
|
18829
20524
|
},
|
|
18830
20525
|
"required": [
|
|
@@ -19018,6 +20713,14 @@ const operationManifest = [
|
|
|
19018
20713
|
"decline_reason": {
|
|
19019
20714
|
"description": "Why the member asked declined it; null unless they gave a reason when declining.",
|
|
19020
20715
|
"type": ["string", "null"]
|
|
20716
|
+
},
|
|
20717
|
+
"edited_on_accept": {
|
|
20718
|
+
"description": "Once accepted: whether the rule was changed first, as on the share. Null until then, or when unreadable.",
|
|
20719
|
+
"type": ["boolean", "null"]
|
|
20720
|
+
},
|
|
20721
|
+
"notice": {
|
|
20722
|
+
"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.",
|
|
20723
|
+
"type": ["string", "null"]
|
|
19021
20724
|
}
|
|
19022
20725
|
},
|
|
19023
20726
|
"required": [
|
|
@@ -35034,7 +36737,7 @@ const operationManifest = [
|
|
|
35034
36737
|
"binaryResponse": false,
|
|
35035
36738
|
"bodyRequired": true,
|
|
35036
36739
|
"command": "decide-suspension",
|
|
35037
|
-
"description": "Keep, narrow or revoke one suspended or flagged item. Keeping it, and revoking an API key
|
|
36740
|
+
"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.",
|
|
35038
36741
|
"hasJsonBody": true,
|
|
35039
36742
|
"method": "POST",
|
|
35040
36743
|
"operationId": "decideSuspension",
|
|
@@ -46372,11 +48075,11 @@ const operationManifest = [
|
|
|
46372
48075
|
"description": "Whether this is the latest attempt of its delivery: the one recorded last, where a record of no attempt (a rule skip or a held send, attempt_count 0) is latest only when no attempt was made. A retry is recorded as a new attempt with its own id and attempt_count (not always higher than the one before), so an earlier attempt never changes: follow the latest one, or read attempts on any of them."
|
|
46373
48076
|
},
|
|
46374
48077
|
"retry_scheduled": {
|
|
46375
|
-
"description": "Whether an automatic retry will try this delivery again. A retry is a delivery pass for the email: it sends again to the endpoint the email's routing resolves then (its recorded route, or the current endpoint for its domain), unless the email already reached it, deciding it afresh (its rules, its owner's access, whether it is still on), so true promises a pass, not a delivery to this endpoint. Null on every attempt of a delivery retried or settled on a schedule of its own
|
|
48078
|
+
"description": "Whether an automatic retry will try this delivery again. A retry is a delivery pass for the email: it sends again to the endpoint the email's routing resolves then (its recorded route, or the current endpoint for its domain), unless the email already reached it, deciding it afresh (its rules, its owner's access, whether it is still on), so true promises a pass, not a delivery to this endpoint. A rule-granted delivery is retried on a schedule of its own, to the same endpoint, deciding it afresh: on its latest attempt true while another attempt is to come (the delivery's next_attempt_at says when); null while an attempt is under way (read it again shortly), while it waits on an owner who cannot receive mail or on a held endpoint (checked again hourly, and sent once that clears), when it is past its 7-day limit, or when that could not be read just now; and false otherwise, including once the endpoint is off or already has the email. Null on every attempt of a delivery retried or settled on a schedule of its own that this does not report: a held one (its release settles it, including the attempts it makes itself), a pull attempt, and a sent email's or other event's delivery. On the latest attempt that failed, is pending or was skipped in a way still owed, of an email that was not rejected and still has its content, to an endpoint that is still on, takes the event, has an owner the pass would deliver to (a revoked or suspended key, or an owner that may not receive the message, reads false) and has not received the email by another delivery: null while a delivery pass for the email is running (whether another follows is decided when it ends), true when the email has another automatic pass to come (a scheduled retry, the reclaim of a pass that stopped, or the recovery of a first pass that did not finish), and false otherwise. False on every other attempt, including every attempt a later one followed: no automatic retry of the email's delivery will try it again, which does not mean nothing else ever will.",
|
|
46376
48079
|
"type": ["boolean", "null"]
|
|
46377
48080
|
},
|
|
46378
48081
|
"next_attempt_at": {
|
|
46379
|
-
"description": "When the next automatic
|
|
48082
|
+
"description": "When the next automatic attempt of a rule-granted delivery is due: set on its latest attempt while retry_scheduled is true. The attempt runs at the first retry pass at or after this time (one runs every few minutes), so a time already past means it is due now. Null otherwise, and on every delivery that is not rule-granted, whose retries are passes for the email with no time reported (whether one is coming is retry_scheduled).",
|
|
46380
48083
|
"type": ["string", "null"]
|
|
46381
48084
|
},
|
|
46382
48085
|
"attempts": {
|
|
@@ -46406,7 +48109,7 @@ const operationManifest = [
|
|
|
46406
48109
|
"type": ["string", "null"]
|
|
46407
48110
|
},
|
|
46408
48111
|
"retry_scheduled": {
|
|
46409
|
-
"description": "Whether an automatic retry will try this delivery again. A retry is a delivery pass for the email: it sends again to the endpoint the email's routing resolves then (its recorded route, or the current endpoint for its domain), unless the email already reached it, deciding it afresh (its rules, its owner's access, whether it is still on), so true promises a pass, not a delivery to this endpoint. Null on every attempt of a delivery retried or settled on a schedule of its own
|
|
48112
|
+
"description": "Whether an automatic retry will try this delivery again. A retry is a delivery pass for the email: it sends again to the endpoint the email's routing resolves then (its recorded route, or the current endpoint for its domain), unless the email already reached it, deciding it afresh (its rules, its owner's access, whether it is still on), so true promises a pass, not a delivery to this endpoint. A rule-granted delivery is retried on a schedule of its own, to the same endpoint, deciding it afresh: on its latest attempt true while another attempt is to come (the delivery's next_attempt_at says when); null while an attempt is under way (read it again shortly), while it waits on an owner who cannot receive mail or on a held endpoint (checked again hourly, and sent once that clears), when it is past its 7-day limit, or when that could not be read just now; and false otherwise, including once the endpoint is off or already has the email. Null on every attempt of a delivery retried or settled on a schedule of its own that this does not report: a held one (its release settles it, including the attempts it makes itself), a pull attempt, and a sent email's or other event's delivery. On the latest attempt that failed, is pending or was skipped in a way still owed, of an email that was not rejected and still has its content, to an endpoint that is still on, takes the event, has an owner the pass would deliver to (a revoked or suspended key, or an owner that may not receive the message, reads false) and has not received the email by another delivery: null while a delivery pass for the email is running (whether another follows is decided when it ends), true when the email has another automatic pass to come (a scheduled retry, the reclaim of a pass that stopped, or the recovery of a first pass that did not finish), and false otherwise. False on every other attempt, including every attempt a later one followed: no automatic retry of the email's delivery will try it again, which does not mean nothing else ever will.",
|
|
46410
48113
|
"type": ["boolean", "null"]
|
|
46411
48114
|
}
|
|
46412
48115
|
},
|
|
@@ -46620,12 +48323,12 @@ const operationManifest = [
|
|
|
46620
48323
|
},
|
|
46621
48324
|
"retry_scheduled": {
|
|
46622
48325
|
"type": ["boolean", "null"],
|
|
46623
|
-
"description": "Whether an automatic retry will try this delivery again. A retry is a delivery pass for the email: it sends again to the endpoint the email's routing resolves then (its recorded route, or the current endpoint for its domain), unless the email already reached it, deciding it afresh (its rules, its owner's access, whether it is still on), so true promises a pass, not a delivery to this endpoint. Null on every attempt of a delivery retried or settled on a schedule of its own
|
|
48326
|
+
"description": "Whether an automatic retry will try this delivery again. A retry is a delivery pass for the email: it sends again to the endpoint the email's routing resolves then (its recorded route, or the current endpoint for its domain), unless the email already reached it, deciding it afresh (its rules, its owner's access, whether it is still on), so true promises a pass, not a delivery to this endpoint. A rule-granted delivery is retried on a schedule of its own, to the same endpoint, deciding it afresh: on its latest attempt true while another attempt is to come (the delivery's next_attempt_at says when); null while an attempt is under way (read it again shortly), while it waits on an owner who cannot receive mail or on a held endpoint (checked again hourly, and sent once that clears), when it is past its 7-day limit, or when that could not be read just now; and false otherwise, including once the endpoint is off or already has the email. Null on every attempt of a delivery retried or settled on a schedule of its own that this does not report: a held one (its release settles it, including the attempts it makes itself), a pull attempt, and a sent email's or other event's delivery. On the latest attempt that failed, is pending or was skipped in a way still owed, of an email that was not rejected and still has its content, to an endpoint that is still on, takes the event, has an owner the pass would deliver to (a revoked or suspended key, or an owner that may not receive the message, reads false) and has not received the email by another delivery: null while a delivery pass for the email is running (whether another follows is decided when it ends), true when the email has another automatic pass to come (a scheduled retry, the reclaim of a pass that stopped, or the recovery of a first pass that did not finish), and false otherwise. False on every other attempt, including every attempt a later one followed: no automatic retry of the email's delivery will try it again, which does not mean nothing else ever will."
|
|
46624
48327
|
},
|
|
46625
48328
|
"next_attempt_at": {
|
|
46626
48329
|
"type": ["string", "null"],
|
|
46627
48330
|
"format": "date-time",
|
|
46628
|
-
"description": "When the next automatic
|
|
48331
|
+
"description": "When the next automatic attempt of a rule-granted delivery is due: set on its latest attempt while retry_scheduled is true. The attempt runs at the first retry pass at or after this time (one runs every few minutes), so a time already past means it is due now. Null otherwise, and on every delivery that is not rule-granted, whose retries are passes for the email with no time reported (whether one is coming is retry_scheduled)."
|
|
46629
48332
|
}
|
|
46630
48333
|
},
|
|
46631
48334
|
"required": [
|
|
@@ -46834,7 +48537,7 @@ function sanitizeForTerminal(value) {
|
|
|
46834
48537
|
return out;
|
|
46835
48538
|
}
|
|
46836
48539
|
// src/oclif/answer-reports.ts
|
|
46837
|
-
function isRecord$
|
|
48540
|
+
function isRecord$3(value) {
|
|
46838
48541
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
46839
48542
|
}
|
|
46840
48543
|
/** Text from an answer, made safe and one line for a report: no terminal controls. */
|
|
@@ -46843,10 +48546,10 @@ function oneLine(text) {
|
|
|
46843
48546
|
}
|
|
46844
48547
|
/** One line per warning a route write answers with (createRoute, updateRoute). */
|
|
46845
48548
|
function routeWarningLines(data) {
|
|
46846
|
-
const warnings = isRecord$
|
|
48549
|
+
const warnings = isRecord$3(data) ? data.warnings : void 0;
|
|
46847
48550
|
if (!Array.isArray(warnings)) return [];
|
|
46848
48551
|
return warnings.flatMap((warning) => {
|
|
46849
|
-
if (!isRecord$
|
|
48552
|
+
if (!isRecord$3(warning)) return [];
|
|
46850
48553
|
const message = typeof warning.message === "string" ? oneLine(warning.message) : "";
|
|
46851
48554
|
if (message) return [`Route warning: ${withCliCommands(message)}`];
|
|
46852
48555
|
const code = typeof warning.code === "string" ? oneLine(warning.code) : "";
|
|
@@ -46874,11 +48577,11 @@ const DELIVERED_STATUSES = /* @__PURE__ */ new Set(["delivered", "header_confirm
|
|
|
46874
48577
|
* and has no recorded answer says so.
|
|
46875
48578
|
*/
|
|
46876
48579
|
function deliveryAttemptLines(data) {
|
|
46877
|
-
const attempts = isRecord$
|
|
48580
|
+
const attempts = isRecord$3(data) ? data.attempts : void 0;
|
|
46878
48581
|
if (!Array.isArray(attempts)) return [];
|
|
46879
48582
|
const lines = [];
|
|
46880
48583
|
for (const attempt of attempts) {
|
|
46881
|
-
if (!isRecord$
|
|
48584
|
+
if (!isRecord$3(attempt)) continue;
|
|
46882
48585
|
const text = (value) => typeof value === "string" && value ? oneLine(value) : "";
|
|
46883
48586
|
const status = text(attempt.status);
|
|
46884
48587
|
const parts = [
|
|
@@ -46972,7 +48675,7 @@ var CliOrgRefusedError = class extends Errors.CLIError {
|
|
|
46972
48675
|
this.orgId = params.orgId;
|
|
46973
48676
|
}
|
|
46974
48677
|
};
|
|
46975
|
-
function isRecord$
|
|
48678
|
+
function isRecord$2(value) {
|
|
46976
48679
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
46977
48680
|
}
|
|
46978
48681
|
function apiOrigin(apiBaseUrl) {
|
|
@@ -46999,7 +48702,7 @@ function isUuid(value) {
|
|
|
46999
48702
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
47000
48703
|
}
|
|
47001
48704
|
function isOrgAccessToken(value) {
|
|
47002
|
-
return isRecord$
|
|
48705
|
+
return isRecord$2(value) && typeof value.access_token === "string" && value.access_token.length > 0 && value.token_type === "Bearer" && typeof value.expires_in === "number" && value.expires_in > 0 && typeof value.org_id === "string" && (value.org_name === null || typeof value.org_name === "string") && typeof value.oauth_grant_id === "string";
|
|
47003
48706
|
}
|
|
47004
48707
|
function orgTokenFromResponse(token, now = Date.now) {
|
|
47005
48708
|
return {
|
|
@@ -47046,7 +48749,7 @@ async function postTokenEndpoint(params) {
|
|
|
47046
48749
|
throw new Errors.CLIError(`Could not reach the Primitive OAuth token endpoint: ${detail}`, { exit: 1 });
|
|
47047
48750
|
}
|
|
47048
48751
|
const payload = await response.json().catch(() => null);
|
|
47049
|
-
if (response.ok && isRecord$
|
|
48752
|
+
if (response.ok && isRecord$2(payload)) return {
|
|
47050
48753
|
ok: true,
|
|
47051
48754
|
payload
|
|
47052
48755
|
};
|
|
@@ -47054,7 +48757,7 @@ async function postTokenEndpoint(params) {
|
|
|
47054
48757
|
ok: true,
|
|
47055
48758
|
payload: {}
|
|
47056
48759
|
};
|
|
47057
|
-
const record = isRecord$
|
|
48760
|
+
const record = isRecord$2(payload) ? payload : {};
|
|
47058
48761
|
return {
|
|
47059
48762
|
ok: false,
|
|
47060
48763
|
status: response.status,
|
|
@@ -47257,12 +48960,12 @@ async function fetchCliOrgs(params) {
|
|
|
47257
48960
|
}
|
|
47258
48961
|
const payload = await response.json().catch(() => null);
|
|
47259
48962
|
if (!response.ok) {
|
|
47260
|
-
const error = isRecord$
|
|
48963
|
+
const error = isRecord$2(payload) && isRecord$2(payload.error) ? payload.error : {};
|
|
47261
48964
|
if (response.status === 404 && error.code === "feature_disabled") throw new Errors.CLIError("This Primitive server does not support switching organizations for CLI logins.", { exit: 1 });
|
|
47262
48965
|
throw new Errors.CLIError(`Could not list organizations${typeof error.message === "string" ? `: ${error.message}` : ` (HTTP ${response.status}).`}`, { exit: 1 });
|
|
47263
48966
|
}
|
|
47264
|
-
const data = isRecord$
|
|
47265
|
-
if (!isRecord$
|
|
48967
|
+
const data = isRecord$2(payload) ? payload.data : null;
|
|
48968
|
+
if (!isRecord$2(data) || !Array.isArray(data.orgs)) throw new Errors.CLIError("Primitive API returned an unexpected organization list.", { exit: 1 });
|
|
47266
48969
|
return data;
|
|
47267
48970
|
}
|
|
47268
48971
|
/** How long a command waits for another CLI process to finish with the saved login. */
|
|
@@ -47835,6 +49538,358 @@ async function withAttachmentWait(call, options) {
|
|
|
47835
49538
|
}
|
|
47836
49539
|
return result;
|
|
47837
49540
|
}
|
|
49541
|
+
// src/oclif/email-rule-samples.ts
|
|
49542
|
+
function errorDetail(error) {
|
|
49543
|
+
return error instanceof Error ? error.message : String(error);
|
|
49544
|
+
}
|
|
49545
|
+
function usageError$1(message) {
|
|
49546
|
+
return new Errors.CLIError(message, { exit: 2 });
|
|
49547
|
+
}
|
|
49548
|
+
function isRecord$1(value) {
|
|
49549
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
49550
|
+
}
|
|
49551
|
+
/** A path as a shell word: quoted when it holds anything but plain characters. */
|
|
49552
|
+
function shellWord(value) {
|
|
49553
|
+
return /^[\w@%+=:,./-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
49554
|
+
}
|
|
49555
|
+
/** A JSON file a flag names, parsed, or an exit-1 error saying why not. */
|
|
49556
|
+
function readJsonFileFlag(path, flag) {
|
|
49557
|
+
let text;
|
|
49558
|
+
try {
|
|
49559
|
+
text = readFileSync(path, "utf8");
|
|
49560
|
+
} catch (error) {
|
|
49561
|
+
throw new Errors.CLIError(`Could not read ${flag} ${path}: ${errorDetail(error)}`, { exit: 1 });
|
|
49562
|
+
}
|
|
49563
|
+
try {
|
|
49564
|
+
return JSON.parse(text);
|
|
49565
|
+
} catch (error) {
|
|
49566
|
+
throw new Errors.CLIError(`${flag} ${path} is not valid JSON: ${errorDetail(error)}`, { exit: 1 });
|
|
49567
|
+
}
|
|
49568
|
+
}
|
|
49569
|
+
/** The longest label the API keeps on a sample. */
|
|
49570
|
+
const SAMPLE_LABEL_MAX = 200;
|
|
49571
|
+
/**
|
|
49572
|
+
* `text` cut to `max` UTF-16 units, as the API counts a label's length,
|
|
49573
|
+
* never inside a character.
|
|
49574
|
+
*/
|
|
49575
|
+
function cutLabel(text, max) {
|
|
49576
|
+
const cut = text.slice(0, max);
|
|
49577
|
+
return (/[\uD800-\uDBFF]$/.test(cut) ? cut.slice(0, -1) : cut).trimEnd();
|
|
49578
|
+
}
|
|
49579
|
+
/**
|
|
49580
|
+
* A look-alike as a must-not-match sample: the email as written, labelled
|
|
49581
|
+
* with its kind and why the rule should not match it ("medical: ..."), or
|
|
49582
|
+
* the why alone when both do not fit a label.
|
|
49583
|
+
*/
|
|
49584
|
+
function lookAlikeSample(lookAlike) {
|
|
49585
|
+
if (!isRecord$1(lookAlike) || !isRecord$1(lookAlike.email)) return null;
|
|
49586
|
+
const kind = typeof lookAlike.kind === "string" ? lookAlike.kind : "";
|
|
49587
|
+
const why = typeof lookAlike.why === "string" ? lookAlike.why.trim() : "";
|
|
49588
|
+
const both = kind && why ? `${kind}: ${why}` : kind || why;
|
|
49589
|
+
const label = both.length <= SAMPLE_LABEL_MAX ? both : why.length <= SAMPLE_LABEL_MAX ? why : `${cutLabel(why, 197)}...`;
|
|
49590
|
+
return {
|
|
49591
|
+
expected: "no_match",
|
|
49592
|
+
...label ? { label } : {},
|
|
49593
|
+
email: lookAlike.email
|
|
49594
|
+
};
|
|
49595
|
+
}
|
|
49596
|
+
/**
|
|
49597
|
+
* The look-alikes of a generate-look-alikes answer that can be saved (each
|
|
49598
|
+
* with an email), in the answer's order. The report numbers these and the
|
|
49599
|
+
* samples file holds these, so a number means the same email in both.
|
|
49600
|
+
*/
|
|
49601
|
+
function lookAlikeEntries(answer) {
|
|
49602
|
+
const list = isRecord$1(answer) && Array.isArray(answer.look_alikes) ? answer.look_alikes : [];
|
|
49603
|
+
const entries = [];
|
|
49604
|
+
for (const item of list) {
|
|
49605
|
+
const sample = lookAlikeSample(item);
|
|
49606
|
+
if (sample && isRecord$1(item)) entries.push({
|
|
49607
|
+
lookAlike: item,
|
|
49608
|
+
sample
|
|
49609
|
+
});
|
|
49610
|
+
}
|
|
49611
|
+
return entries;
|
|
49612
|
+
}
|
|
49613
|
+
/** The look-alikes of a generate-look-alikes answer, as samples, in order. */
|
|
49614
|
+
function lookAlikeSamples(answer) {
|
|
49615
|
+
return lookAlikeEntries(answer).map((entry) => entry.sample);
|
|
49616
|
+
}
|
|
49617
|
+
/** The fields a saved made-up email takes, and the API's bounds on them. */
|
|
49618
|
+
const SAMPLE_EMAIL_KEYS = /* @__PURE__ */ new Set([
|
|
49619
|
+
"from",
|
|
49620
|
+
"to",
|
|
49621
|
+
"cc",
|
|
49622
|
+
"subject",
|
|
49623
|
+
"text",
|
|
49624
|
+
"attachment_types",
|
|
49625
|
+
"automated",
|
|
49626
|
+
"from_mailing_list",
|
|
49627
|
+
"spam_score"
|
|
49628
|
+
]);
|
|
49629
|
+
const SAMPLE_TEXT_MAX = 4e3;
|
|
49630
|
+
const SAMPLE_SUBJECT_MAX = 998;
|
|
49631
|
+
const SAMPLE_LIST_MAX = 10;
|
|
49632
|
+
/**
|
|
49633
|
+
* Refuse, naming the sample and the field, a made-up email the API would
|
|
49634
|
+
* refuse to keep: the fields it takes and their bounds. The API checks the
|
|
49635
|
+
* rest (address forms, a NUL).
|
|
49636
|
+
*/
|
|
49637
|
+
function checkSampleEmail(email, at) {
|
|
49638
|
+
const fail = (what) => {
|
|
49639
|
+
throw usageError$1(`${at}: email ${what}.`);
|
|
49640
|
+
};
|
|
49641
|
+
const unknown = Object.keys(email).find((key) => !SAMPLE_EMAIL_KEYS.has(key));
|
|
49642
|
+
if (unknown !== void 0) fail(`takes no "${unknown}" (it takes ${[...SAMPLE_EMAIL_KEYS].join(", ")})`);
|
|
49643
|
+
if (!isRecord$1(email.from) || typeof email.from.address !== "string") fail("needs from.address");
|
|
49644
|
+
const list = (key, min) => {
|
|
49645
|
+
const value = email[key];
|
|
49646
|
+
if (value === void 0 && min === 0) return;
|
|
49647
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string") || value.length < min || value.length > SAMPLE_LIST_MAX) fail(`${key} must be a list of ${min > 0 ? `${min} to ` : "at most "}${SAMPLE_LIST_MAX} strings`);
|
|
49648
|
+
};
|
|
49649
|
+
list("to", 1);
|
|
49650
|
+
list("cc", 0);
|
|
49651
|
+
list("attachment_types", 0);
|
|
49652
|
+
if (email.subject !== void 0 && (typeof email.subject !== "string" || email.subject.length > SAMPLE_SUBJECT_MAX)) fail(`subject must be a string of at most ${SAMPLE_SUBJECT_MAX} characters`);
|
|
49653
|
+
if (typeof email.text !== "string" || email.text.length === 0 || email.text.length > SAMPLE_TEXT_MAX) fail(`text must be 1 to ${SAMPLE_TEXT_MAX} characters`);
|
|
49654
|
+
for (const key of ["automated", "from_mailing_list"]) if (email[key] !== void 0 && typeof email[key] !== "boolean") fail(`${key} must be true or false`);
|
|
49655
|
+
if (email.spam_score !== void 0 && (typeof email.spam_score !== "number" || !Number.isFinite(email.spam_score))) fail("spam_score must be a number");
|
|
49656
|
+
}
|
|
49657
|
+
/** The keys a sample may carry: a saved one's id and time are taken and ignored. */
|
|
49658
|
+
const SAMPLE_KEYS = /* @__PURE__ */ new Set([
|
|
49659
|
+
"expected",
|
|
49660
|
+
"label",
|
|
49661
|
+
"email",
|
|
49662
|
+
"id",
|
|
49663
|
+
"created_at"
|
|
49664
|
+
]);
|
|
49665
|
+
/**
|
|
49666
|
+
* The samples a file holds: a JSON array of samples, as --samples-file
|
|
49667
|
+
* writes them, or a generate-look-alikes answer saved as the command printed
|
|
49668
|
+
* it (with or without --json), whose look-alikes become must-not-match
|
|
49669
|
+
* samples numbered as its report numbered them. Only a literal [] holds no
|
|
49670
|
+
* samples; an answer with none is refused, so it never empties a rule's.
|
|
49671
|
+
*/
|
|
49672
|
+
function samplesFromJson(value, source) {
|
|
49673
|
+
const data = isRecord$1(value) && isRecord$1(value.data) ? value.data : value;
|
|
49674
|
+
if (isRecord$1(data) && Array.isArray(data.look_alikes)) {
|
|
49675
|
+
const samples = lookAlikeSamples(data);
|
|
49676
|
+
if (samples.length === 0) throw usageError$1(`${source} is a generate-look-alikes answer with no look-alikes in it.`);
|
|
49677
|
+
samples.forEach((sample, index) => {
|
|
49678
|
+
checkSampleEmail(sample.email, `${source}, look-alike ${index + 1}`);
|
|
49679
|
+
});
|
|
49680
|
+
return samples;
|
|
49681
|
+
}
|
|
49682
|
+
if (!Array.isArray(data)) throw usageError$1(`${source} must hold a JSON array of samples, such as [{"expected": "no_match", "label": "a personal bill", "email": {...}}], or a generate-look-alikes answer.`);
|
|
49683
|
+
return data.map((item, index) => {
|
|
49684
|
+
const at = `${source}, sample ${index + 1}`;
|
|
49685
|
+
if (!isRecord$1(item)) throw usageError$1(`${at} is not an object.`);
|
|
49686
|
+
const unknown = Object.keys(item).find((key) => !SAMPLE_KEYS.has(key));
|
|
49687
|
+
if (unknown !== void 0) throw usageError$1(`${at}: "${unknown}" is not a sample field; a sample takes expected, label and email.`);
|
|
49688
|
+
if (item.expected !== "match" && item.expected !== "no_match") throw usageError$1(`${at}: expected must be "match" or "no_match".`);
|
|
49689
|
+
if (!isRecord$1(item.email)) throw usageError$1(`${at}: email must be an object, as test-email-rule-on-sample takes it.`);
|
|
49690
|
+
checkSampleEmail(item.email, at);
|
|
49691
|
+
const label = item.label;
|
|
49692
|
+
if (label !== void 0 && label !== null) {
|
|
49693
|
+
if (typeof label !== "string") throw usageError$1(`${at}: label must be a string.`);
|
|
49694
|
+
if (label.trim() === "") throw usageError$1(`${at}: label is blank; leave it out or say what the email stands for.`);
|
|
49695
|
+
if (label.trim().length > SAMPLE_LABEL_MAX) throw usageError$1(`${at}: label is longer than ${SAMPLE_LABEL_MAX} characters.`);
|
|
49696
|
+
}
|
|
49697
|
+
return {
|
|
49698
|
+
expected: item.expected,
|
|
49699
|
+
...label !== void 0 ? { label } : {},
|
|
49700
|
+
email: item.email
|
|
49701
|
+
};
|
|
49702
|
+
});
|
|
49703
|
+
}
|
|
49704
|
+
/**
|
|
49705
|
+
* The numbers --pick names, from 1, in the order given and without repeats,
|
|
49706
|
+
* or a usage error: "2,4" or "2 4". `count` is how many there are to pick
|
|
49707
|
+
* from; a number past it is refused, rather than skipped.
|
|
49708
|
+
*/
|
|
49709
|
+
function pickNumbers(pick, count, source) {
|
|
49710
|
+
const parts = pick.split(/[\s,]+/).map((part) => part.trim()).filter((part) => part !== "");
|
|
49711
|
+
if (parts.length === 0) throw usageError$1("--pick needs the numbers of the samples to take, such as --pick 2,4.");
|
|
49712
|
+
const chosen = [];
|
|
49713
|
+
for (const part of parts) {
|
|
49714
|
+
if (!/^\d+$/.test(part)) throw usageError$1(`--pick ${pick}: "${part}" is not a sample number.`);
|
|
49715
|
+
const n = Number(part);
|
|
49716
|
+
if (n < 1 || n > count) throw usageError$1(count === 0 ? `--pick ${pick}: ${source} holds no samples.` : `--pick ${pick}: ${n} is not a sample number; ${source} holds ${count === 1 ? "sample 1" : `samples 1 to ${count}`}.`);
|
|
49717
|
+
if (!chosen.includes(n)) chosen.push(n);
|
|
49718
|
+
}
|
|
49719
|
+
return chosen;
|
|
49720
|
+
}
|
|
49721
|
+
/** The samples --pick chooses, by their numbers from 1. */
|
|
49722
|
+
function pickSamples(samples, pick, source) {
|
|
49723
|
+
return pickNumbers(pick, samples.length, source).map((n) => samples[n - 1]);
|
|
49724
|
+
}
|
|
49725
|
+
/**
|
|
49726
|
+
* The number a person knows a sample a command sent by: its number in the
|
|
49727
|
+
* samples file when --pick took it, else its place from 1 in what was sent.
|
|
49728
|
+
* `index` is its place in what was sent, from 0, as the API gives it.
|
|
49729
|
+
*/
|
|
49730
|
+
function sentSampleNumber(index, flags) {
|
|
49731
|
+
const pick = flags?.[PICK_FLAG];
|
|
49732
|
+
if (typeof pick !== "string") return index + 1;
|
|
49733
|
+
try {
|
|
49734
|
+
return pickNumbers(pick, Number.MAX_SAFE_INTEGER, "")[index] ?? index + 1;
|
|
49735
|
+
} catch {
|
|
49736
|
+
return index + 1;
|
|
49737
|
+
}
|
|
49738
|
+
}
|
|
49739
|
+
/** The samples file flags: replace (create and update), add (update), and test. */
|
|
49740
|
+
const SAMPLES_FILE_FLAG = "expectation-samples-file";
|
|
49741
|
+
const ADD_SAMPLES_FILE_FLAG = "add-expectation-samples-file";
|
|
49742
|
+
const EXPECT_SAMPLES_FILE_FLAG = "expect-samples-file";
|
|
49743
|
+
const PICK_FLAG = "pick";
|
|
49744
|
+
/** generate-look-alikes' flag for the file it writes them to. */
|
|
49745
|
+
const LOOK_ALIKES_SAMPLES_FILE_FLAG = "samples-file";
|
|
49746
|
+
/** The samples a samples file flag names, only those --pick chooses if given. */
|
|
49747
|
+
function readSamplesFile(path, flag, pick) {
|
|
49748
|
+
const source = `--${flag} ${path}`;
|
|
49749
|
+
const all = samplesFromJson(readJsonFileFlag(path, `--${flag}`), source);
|
|
49750
|
+
const samples = typeof pick === "string" ? pickSamples(all, pick, source) : all;
|
|
49751
|
+
if (samples.length > 20) throw usageError$1(`${source} holds ${samples.length} samples, and a rule or a test takes at most 20; take some with --pick.`);
|
|
49752
|
+
return samples;
|
|
49753
|
+
}
|
|
49754
|
+
/** The one of `names` given as a file path, or null; --pick without one is refused. */
|
|
49755
|
+
function givenSamplesFlag(flags, names) {
|
|
49756
|
+
const given = names.find((name) => typeof flags[name] === "string") ?? null;
|
|
49757
|
+
if (given === null && typeof flags["pick"] === "string") throw usageError$1(`--pick chooses from a samples file: pass it with ${names.map((name) => `--${name}`).join(" or ")}.`);
|
|
49758
|
+
return given;
|
|
49759
|
+
}
|
|
49760
|
+
/**
|
|
49761
|
+
* The body fields a samples file sets on create-email-rule and
|
|
49762
|
+
* update-email-rule: expectation_samples (on update it replaces them) or, on
|
|
49763
|
+
* update, add_expectation_samples. With --pick, only the samples it names.
|
|
49764
|
+
*/
|
|
49765
|
+
function samplesBodyFields(canAdd) {
|
|
49766
|
+
const names = canAdd ? [SAMPLES_FILE_FLAG, ADD_SAMPLES_FILE_FLAG] : [SAMPLES_FILE_FLAG];
|
|
49767
|
+
return (flags) => {
|
|
49768
|
+
const flag = givenSamplesFlag(flags, names);
|
|
49769
|
+
if (flag === null) return [];
|
|
49770
|
+
const path = flags[flag];
|
|
49771
|
+
const samples = readSamplesFile(path, flag, flags[PICK_FLAG]);
|
|
49772
|
+
if (samples.length === 0 && !(canAdd && flag === "expectation-samples-file")) throw usageError$1(`--${flag} ${path} holds no samples.`);
|
|
49773
|
+
return [{
|
|
49774
|
+
flag: `--${flag}`,
|
|
49775
|
+
property: flag === "add-expectation-samples-file" ? "add_expectation_samples" : "expectation_samples",
|
|
49776
|
+
value: samples
|
|
49777
|
+
}];
|
|
49778
|
+
};
|
|
49779
|
+
}
|
|
49780
|
+
/**
|
|
49781
|
+
* The samples test-email-rule expects, from --expect-samples-file (only
|
|
49782
|
+
* those --pick chooses if given), or null without the flag. They go in
|
|
49783
|
+
* expect.samples.
|
|
49784
|
+
*/
|
|
49785
|
+
function expectSamples(flags) {
|
|
49786
|
+
const flag = givenSamplesFlag(flags, [EXPECT_SAMPLES_FILE_FLAG]);
|
|
49787
|
+
if (flag === null) return null;
|
|
49788
|
+
const path = flags[flag];
|
|
49789
|
+
const samples = readSamplesFile(path, flag, flags[PICK_FLAG]);
|
|
49790
|
+
if (samples.length === 0) throw usageError$1(`--${flag} ${path} holds no samples.`);
|
|
49791
|
+
return samples;
|
|
49792
|
+
}
|
|
49793
|
+
/** --pick's help, naming the flags it chooses from. */
|
|
49794
|
+
function pickFlagHelp(flags) {
|
|
49795
|
+
return `Take only these samples from ${flags.map((flag) => `--${flag}`).join(" or ")}, by their numbers from 1 as generate-look-alikes printed them: --pick 2,4. A number the file does not hold is refused.`;
|
|
49796
|
+
}
|
|
49797
|
+
/** What the rule decided for a look-alike, and what its status makes of it. */
|
|
49798
|
+
function decidedLine(outcome, status) {
|
|
49799
|
+
if (outcome === "not_decided") return "The rule did not decide it: it was not reached in time, or deciding it failed.";
|
|
49800
|
+
if (outcome === "matched") return status === "undecided" ? "The rule matched it, but it reads a spam score, which a look-alike does not carry, so this one is undecided." : "The rule matched it, so mail like it would be shared.";
|
|
49801
|
+
const decided = outcome === "near_miss" ? "The rule nearly matched it, but did not" : "The rule did not match it";
|
|
49802
|
+
return status === "undecided" ? `${decided}, on something it could not decide (its classification failed, a fact or a question's answer is missing, or it reads a spam score, which a look-alike does not carry), so this one is undecided.` : `${decided}.`;
|
|
49803
|
+
}
|
|
49804
|
+
/**
|
|
49805
|
+
* The stderr report of generate-look-alikes: each look-alike numbered as the
|
|
49806
|
+
* samples file numbers it, with its status, kind, subject, sender, why the
|
|
49807
|
+
* rule should not match it and what the rule decided; the counts; the
|
|
49808
|
+
* answer's note; and how to save the ones you choose. With --samples-file it
|
|
49809
|
+
* writes every look-alike there as a must-not-match sample, in that order.
|
|
49810
|
+
*/
|
|
49811
|
+
function lookAlikesReport(data, request) {
|
|
49812
|
+
const file = request.flags?.[LOOK_ALIKES_SAMPLES_FILE_FLAG];
|
|
49813
|
+
if (!isRecord$1(data)) {
|
|
49814
|
+
if (typeof file !== "string") return { lines: [] };
|
|
49815
|
+
const message = `Did not write --${LOOK_ALIKES_SAMPLES_FILE_FLAG} ${file}: the answer held no look-alikes to write.`;
|
|
49816
|
+
return {
|
|
49817
|
+
lines: [message],
|
|
49818
|
+
exitCode: 1,
|
|
49819
|
+
error: {
|
|
49820
|
+
code: "samples_file_not_written",
|
|
49821
|
+
message
|
|
49822
|
+
}
|
|
49823
|
+
};
|
|
49824
|
+
}
|
|
49825
|
+
const entries = lookAlikeEntries(data);
|
|
49826
|
+
const lines = [];
|
|
49827
|
+
entries.forEach(({ lookAlike }, index) => {
|
|
49828
|
+
const email = lookAlike.email;
|
|
49829
|
+
const from = isRecord$1(email.from) && typeof email.from.address === "string" ? email.from.address : "an unknown sender";
|
|
49830
|
+
const subject = typeof email.subject === "string" && email.subject ? `"${email.subject}"` : "(no subject)";
|
|
49831
|
+
const status = typeof lookAlike.status === "string" ? lookAlike.status : "undecided";
|
|
49832
|
+
const kind = typeof lookAlike.kind === "string" ? lookAlike.kind : "other";
|
|
49833
|
+
const outcome = typeof lookAlike.outcome === "string" ? lookAlike.outcome : "not_decided";
|
|
49834
|
+
lines.push(`${index + 1}. ${status} ${kind} ${subject} from ${from}`);
|
|
49835
|
+
if (typeof lookAlike.why === "string" && lookAlike.why.trim()) lines.push(` Why it should not match: ${lookAlike.why.trim()}`);
|
|
49836
|
+
lines.push(` ${decidedLine(outcome, status)}`);
|
|
49837
|
+
});
|
|
49838
|
+
const counts = isRecord$1(data.counts) ? data.counts : {};
|
|
49839
|
+
const count = (key) => typeof counts[key] === "number" ? counts[key] : 0;
|
|
49840
|
+
const dropped = count("dropped");
|
|
49841
|
+
lines.push(`Counts: ${count("passed")} passed, ${count("failed")} failed, ${count("undecided")} undecided${dropped > 0 ? `; ${dropped} asked for did not come back` : ""}.`);
|
|
49842
|
+
if (typeof data.note === "string" && data.note.trim()) lines.push(data.note.trim());
|
|
49843
|
+
const numbered = (status) => entries.map(({ lookAlike }, index) => lookAlike.status === status ? index + 1 : 0).filter((n) => n > 0);
|
|
49844
|
+
const matched = numbered("failed");
|
|
49845
|
+
const undecided = numbered("undecided");
|
|
49846
|
+
const chosen = matched.length > 0 ? matched : undecided.length > 0 ? numbered("passed") : [];
|
|
49847
|
+
const pick = chosen.length > 0 ? ` --pick ${chosen.join(",")}` : "";
|
|
49848
|
+
const body = isRecord$1(request.body) ? request.body : {};
|
|
49849
|
+
const ruleId = typeof body.rule_id === "string" ? body.rule_id : null;
|
|
49850
|
+
const requestId = typeof body.request_id === "string" ? body.request_id : null;
|
|
49851
|
+
const target = typeof file === "string" ? shellWord(file) : "<file>";
|
|
49852
|
+
const save = ruleId ? `\`primitive email-rules update-email-rule ${ruleId} --${ADD_SAMPLES_FILE_FLAG} ${target}${pick}\`` : requestId ? `\`primitive email-rules accept-email-rule-request ${requestId} --${SAMPLES_FILE_FLAG} ${target}${pick}\` when the member the request asks accepts it` : `\`primitive email-rules create-email-rule ... --${SAMPLES_FILE_FLAG} ${target}${pick}\` when you save the rule`;
|
|
49853
|
+
const one = (count, single, many) => count === 1 ? single : many;
|
|
49854
|
+
const noneDecided = entries.length > 0 && undecided.length === entries.length ? `None of them was decided (each says why above), so saved as they are they may stay undecided; if the rule reads a spam score, give each email a spam_score${typeof file === "string" ? " in the file" : ""} first.` : "";
|
|
49855
|
+
const which = matched.length > 0 ? `Keep ${one(matched.length, "the one it matched as an email", "the ones it matched as emails")} it must not match` : chosen.length > 0 ? `Keep ${one(chosen.length, "the one it did not match as an email", "the ones it did not match as emails")} it must not match (an undecided one may stay undecided as a sample)` : `Keep ${one(entries.length, "it as an email", "them as emails")} the rule must not match`;
|
|
49856
|
+
const test = matched.length > 0 ? ` Test an edited rule on ${one(matched.length, "it", "them")} with \`primitive email-rules test-email-rule ${ruleId ? `--rule-id ${ruleId}` : "--body-file <body.json>"} --${EXPECT_SAMPLES_FILE_FLAG} ${target}${pick}\`.` : "";
|
|
49857
|
+
if (typeof file === "string") {
|
|
49858
|
+
const samples = entries.map((entry) => entry.sample);
|
|
49859
|
+
if (samples.length === 0) {
|
|
49860
|
+
const message = `Did not write --${LOOK_ALIKES_SAMPLES_FILE_FLAG} ${file}: no look-alikes came back, so it was left as it was.`;
|
|
49861
|
+
return {
|
|
49862
|
+
lines: [...lines, message],
|
|
49863
|
+
exitCode: 1,
|
|
49864
|
+
error: {
|
|
49865
|
+
code: "samples_file_not_written",
|
|
49866
|
+
message
|
|
49867
|
+
}
|
|
49868
|
+
};
|
|
49869
|
+
}
|
|
49870
|
+
try {
|
|
49871
|
+
writeFileSync(file, `${JSON.stringify(samples, null, 2)}\n`);
|
|
49872
|
+
} catch (error) {
|
|
49873
|
+
const message = `Could not write --${LOOK_ALIKES_SAMPLES_FILE_FLAG} ${file}: ${errorDetail(error)}`;
|
|
49874
|
+
return {
|
|
49875
|
+
lines: [
|
|
49876
|
+
...lines,
|
|
49877
|
+
message,
|
|
49878
|
+
"The JSON printed above holds these same look-alikes: save it to a file and give that file instead. Running generate-look-alikes again writes new ones."
|
|
49879
|
+
],
|
|
49880
|
+
exitCode: 1,
|
|
49881
|
+
error: {
|
|
49882
|
+
code: "samples_file_not_written",
|
|
49883
|
+
message
|
|
49884
|
+
}
|
|
49885
|
+
};
|
|
49886
|
+
}
|
|
49887
|
+
const wrote = `Wrote ${one(samples.length, "the look-alike", `the ${samples.length} look-alikes`)} to ${file}, numbered as above.`;
|
|
49888
|
+
lines.push(noneDecided ? `${wrote} ${noneDecided}` : `${wrote} ${which} with ${save}; --pick takes any of the numbers.${test}`);
|
|
49889
|
+
} else if (noneDecided) lines.push(noneDecided);
|
|
49890
|
+
else if (entries.length > 0) lines.push(`${which}: save the JSON printed above to a file and give it to ${save}; --pick takes any of the numbers above.${test}`);
|
|
49891
|
+
return { lines };
|
|
49892
|
+
}
|
|
47838
49893
|
// src/oclif/email-rule-authoring.ts
|
|
47839
49894
|
const EMAIL_ID = /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i;
|
|
47840
49895
|
/** Whether `id` has the shape of an email id (a UUID). */
|
|
@@ -47847,9 +49902,6 @@ function usageError(message) {
|
|
|
47847
49902
|
function isRecord(value) {
|
|
47848
49903
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
47849
49904
|
}
|
|
47850
|
-
function errorDetail(error) {
|
|
47851
|
-
return error instanceof Error ? error.message : String(error);
|
|
47852
|
-
}
|
|
47853
49905
|
/** `count` and the word for it: "1 expectation", "2 expectations". */
|
|
47854
49906
|
function plural$1(count, one, many) {
|
|
47855
49907
|
return `${count} ${count === 1 ? one : many}`;
|
|
@@ -47890,7 +49942,7 @@ function checkExpectations(value, label) {
|
|
|
47890
49942
|
if (!isRecord(value)) throw usageError(`${label} must hold a JSON object such as {"match": ["<email-id>"], "no_match": ["<email-id>"]}.`);
|
|
47891
49943
|
const expectations = {};
|
|
47892
49944
|
for (const [key, list] of Object.entries(value)) {
|
|
47893
|
-
if (key !== "match" && key !== "no_match") throw usageError(`${label} takes only "match" and "no_match"; "${key}" is not one of them.`);
|
|
49945
|
+
if (key !== "match" && key !== "no_match") throw usageError(key === "samples" ? `${label} takes emails by id only; give made-up emails with ${label.startsWith("--expectations-file") ? "--expectation-samples-file" : "--expect-samples-file"}.` : `${label} takes only "match" and "no_match"; "${key}" is not one of them.`);
|
|
47894
49946
|
if (!Array.isArray(list) || list.some((id) => typeof id !== "string")) throw usageError(`${label}: "${key}" must be a list of email ids.`);
|
|
47895
49947
|
const ids = [];
|
|
47896
49948
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -47906,33 +49958,19 @@ function checkExpectations(value, label) {
|
|
|
47906
49958
|
assertDisjoint(expectations, label);
|
|
47907
49959
|
return expectations;
|
|
47908
49960
|
}
|
|
47909
|
-
/**
|
|
47910
|
-
|
|
47911
|
-
|
|
47912
|
-
|
|
47913
|
-
text = readFileSync(path, "utf8");
|
|
47914
|
-
} catch (error) {
|
|
47915
|
-
throw new Errors.CLIError(`Could not read ${flag} ${path}: ${errorDetail(error)}`, { exit: 1 });
|
|
47916
|
-
}
|
|
47917
|
-
try {
|
|
47918
|
-
return JSON.parse(text);
|
|
47919
|
-
} catch (error) {
|
|
47920
|
-
throw new Errors.CLIError(`${flag} ${path} is not valid JSON: ${errorDetail(error)}`, { exit: 1 });
|
|
47921
|
-
}
|
|
47922
|
-
}
|
|
47923
|
-
/** `expect` for test-email-rule, from --expect-file or the id flags. */
|
|
49961
|
+
/**
|
|
49962
|
+
* `expect` for test-email-rule, from --expect-file or the id flags, with the
|
|
49963
|
+
* made-up emails --expect-samples-file holds in expect.samples.
|
|
49964
|
+
*/
|
|
47924
49965
|
function expectBodyFields(flags) {
|
|
49966
|
+
const samples = expectSamples(flags);
|
|
47925
49967
|
const file = flags["expect-file"];
|
|
47926
|
-
if (typeof file === "string") {
|
|
47927
|
-
const label = `--expect-file ${file}`;
|
|
47928
|
-
return [{
|
|
47929
|
-
flag: "--expect-file",
|
|
47930
|
-
property: "expect",
|
|
47931
|
-
value: checkExpectations(readJsonFileFlag(file, "--expect-file"), label)
|
|
47932
|
-
}];
|
|
47933
|
-
}
|
|
47934
49968
|
const expect = {};
|
|
47935
49969
|
const given = [];
|
|
49970
|
+
if (typeof file === "string") {
|
|
49971
|
+
Object.assign(expect, checkExpectations(readJsonFileFlag(file, "--expect-file"), `--expect-file ${file}`));
|
|
49972
|
+
given.push("--expect-file");
|
|
49973
|
+
}
|
|
47936
49974
|
const match = flags["expect-match"];
|
|
47937
49975
|
if (Array.isArray(match)) {
|
|
47938
49976
|
expect.match = parseEmailIdList(match, "--expect-match");
|
|
@@ -47943,8 +49981,12 @@ function expectBodyFields(flags) {
|
|
|
47943
49981
|
expect.no_match = parseEmailIdList(noMatch, "--expect-no-match");
|
|
47944
49982
|
given.push("--expect-no-match");
|
|
47945
49983
|
}
|
|
47946
|
-
if (given.length === 0) return [];
|
|
47947
49984
|
assertDisjoint(expect, given.join(" and "));
|
|
49985
|
+
if (samples) {
|
|
49986
|
+
expect.samples = samples;
|
|
49987
|
+
given.push(`--${EXPECT_SAMPLES_FILE_FLAG}`);
|
|
49988
|
+
}
|
|
49989
|
+
if (given.length === 0) return [];
|
|
47948
49990
|
return [{
|
|
47949
49991
|
flag: given.join(", "),
|
|
47950
49992
|
property: "expect",
|
|
@@ -48081,18 +50123,72 @@ function expectationLine(result, results) {
|
|
|
48081
50123
|
const note = result.status === "undecided" ? undecidedNote(undecidedReason(result, results)) : void 0;
|
|
48082
50124
|
return ` ${result.status}: ${result.email_id} expected ${result.expected}, got ${result.outcome}${note ? ` (${note})` : ""}`;
|
|
48083
50125
|
}
|
|
48084
|
-
|
|
50126
|
+
/** The made-up emails a test's expectations judged, in order. */
|
|
50127
|
+
function sampleJudgments(expectations) {
|
|
50128
|
+
return (isRecord(expectations) && Array.isArray(expectations.samples) ? expectations.samples : []).filter(isRecord).map((item, position) => ({
|
|
50129
|
+
index: typeof item.index === "number" ? item.index : position,
|
|
50130
|
+
label: typeof item.label === "string" ? item.label : null,
|
|
50131
|
+
expected: String(item.expected ?? ""),
|
|
50132
|
+
outcome: String(item.outcome ?? "not_decided"),
|
|
50133
|
+
status: String(item.status ?? "undecided"),
|
|
50134
|
+
question_answers: Array.isArray(item.question_answers) ? item.question_answers.filter(isRecord) : []
|
|
50135
|
+
}));
|
|
50136
|
+
}
|
|
50137
|
+
function sampleNamer(request) {
|
|
50138
|
+
const sent = isRecord(request?.body) && isRecord(request.body.expect) && Array.isArray(request.body.expect.samples);
|
|
50139
|
+
const fromFile = sent && typeof request?.flags?.["expect-samples-file"] === "string";
|
|
50140
|
+
return ({ index, label }) => {
|
|
50141
|
+
const n = fromFile ? sentSampleNumber(index, request?.flags) : index + 1;
|
|
50142
|
+
const name = sent ? `sample ${n}` : `saved sample ${n}`;
|
|
50143
|
+
return label ? `${name} ("${label}")` : name;
|
|
50144
|
+
};
|
|
50145
|
+
}
|
|
50146
|
+
/**
|
|
50147
|
+
* Why a made-up email is undecided, and whether a later test can decide it:
|
|
50148
|
+
* yes when it was not reached or a question went unanswered; maybe not
|
|
50149
|
+
* otherwise, since a spam score it does not carry, a fact its
|
|
50150
|
+
* classification left out or a failed classification look the same. A
|
|
50151
|
+
* spam score left unset makes one undecided even with a question
|
|
50152
|
+
* unanswered, which then reads as decidable later: the answer cannot tell.
|
|
50153
|
+
*/
|
|
50154
|
+
function sampleUndecidedReason(sample) {
|
|
50155
|
+
if (sample.outcome === "not_decided") return {
|
|
50156
|
+
why: "it was not reached in time, or deciding it failed",
|
|
50157
|
+
resolvable: true
|
|
50158
|
+
};
|
|
50159
|
+
const answers = sample.question_answers;
|
|
50160
|
+
if (answers.some((answer) => answer.answer === null) && !answers.some((answer) => answer.answer === false)) return {
|
|
50161
|
+
why: "a question went unanswered",
|
|
50162
|
+
resolvable: true
|
|
50163
|
+
};
|
|
50164
|
+
return {
|
|
50165
|
+
why: "it reads a spam score the sample does not carry, a fact its classification left out, or its classification failed",
|
|
50166
|
+
resolvable: false,
|
|
50167
|
+
may: true
|
|
50168
|
+
};
|
|
50169
|
+
}
|
|
50170
|
+
function sampleLines(sample, name) {
|
|
50171
|
+
const note = sample.status === "undecided" ? ` (${undecidedNote(sampleUndecidedReason(sample))})` : "";
|
|
50172
|
+
const lines = [` ${sample.status}: ${name(sample)} expected ${sample.expected}, got ${sample.outcome}${note}`];
|
|
50173
|
+
if (sample.question_answers.length > 0) lines.push(` questions: ${sample.question_answers.map(answerText).join("; ")}`);
|
|
50174
|
+
return lines;
|
|
50175
|
+
}
|
|
50176
|
+
function expectationsLines(expectations, testResults, name = sampleNamer()) {
|
|
48085
50177
|
if (!expectations) return [];
|
|
48086
50178
|
const results = Array.isArray(expectations.results) ? expectations.results : [];
|
|
48087
50179
|
const passed = expectations.counts?.passed ?? 0;
|
|
48088
50180
|
const failed = expectations.counts?.failed ?? 0;
|
|
48089
50181
|
const undecided = expectations.counts?.undecided ?? 0;
|
|
48090
50182
|
if (expectations.passed && failed === 0 && undecided === 0) return [`Expectations: all ${passed} passed.`];
|
|
48091
|
-
return [
|
|
48092
|
-
|
|
48093
|
-
|
|
48094
|
-
|
|
48095
|
-
|
|
50183
|
+
return [
|
|
50184
|
+
`Expectations: ${[
|
|
50185
|
+
failed > 0 ? `${failed} failed` : "",
|
|
50186
|
+
undecided > 0 ? `${undecided} undecided` : "",
|
|
50187
|
+
`${passed} passed`
|
|
50188
|
+
].filter((part) => part !== "").join(", ")}.`,
|
|
50189
|
+
...results.filter((result) => result.status !== "passed").map((result) => expectationLine(result, testResults)),
|
|
50190
|
+
...sampleJudgments(expectations).filter((sample) => sample.status !== "passed").flatMap((sample) => sampleLines(sample, name))
|
|
50191
|
+
];
|
|
48096
50192
|
}
|
|
48097
50193
|
const NOT_MATCHED_ORDER = {
|
|
48098
50194
|
near_miss: 1,
|
|
@@ -48154,14 +50250,24 @@ function notMatchedLines(results, expectations) {
|
|
|
48154
50250
|
* test can decide it;
|
|
48155
50251
|
* - else nothing: the exit code stays 0.
|
|
48156
50252
|
*/
|
|
48157
|
-
function expectationsOutcome(expectations, testResults = []) {
|
|
50253
|
+
function expectationsOutcome(expectations, testResults = [], name = sampleNamer()) {
|
|
48158
50254
|
if (!expectations) return {};
|
|
48159
50255
|
const results = Array.isArray(expectations.results) ? expectations.results : [];
|
|
48160
|
-
const
|
|
48161
|
-
const
|
|
50256
|
+
const samples = sampleJudgments(expectations);
|
|
50257
|
+
const failed = [...results.filter((result) => result.status === "failed").map((result) => result.email_id), ...samples.filter((sample) => sample.status === "failed").map((sample) => name({
|
|
50258
|
+
index: sample.index,
|
|
50259
|
+
label: null
|
|
50260
|
+
}))];
|
|
50261
|
+
const undecided = [...results.filter((result) => result.status === "undecided").map((result) => ({
|
|
48162
50262
|
id: result.email_id,
|
|
48163
50263
|
reason: undecidedReason(result, testResults)
|
|
48164
|
-
}))
|
|
50264
|
+
})), ...samples.filter((sample) => sample.status === "undecided").map((sample) => ({
|
|
50265
|
+
id: name({
|
|
50266
|
+
index: sample.index,
|
|
50267
|
+
label: null
|
|
50268
|
+
}),
|
|
50269
|
+
reason: sampleUndecidedReason(sample)
|
|
50270
|
+
}))];
|
|
48165
50271
|
const unresolvable = undecided.filter(({ reason }) => !reason.resolvable);
|
|
48166
50272
|
const failedCount = Math.max(expectations.counts?.failed ?? 0, failed.length);
|
|
48167
50273
|
const undecidedCount = Math.max(expectations.counts?.undecided ?? 0, undecided.length);
|
|
@@ -48177,7 +50283,7 @@ function expectationsOutcome(expectations, testResults = []) {
|
|
|
48177
50283
|
exitCode: 1,
|
|
48178
50284
|
error: {
|
|
48179
50285
|
code: "expectations_unresolvable",
|
|
48180
|
-
message: `${plural$1(unresolvable.length, "expectation is", "expectations are")} undecided, and testing again will not decide ${unresolvable.length === 1 ? "it" : "them"}: ${why(unresolvable)}.`
|
|
50286
|
+
message: `${plural$1(unresolvable.length, "expectation is", "expectations are")} undecided, and testing again ${unresolvable.every(({ reason }) => reason.may) ? "may not decide" : "will not decide"} ${unresolvable.length === 1 ? "it" : "them"}: ${why(unresolvable)}.`
|
|
48181
50287
|
}
|
|
48182
50288
|
};
|
|
48183
50289
|
if (undecidedCount > 0) return {
|
|
@@ -48190,8 +50296,9 @@ function expectationsOutcome(expectations, testResults = []) {
|
|
|
48190
50296
|
return {};
|
|
48191
50297
|
}
|
|
48192
50298
|
/** The stderr report and exit code of a test-email-rule answer. */
|
|
48193
|
-
function testEmailRuleReport(data) {
|
|
50299
|
+
function testEmailRuleReport(data, request) {
|
|
48194
50300
|
if (!isRecord(data)) return { lines: [] };
|
|
50301
|
+
const name = sampleNamer(request);
|
|
48195
50302
|
const result = data;
|
|
48196
50303
|
const results = Array.isArray(result.results) ? result.results : [];
|
|
48197
50304
|
const summary = isRecord(result.summary) ? result.summary : void 0;
|
|
@@ -48201,24 +50308,58 @@ function testEmailRuleReport(data) {
|
|
|
48201
50308
|
...scopeWarningLines(result.scope_warnings),
|
|
48202
50309
|
...conditionLines(summary),
|
|
48203
50310
|
...questionLines(summary, results),
|
|
48204
|
-
...expectationsLines(result.expectations, results),
|
|
50311
|
+
...expectationsLines(result.expectations, results, name),
|
|
48205
50312
|
...notMatchedLines(results, result.expectations)
|
|
48206
50313
|
],
|
|
48207
|
-
...expectationsOutcome(result.expectations, results)
|
|
50314
|
+
...expectationsOutcome(result.expectations, results, name)
|
|
50315
|
+
};
|
|
50316
|
+
}
|
|
50317
|
+
/** A list of numbers from 0, as the API gives sample indexes. */
|
|
50318
|
+
function indexList(value) {
|
|
50319
|
+
return Array.isArray(value) ? value.filter((n) => typeof n === "number") : [];
|
|
50320
|
+
}
|
|
50321
|
+
/**
|
|
50322
|
+
* How a rule write names a made-up email its check gives by index, from 0 in
|
|
50323
|
+
* the set judged: those this write sends in place of the rule's
|
|
50324
|
+
* (expectation_samples), or the rule's saved ones oldest first and then
|
|
50325
|
+
* those it adds (add_expectation_samples). One this write sent is "sample
|
|
50326
|
+
* N", its number in the samples file --pick took it from (or its place in
|
|
50327
|
+
* what was sent); any other is "saved sample N", its place among the rule's.
|
|
50328
|
+
* With its label when known. `saved` is the answer's expectation_samples,
|
|
50329
|
+
* every one the rule keeps after the write, when it has them.
|
|
50330
|
+
*/
|
|
50331
|
+
function writtenSampleNamer(body, flags, saved) {
|
|
50332
|
+
const sent = isRecord(body) && Array.isArray(body.expectation_samples) ? body.expectation_samples : null;
|
|
50333
|
+
const added = isRecord(body) && Array.isArray(body.add_expectation_samples) ? body.add_expectation_samples : null;
|
|
50334
|
+
const kept = Array.isArray(saved) ? saved : null;
|
|
50335
|
+
const labelled = (name, sample) => isRecord(sample) && typeof sample.label === "string" && sample.label ? `${name} ("${sample.label}")` : name;
|
|
50336
|
+
return (index) => {
|
|
50337
|
+
if (sent) return labelled(`sample ${sentSampleNumber(index, flags)}`, sent[index]);
|
|
50338
|
+
if (added) {
|
|
50339
|
+
const start = kept ? kept.length - added.length : null;
|
|
50340
|
+
if (start === null) return `made-up email ${index + 1}`;
|
|
50341
|
+
if (index >= start) return labelled(`sample ${sentSampleNumber(index - start, flags)}`, added[index - start]);
|
|
50342
|
+
}
|
|
50343
|
+
return labelled(`saved sample ${index + 1}`, kept?.[index]);
|
|
48208
50344
|
};
|
|
48209
50345
|
}
|
|
48210
|
-
function expectationsCheckLine(check, ruleId) {
|
|
50346
|
+
function expectationsCheckLine(check, ruleId, name = (index) => `saved sample ${index + 1}`) {
|
|
48211
50347
|
if (!isRecord(check)) return void 0;
|
|
48212
50348
|
const on = typeof check.version === "number" ? `version ${check.version}` : "the current version";
|
|
48213
50349
|
const failed = Array.isArray(check.failed) ? check.failed : [];
|
|
48214
50350
|
const undecided = Array.isArray(check.undecided) ? check.undecided : [];
|
|
48215
|
-
|
|
50351
|
+
const failedSamples = indexList(check.failed_samples);
|
|
50352
|
+
const undecidedSamples = indexList(check.undecided_samples);
|
|
50353
|
+
if (check.passed && failed.length === 0 && undecided.length === 0 && failedSamples.length === 0 && undecidedSamples.length === 0) return `Expectations on ${on}: all passed.`;
|
|
48216
50354
|
const parts = [];
|
|
48217
50355
|
if (failed.length > 0) parts.push(`${failed.length} failed (${failed.join(", ")})`);
|
|
48218
|
-
|
|
50356
|
+
if (failedSamples.length > 0) parts.push(`${plural$1(failedSamples.length, "made-up email", "made-up emails")} failed (${failedSamples.map(name).join(", ")})`);
|
|
50357
|
+
if (failed.length === 0 && failedSamples.length === 0 && (undecided.length > 0 || undecidedSamples.length > 0)) parts.push("none failed");
|
|
48219
50358
|
if (undecided.length > 0) parts.push(`${undecided.length} undecided (${undecided.join(", ")})`);
|
|
48220
|
-
|
|
48221
|
-
|
|
50359
|
+
if (undecidedSamples.length > 0) parts.push(`${plural$1(undecidedSamples.length, "made-up email", "made-up emails")} not judged by this write (${undecidedSamples.map(name).join(", ")})`);
|
|
50360
|
+
const rest = undecided.length > 0 || undecidedSamples.length > 0 ? ` To decide the rest, run \`primitive email-rules test-email-rule --rule-id ${ruleId}\`.` : "";
|
|
50361
|
+
const note = typeof check.note === "string" && check.note.trim() ? ` ${check.note.trim()}` : "";
|
|
50362
|
+
return `Expectations on ${on}: ${parts.length > 0 ? parts.join(", ") : "not passed"}.${rest}${note}`;
|
|
48222
50363
|
}
|
|
48223
50364
|
/** The flag that makes a rule write's exit code follow its expectations_check. */
|
|
48224
50365
|
const FAIL_ON_EXPECTATIONS_FLAG = "fail-on-expectations";
|
|
@@ -48230,7 +50371,7 @@ const FAIL_ON_EXPECTATIONS_FLAG = "fail-on-expectations";
|
|
|
48230
50371
|
* `passed` as false or as null when nothing failed and something is
|
|
48231
50372
|
* undecided; any other check that did not pass exits 1.
|
|
48232
50373
|
*/
|
|
48233
|
-
function writtenExpectationsOutcome(check, sentExpectations, ruleId) {
|
|
50374
|
+
function writtenExpectationsOutcome(check, sentExpectations, ruleId, saved = "", name = (index) => `saved sample ${index + 1}`) {
|
|
48234
50375
|
const test = `\`primitive email-rules test-email-rule --rule-id ${ruleId}\``;
|
|
48235
50376
|
if (!isRecord(check)) {
|
|
48236
50377
|
if (!sentExpectations) return {};
|
|
@@ -48238,52 +50379,115 @@ function writtenExpectationsOutcome(check, sentExpectations, ruleId) {
|
|
|
48238
50379
|
exitCode: 3,
|
|
48239
50380
|
error: {
|
|
48240
50381
|
code: "expectations_unchecked",
|
|
48241
|
-
message:
|
|
50382
|
+
message: `${saved}The expectations this write sent were not checked on it. Test them with ${test}.`
|
|
48242
50383
|
}
|
|
48243
50384
|
};
|
|
48244
50385
|
}
|
|
48245
|
-
const failed = Array.isArray(check.failed) ? check.failed : [];
|
|
48246
|
-
const
|
|
50386
|
+
const failed = [...Array.isArray(check.failed) ? check.failed : [], ...indexList(check.failed_samples).map(name)];
|
|
50387
|
+
const undecidedSamples = indexList(check.undecided_samples);
|
|
50388
|
+
const undecided = [...Array.isArray(check.undecided) ? check.undecided : [], ...undecidedSamples.map(name)];
|
|
48247
50389
|
if (failed.length > 0) return {
|
|
48248
50390
|
exitCode: 1,
|
|
48249
50391
|
error: {
|
|
48250
50392
|
code: "expectations_failed",
|
|
48251
|
-
message: `${plural$1(failed.length, "expectation", "expectations")} failed on this write: ${failed.join(", ")}.`
|
|
50393
|
+
message: `${saved}${plural$1(failed.length, "expectation", "expectations")} failed on this write: ${failed.join(", ")}.`
|
|
48252
50394
|
}
|
|
48253
50395
|
};
|
|
48254
50396
|
if (undecided.length > 0) return {
|
|
48255
50397
|
exitCode: 3,
|
|
48256
50398
|
error: {
|
|
48257
50399
|
code: "expectations_undecided",
|
|
48258
|
-
message: `${plural$1(undecided.length, "expectation is", "expectations are")} undecided on this write, which judges them without classifying mail or asking the rule's questions: ${undecided.join(", ")}. Test them with ${test}.`
|
|
50400
|
+
message: `${saved}${plural$1(undecided.length, "expectation is", "expectations are")} undecided on this write, which judges them without classifying mail or asking the rule's questions${undecidedSamples.length > 0 ? ", and leaves made-up emails unjudged" : ""}: ${undecided.join(", ")}. Test them with ${test}.`
|
|
48259
50401
|
}
|
|
48260
50402
|
};
|
|
48261
50403
|
if (check.passed !== true) return {
|
|
48262
50404
|
exitCode: 1,
|
|
48263
50405
|
error: {
|
|
48264
50406
|
code: "expectations_failed",
|
|
48265
|
-
message:
|
|
50407
|
+
message: `${saved}The write's expectations check did not pass.`
|
|
48266
50408
|
}
|
|
48267
50409
|
};
|
|
48268
50410
|
return {};
|
|
48269
50411
|
}
|
|
50412
|
+
/**
|
|
50413
|
+
* update-email-rule's post-save outcome says the write was saved: the server
|
|
50414
|
+
* judges a write before saving only when it saves a version or turns the
|
|
50415
|
+
* rule active, and refuses with these same codes, saving nothing.
|
|
50416
|
+
*/
|
|
50417
|
+
const SAVED_UNJUDGED = "The write was saved: the server judges a write before saving only when it saves a version or turns the rule active, so not this one. ";
|
|
50418
|
+
/** What a written do_not_share rule's report says first. */
|
|
50419
|
+
const BLOCKS_SHARING = "This rule blocks sharing (kind do_not_share): while it is active, no rule shares an email it matches.";
|
|
50420
|
+
/** A do_not_share rule's protection_note, as a line, or undefined. */
|
|
50421
|
+
function protectionLine(rule) {
|
|
50422
|
+
if (!isRecord(rule)) return void 0;
|
|
50423
|
+
const note = rule.protection_note;
|
|
50424
|
+
return typeof note === "string" && note.trim() ? `Protection: ${note.trim()}` : void 0;
|
|
50425
|
+
}
|
|
50426
|
+
/** What a restore_version write saved, as its answer says. */
|
|
50427
|
+
function restoredLine(restored, currentVersion) {
|
|
50428
|
+
if (!isRecord(restored) || typeof restored.version !== "number") return void 0;
|
|
50429
|
+
const note = typeof restored.note === "string" && restored.note.trim() ? ` ${restored.note.trim()}` : "";
|
|
50430
|
+
const as = typeof currentVersion === "number" ? `as version ${currentVersion}, now current` : "as the current version";
|
|
50431
|
+
return `Saved version ${restored.version} again ${as}.${note}`;
|
|
50432
|
+
}
|
|
50433
|
+
/**
|
|
50434
|
+
* Who an update that saved a version reaches, as its answer counts them: the
|
|
50435
|
+
* keys listing the rule, the webhooks those keys own, and the shares the
|
|
50436
|
+
* write ended.
|
|
50437
|
+
*/
|
|
50438
|
+
function affectsLine(affects) {
|
|
50439
|
+
if (!isRecord(affects)) return void 0;
|
|
50440
|
+
const count = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
50441
|
+
const keys = count(affects.keys);
|
|
50442
|
+
const endpoints = count(affects.endpoints);
|
|
50443
|
+
const shares = count(affects.shares_ended);
|
|
50444
|
+
if (keys === void 0 || endpoints === void 0 || shares === void 0) return void 0;
|
|
50445
|
+
return `This version reaches ${plural$1(keys, "API key", "API keys")} listing the rule and ${plural$1(endpoints, "webhook", "webhooks")} those keys own; it ended ${plural$1(shares, "share", "shares")}. The rule keeps its state.`;
|
|
50446
|
+
}
|
|
50447
|
+
/** The rule's author, by name and email where the answer gives them. */
|
|
50448
|
+
function authorLine(author) {
|
|
50449
|
+
if (!isRecord(author) || typeof author.user_id !== "string") return void 0;
|
|
50450
|
+
const name = typeof author.name === "string" && author.name.trim() ? author.name.trim() : null;
|
|
50451
|
+
const email = typeof author.email === "string" && author.email.trim() ? author.email.trim() : null;
|
|
50452
|
+
if (name && email) return `Author: ${name} <${email}>`;
|
|
50453
|
+
if (name || email) return `Author: ${name ?? email}`;
|
|
50454
|
+
return `Author: user ${author.user_id} (name and email not shown to you)`;
|
|
50455
|
+
}
|
|
48270
50456
|
/** The stderr report of a rule written by create-email-rule or update-email-rule. */
|
|
48271
50457
|
function writtenEmailRuleReport(data, request) {
|
|
48272
50458
|
if (!isRecord(data)) return { lines: [] };
|
|
48273
50459
|
const rule = data;
|
|
48274
50460
|
const ruleId = typeof rule.id === "string" ? rule.id : "<rule-id>";
|
|
48275
50461
|
const lines = scopeWarningLines(rule.scope_warnings);
|
|
48276
|
-
if (rule.kind === "do_not_share")
|
|
48277
|
-
|
|
48278
|
-
|
|
50462
|
+
if (rule.kind === "do_not_share") {
|
|
50463
|
+
const note = protectionLine(rule);
|
|
50464
|
+
lines.unshift(...note ? [BLOCKS_SHARING, note] : [`${BLOCKS_SHARING}${typeof rule.state === "string" && rule.state !== "active" ? " It blocks nothing until it is active." : ""}`]);
|
|
50465
|
+
}
|
|
50466
|
+
const restored = restoredLine(rule.restored_from, rule.current_version);
|
|
50467
|
+
if (restored) lines.push(restored);
|
|
50468
|
+
const reach = affectsLine(rule.affects);
|
|
50469
|
+
if (reach) lines.push(reach);
|
|
50470
|
+
const sentExpectations = sentExpectationsIn(request.body);
|
|
50471
|
+
const name = writtenSampleNamer(request.body, request.flags, rule.expectation_samples);
|
|
50472
|
+
const checked = expectationsCheckLine(rule.expectations_check, ruleId, name);
|
|
48279
50473
|
if (checked) lines.push(checked);
|
|
48280
50474
|
else if (sentExpectations) lines.push(`Expectations were not checked on this write. Test them with \`primitive email-rules test-email-rule --rule-id ${ruleId}\`.`);
|
|
48281
50475
|
if (request.flags?.["fail-on-expectations"] !== true) return { lines };
|
|
48282
50476
|
return {
|
|
48283
50477
|
lines,
|
|
48284
|
-
...writtenExpectationsOutcome(rule.expectations_check, sentExpectations, ruleId)
|
|
50478
|
+
...writtenExpectationsOutcome(rule.expectations_check, sentExpectations, ruleId, isUpdate(request) ? SAVED_UNJUDGED : "", name)
|
|
48285
50479
|
};
|
|
48286
50480
|
}
|
|
50481
|
+
/** Whether a rule write sent expectations: emails by id, or made-up emails to save or add. */
|
|
50482
|
+
function sentExpectationsIn(body) {
|
|
50483
|
+
if (!isRecord(body)) return false;
|
|
50484
|
+
const some = (value) => Array.isArray(value) && value.length > 0;
|
|
50485
|
+
return isRecord(body.expectations) || some(body.expectation_samples) || some(body.add_expectation_samples);
|
|
50486
|
+
}
|
|
50487
|
+
/** Whether a rule write is an update: it names the rule in its path. */
|
|
50488
|
+
function isUpdate(request) {
|
|
50489
|
+
return typeof request.path?.id === "string";
|
|
50490
|
+
}
|
|
48287
50491
|
/**
|
|
48288
50492
|
* A rule write's exit code when its report cannot be built, under
|
|
48289
50493
|
* --fail-on-expectations: 1 for a check that is there and did not pass, and 3
|
|
@@ -48291,7 +50495,7 @@ function writtenEmailRuleReport(data, request) {
|
|
|
48291
50495
|
*/
|
|
48292
50496
|
function writtenFallbackExitCode(data, request) {
|
|
48293
50497
|
if (request.flags["fail-on-expectations"] !== true) return void 0;
|
|
48294
|
-
return writtenExpectationsOutcome(isRecord(data) ? data.expectations_check : void 0,
|
|
50498
|
+
return writtenExpectationsOutcome(isRecord(data) ? data.expectations_check : void 0, sentExpectationsIn(request.body), "<rule-id>").exitCode;
|
|
48295
50499
|
}
|
|
48296
50500
|
/** The stderr report of get-email-rule-scope. */
|
|
48297
50501
|
function emailRuleScopeReport(data) {
|
|
@@ -48350,9 +50554,156 @@ function versionMovedHint(payload, ruleId) {
|
|
|
48350
50554
|
const read = `Read it again with \`primitive email-rules get-email-rule ${id}\` and reapply your change to what it shows`;
|
|
48351
50555
|
return current === void 0 ? `Hint: the rule's version is not the if_version you sent, so nothing was saved, and the answer did not give its current version. ${read}.` : `Hint: the rule's current version is ${current}, not the if_version you sent, so nothing was saved. ${read}, then retry with --if-version ${current}.`;
|
|
48352
50556
|
}
|
|
48353
|
-
/**
|
|
48354
|
-
|
|
50557
|
+
/** The read-again advice every conflict ends with. */
|
|
50558
|
+
function readAgain(id) {
|
|
50559
|
+
return `Read it again with \`primitive email-rules get-email-rule ${id}\` and send the write again`;
|
|
50560
|
+
}
|
|
50561
|
+
/**
|
|
50562
|
+
* The hint for update-email-rule's refusals: an email_rule_conflict by its
|
|
50563
|
+
* details.reason (version_moved, revision_moved, expectations_moved,
|
|
50564
|
+
* rule_changed), and the expectations guard's expectations_failed,
|
|
50565
|
+
* expectations_undecided and no_expectations. Nothing was saved in any of
|
|
50566
|
+
* them.
|
|
50567
|
+
*/
|
|
50568
|
+
function updateEmailRuleErrorHint(code, payload, ruleId, body, flags) {
|
|
50569
|
+
const id = typeof ruleId === "string" && ruleId ? ruleId : "<rule-id>";
|
|
50570
|
+
const details = errorDetails(payload);
|
|
50571
|
+
const guarded = isRecord(body) && body.if_expectations_pass === true;
|
|
50572
|
+
if (code === "email_rule_conflict") switch (details.reason) {
|
|
50573
|
+
case "version_moved": return versionMovedHint(payload, ruleId);
|
|
50574
|
+
case "revision_moved": {
|
|
50575
|
+
const current = typeof details.current_revision === "number" ? details.current_revision : void 0;
|
|
50576
|
+
return current === void 0 ? `Hint: the rule was updated since the if_revision you sent, so nothing was saved. ${readAgain(id)} with --if-revision set to its revision.` : `Hint: the rule is now at revision ${current}, not the if_revision you sent, so nothing was saved. ${readAgain(id)}, reapplying your change to what it shows, with --if-revision ${current}.`;
|
|
50577
|
+
}
|
|
50578
|
+
case "expectations_moved": return `Hint: the rule's saved expectations changed while this write was checked against them, so nothing was saved. ${readAgain(id)}.`;
|
|
50579
|
+
case "rule_changed": return guarded ? `Hint: the rule changed after this write was judged against its expectations and before it was saved, so saving it would have gone unchecked; nothing was saved. ${readAgain(id)}.` : `Hint: the rule changed while this write was being saved, so nothing was saved. ${readAgain(id)}.`;
|
|
50580
|
+
default: return;
|
|
50581
|
+
}
|
|
50582
|
+
if (code === "expectations_failed" || code === "expectations_undecided") {
|
|
50583
|
+
const check = isRecord(details.expectations_check) ? details.expectations_check : {};
|
|
50584
|
+
const failed = Array.isArray(check.failed) ? check.failed : [];
|
|
50585
|
+
const undecided = Array.isArray(check.undecided) ? check.undecided : [];
|
|
50586
|
+
const name = writtenSampleNamer(body, flags);
|
|
50587
|
+
const failedAll = [...failed, ...indexList(check.failed_samples).map(name)];
|
|
50588
|
+
const undecidedSamples = indexList(check.undecided_samples);
|
|
50589
|
+
const undecidedAll = [...undecided, ...undecidedSamples.map(name)];
|
|
50590
|
+
const parts = [...failedAll.length > 0 ? [`failed: ${failedAll.join(", ")}`] : [], ...undecidedAll.length > 0 ? [`undecided: ${undecidedAll.join(", ")}`] : []];
|
|
50591
|
+
const addedOrder = isRecord(body) && Array.isArray(body.add_expectation_samples) && indexList(check.failed_samples).length + undecidedSamples.length > 0 ? " Made-up emails are numbered from 1: the rule's saved ones first, then those this write adds." : "";
|
|
50592
|
+
const which = parts.length > 0 ? ` Expectations ${parts.join("; ")}.${addedOrder}` : "";
|
|
50593
|
+
const reason = String(details.reason);
|
|
50594
|
+
const samplesMayStay = undecidedSamplesMayStay(undecidedSamples.length, reason);
|
|
50595
|
+
const byReason = reason !== "none" && Object.hasOwn(UNDECIDED_NEXT_STEP, reason) ? UNDECIDED_NEXT_STEP[reason] : UNDECIDED_NEXT_STEP.none;
|
|
50596
|
+
return `Hint: the rule, as this write would leave it, was judged against its expectations before saving (if_expectations_pass), and nothing was saved.${which}${code === "expectations_failed" ? "" : samplesMayStay ? `${undecided.length > 0 ? ` ${byReason}` : ""} ${UNDECIDED_SAMPLES_NEXT_STEP} ${refusedSampleTest(body, id)}` : ` ${byReason}`} The command exits ${updateEmailRuleErrorExitCode(payload) ?? 1}.`;
|
|
50597
|
+
}
|
|
50598
|
+
if (code === "no_expectations") return "Hint: nothing was saved. Give the rule expectations with --expectations-file or made-up emails with --expectation-samples-file or --add-expectation-samples-file (in this write or before it), or send the write without if_expectations_pass (--fail-on-expectations or --if-expectations-pass).";
|
|
50599
|
+
}
|
|
50600
|
+
/** The reasons a guard's undecided judgment can be decided by sending the write again. */
|
|
50601
|
+
const RETRYABLE_REASONS = /* @__PURE__ */ new Set([
|
|
50602
|
+
"deadline",
|
|
50603
|
+
"error",
|
|
50604
|
+
"client_gone"
|
|
50605
|
+
]);
|
|
50606
|
+
/**
|
|
50607
|
+
* A made-up email can stay undecided: one that reads a spam score it does
|
|
50608
|
+
* not carry, or whose classification failed or left out a fact the rule
|
|
50609
|
+
* needs, decides the same way every time.
|
|
50610
|
+
*/
|
|
50611
|
+
const UNDECIDED_SAMPLES_NEXT_STEP = "A made-up email undecided for no reason given may stay so (a spam score it does not carry, a fact its classification left out, or a failed classification), so sending the write again may not decide it.";
|
|
50612
|
+
/**
|
|
50613
|
+
* How to see why a refused write's made-up emails are undecided: only
|
|
50614
|
+
* test-email-rule-on-sample answers with what a made-up email lacked. Test
|
|
50615
|
+
* the tree this write sent (or the version it restores), else the rule, on
|
|
50616
|
+
* each. Nothing was saved, so a test by rule id would see neither.
|
|
50617
|
+
*/
|
|
50618
|
+
function refusedSampleTest(body, ruleId) {
|
|
50619
|
+
const onSample = "`primitive email-rules test-email-rule-on-sample`";
|
|
50620
|
+
if (isRecord(body) && isRecord(body.tree)) return `To see why, test the tree this write sent on each with ${onSample}.`;
|
|
50621
|
+
if (isRecord(body) && typeof body.restore_version === "number") return `To see why, test version ${body.restore_version}'s tree (\`primitive email-rules list-email-rule-versions ${ruleId} --versions ${body.restore_version}\`) on each with ${onSample}.`;
|
|
50622
|
+
return `To see why, test rule ${ruleId} on each with ${onSample}${isRecord(body) && Array.isArray(body.expectation_samples) ? "" : ` (\`primitive email-rules get-email-rule ${ruleId}\` lists its saved ones)`}.`;
|
|
50623
|
+
}
|
|
50624
|
+
/** What to do about an undecided judgment, by details.reason ("none": no reason given). */
|
|
50625
|
+
const UNDECIDED_NEXT_STEP = {
|
|
50626
|
+
none: "Some could not be decided yet (mail not classified yet, or a question not answered yet); send the write again later.",
|
|
50627
|
+
deadline: "The judgment ran out of time; send the write again.",
|
|
50628
|
+
error: "The judgment could not finish; send the write again.",
|
|
50629
|
+
client_gone: "The request ended before the write was saved; send it again.",
|
|
50630
|
+
author_unresolvable: "The rule cannot run as its author over the mail it would cover (details.author_reason), so sending the write again will not decide them."
|
|
50631
|
+
};
|
|
50632
|
+
/**
|
|
50633
|
+
* update-email-rule's exit code for the expectations guard's refusals: 1 when
|
|
50634
|
+
* one failed, or when they may not be decided by trying again (the rule
|
|
50635
|
+
* cannot run as its author, or a made-up email is undecided for no reason
|
|
50636
|
+
* the answer gives); 3 when trying again later can decide them.
|
|
50637
|
+
*/
|
|
50638
|
+
function updateEmailRuleErrorExitCode(payload) {
|
|
50639
|
+
const code = isRecord(payload) ? payload.code : void 0;
|
|
50640
|
+
if (code === "expectations_failed") return 1;
|
|
50641
|
+
if (code !== "expectations_undecided") return void 0;
|
|
50642
|
+
const details = errorDetails(payload);
|
|
50643
|
+
if (details.reason === "author_unresolvable") return 1;
|
|
50644
|
+
return undecidedSamplesMayStay(indexList((isRecord(details.expectations_check) ? details.expectations_check : {}).undecided_samples).length, String(details.reason)) ? 1 : 3;
|
|
50645
|
+
}
|
|
50646
|
+
/**
|
|
50647
|
+
* Whether a guard's undecided made-up emails may stay undecided: some are,
|
|
50648
|
+
* and the answer gives no reason that sending the write again can change
|
|
50649
|
+
* (author_unresolvable has its own advice and exit code).
|
|
50650
|
+
*/
|
|
50651
|
+
function undecidedSamplesMayStay(count, reason) {
|
|
50652
|
+
return count > 0 && !RETRYABLE_REASONS.has(reason) && reason !== "author_unresolvable";
|
|
50653
|
+
}
|
|
50654
|
+
/** The stderr report of compile-email-rule: how many recent emails it holds for. */
|
|
50655
|
+
function compileEmailRuleReport(data) {
|
|
50656
|
+
if (!isRecord(data)) return { lines: [] };
|
|
50657
|
+
const lines = (Array.isArray(data.warnings) ? data.warnings : []).filter((warning) => typeof warning === "string" && warning.trim() !== "").map((warning) => `Warning: ${warning.trim()}`);
|
|
50658
|
+
const breadth = data.breadth;
|
|
50659
|
+
if (isRecord(breadth) && typeof breadth.checked === "number" && typeof breadth.matched === "number") lines.push(`Breadth: its conditions hold for ${breadth.matched} of ${plural$1(breadth.checked, "recent email", "recent emails")} checked in your own mailboxes, whatever scope you save it with.`);
|
|
50660
|
+
return { lines };
|
|
50661
|
+
}
|
|
50662
|
+
/** The stderr report of get-email-rule: who wrote it, and its revision. */
|
|
50663
|
+
function getEmailRuleReport(data) {
|
|
50664
|
+
if (!isRecord(data)) return { lines: [] };
|
|
50665
|
+
const lines = [];
|
|
50666
|
+
const author = authorLine(data.author);
|
|
50667
|
+
if (author) lines.push(author);
|
|
50668
|
+
const protection = protectionLine(data);
|
|
50669
|
+
if (protection) lines.push(protection);
|
|
50670
|
+
if (typeof data.revision === "number") lines.push(`Revision ${data.revision}: pass --if-revision ${data.revision} to update-email-rule so a change made since is never replaced unseen.`);
|
|
50671
|
+
return { lines };
|
|
50672
|
+
}
|
|
50673
|
+
/** The stderr report of list-email-rules: the protective rules that protect nothing yet, or less than they seem. */
|
|
50674
|
+
function listEmailRulesReport(data) {
|
|
50675
|
+
const rules = Array.isArray(data) ? data : [];
|
|
50676
|
+
const lines = [];
|
|
50677
|
+
for (const rule of rules) {
|
|
50678
|
+
if (!isRecord(rule)) continue;
|
|
50679
|
+
const note = typeof rule.protection_note === "string" ? rule.protection_note.trim() : "";
|
|
50680
|
+
if (!note) continue;
|
|
50681
|
+
const id = typeof rule.id === "string" ? rule.id : "<rule-id>";
|
|
50682
|
+
const name = typeof rule.name === "string" ? rule.name : "no name";
|
|
50683
|
+
lines.push(`Protection of ${id} (${name}): ${note}`);
|
|
50684
|
+
}
|
|
50685
|
+
return { lines };
|
|
50686
|
+
}
|
|
50687
|
+
/** create-email-rule's --fail-on-expectations: the exit code follows the check made after saving. */
|
|
50688
|
+
const FAIL_ON_EXPECTATIONS_HELP = "Exit 1 when an expectation in the answer's expectations_check failed, and 3 when none failed but one is undecided or the expectations this write sent were not checked (test the rule to decide). A create does not judge made-up emails, so with some it exits 3. Without it the command exits 0 whatever the check says.";
|
|
50689
|
+
/** update-email-rule's --fail-on-expectations: the server refuses the write. */
|
|
50690
|
+
const UPDATE_FAIL_ON_EXPECTATIONS_HELP = "Refuse the write unless the rule, as this write would leave it, passes its expectations. It sends if_expectations_pass (over any in --body-file or --raw-body), so the server judges a new version, a restored one, a new scope or turning the rule active before saving, on its emails by id and its made-up emails, and saves nothing when one failed (exit 1), one is undecided (exit 3, or 1 when the rule cannot run as its author or a made-up email is undecided for no reason the answer gives) or the rule has none (exit 1). It can take as long as a test of the rule; the command waits for it. Judging needs a credential that reads the mail's content. A write the server does not judge (one changing only expectations, say) is saved, and the exit code then follows the answer's expectations_check, as on create. With --json, a refused write's document holds the API's error alone; a saved write's holds the rule.";
|
|
50691
|
+
/** What a samples file flag reads. */
|
|
50692
|
+
const SAMPLES_FILE_SHAPE = "A JSON file of made-up emails the rule must match or not: an array such as [{\"expected\": \"no_match\", \"label\": \"a personal bill\", \"email\": {...}}], with email as test-email-rule-on-sample takes it (text up to 4,000 characters, at most 10 each of to, cc and attachment_types), like the one generate-look-alikes --samples-file writes, or a generate-look-alikes answer saved as it printed it.";
|
|
48355
50693
|
const EXPECTATION_FLAG_NOTE = "Replaces any expect in --body-file or --raw-body. The answer reports them in expectations (passed, counts, results) and the exit code follows it; see --help-full.";
|
|
50694
|
+
/**
|
|
50695
|
+
* update-email-rule's --fail-on-expectations sends if_expectations_pass, over
|
|
50696
|
+
* any in the body. Asking for it while turning it off is refused.
|
|
50697
|
+
*/
|
|
50698
|
+
function expectationsGuardBodyFields(flags) {
|
|
50699
|
+
if (flags["fail-on-expectations"] !== true) return [];
|
|
50700
|
+
if (flags["if-expectations-pass"] === false) throw usageError("--fail-on-expectations asks the server to judge the write before saving it; it cannot be combined with --no-if-expectations-pass.");
|
|
50701
|
+
return [{
|
|
50702
|
+
flag: `--${FAIL_ON_EXPECTATIONS_FLAG}`,
|
|
50703
|
+
property: "if_expectations_pass",
|
|
50704
|
+
value: true
|
|
50705
|
+
}];
|
|
50706
|
+
}
|
|
48356
50707
|
const EMAIL_RULE_OPERATION_EXTENSIONS = {
|
|
48357
50708
|
testEmailRule: {
|
|
48358
50709
|
flags: {
|
|
@@ -48367,9 +50718,11 @@ const EMAIL_RULE_OPERATION_EXTENSIONS = {
|
|
|
48367
50718
|
exclusive: ["expect-file"]
|
|
48368
50719
|
}),
|
|
48369
50720
|
"expect-file": Flags.string({
|
|
48370
|
-
description: "A JSON file holding the request's expect, such as {\"match\": [\"<email-id>\"], \"no_match\": [\"<email-id>\"]}. Not with --expect-match or --expect-no-match.",
|
|
50721
|
+
description: "A JSON file holding the emails the request's expect names by id, such as {\"match\": [\"<email-id>\"], \"no_match\": [\"<email-id>\"]}; made-up emails go in --expect-samples-file. Not with --expect-match or --expect-no-match.",
|
|
48371
50722
|
exclusive: ["expect-match", "expect-no-match"]
|
|
48372
|
-
})
|
|
50723
|
+
}),
|
|
50724
|
+
[EXPECT_SAMPLES_FILE_FLAG]: Flags.string({ description: `${SAMPLES_FILE_SHAPE} Sets expect.samples (at most 20), beside any emails by id; the answer judges each in expectations.samples (index from 0 over those sent), and the report names each by its number in the file. Like any expect, it replaces a saved rule's own expectations and samples for this test. ${EXPECTATION_FLAG_NOTE}` }),
|
|
50725
|
+
[PICK_FLAG]: Flags.string({ description: pickFlagHelp([EXPECT_SAMPLES_FILE_FLAG]) })
|
|
48373
50726
|
},
|
|
48374
50727
|
bodyFields: expectBodyFields,
|
|
48375
50728
|
bodyProperties: ["expect"],
|
|
@@ -48382,23 +50735,67 @@ const EMAIL_RULE_OPERATION_EXTENSIONS = {
|
|
|
48382
50735
|
createEmailRule: {
|
|
48383
50736
|
flags: {
|
|
48384
50737
|
"expectations-file": Flags.string({ description: "A JSON file holding the emails the rule must match and must not, such as {\"match\": [\"<email-id>\"], \"no_match\": [\"<email-id>\"]}. Sets expectations, over any in --body-file or --raw-body; the answer judges them in expectations_check (passed, decided, failed, undecided); the report test-email-rule answers with is named expectations instead." }),
|
|
50738
|
+
[SAMPLES_FILE_FLAG]: Flags.string({ description: `${SAMPLES_FILE_SHAPE} Sets expectation_samples (at most 20), over any in --body-file or --raw-body. A create does not judge them; test-email-rule --rule-id does.` }),
|
|
50739
|
+
[PICK_FLAG]: Flags.string({ description: pickFlagHelp([SAMPLES_FILE_FLAG]) }),
|
|
48385
50740
|
[FAIL_ON_EXPECTATIONS_FLAG]: Flags.boolean({ description: FAIL_ON_EXPECTATIONS_HELP })
|
|
48386
50741
|
},
|
|
48387
|
-
bodyFields: expectationsBodyFields(false),
|
|
48388
|
-
bodyProperties: ["expectations"],
|
|
50742
|
+
bodyFields: (flags) => [...expectationsBodyFields(false)(flags), ...samplesBodyFields(false)(flags)],
|
|
50743
|
+
bodyProperties: ["expectations", "expectation_samples"],
|
|
48389
50744
|
fallbackExitCode: writtenFallbackExitCode,
|
|
48390
50745
|
report: writtenEmailRuleReport
|
|
48391
50746
|
},
|
|
48392
50747
|
updateEmailRule: {
|
|
48393
50748
|
flags: {
|
|
48394
50749
|
"expectations-file": Flags.string({ description: "A JSON file holding the emails the rule must match and must not, such as {\"match\": [\"<email-id>\"], \"no_match\": [\"<email-id>\"]}, replacing its saved expectations; a file holding null removes them. Sets expectations, over any in --body-file or --raw-body; the answer judges them in expectations_check (passed, decided, failed, undecided); the report test-email-rule answers with is named expectations instead." }),
|
|
48395
|
-
[
|
|
50750
|
+
[SAMPLES_FILE_FLAG]: Flags.string({
|
|
50751
|
+
description: `${SAMPLES_FILE_SHAPE} They replace the rule's saved ones, and a file holding [] removes them. Sets expectation_samples, over any in --body-file or --raw-body. Not with --${ADD_SAMPLES_FILE_FLAG}. The write judges them only with --${FAIL_ON_EXPECTATIONS_FLAG}, and only when it saves a version or turns the rule active.`,
|
|
50752
|
+
exclusive: [ADD_SAMPLES_FILE_FLAG]
|
|
50753
|
+
}),
|
|
50754
|
+
[ADD_SAMPLES_FILE_FLAG]: Flags.string({
|
|
50755
|
+
description: `${SAMPLES_FILE_SHAPE} They are added to the rule's saved ones, 20 at most in all. Sets add_expectation_samples, over any in --body-file or --raw-body. Not with --${SAMPLES_FILE_FLAG}. The write judges them only with --${FAIL_ON_EXPECTATIONS_FLAG}, and only when it saves a version or turns the rule active.`,
|
|
50756
|
+
exclusive: [SAMPLES_FILE_FLAG]
|
|
50757
|
+
}),
|
|
50758
|
+
[PICK_FLAG]: Flags.string({ description: pickFlagHelp([SAMPLES_FILE_FLAG, ADD_SAMPLES_FILE_FLAG]) }),
|
|
50759
|
+
[FAIL_ON_EXPECTATIONS_FLAG]: Flags.boolean({ description: UPDATE_FAIL_ON_EXPECTATIONS_HELP })
|
|
48396
50760
|
},
|
|
48397
|
-
bodyFields:
|
|
48398
|
-
|
|
50761
|
+
bodyFields: (flags) => [
|
|
50762
|
+
...expectationsBodyFields(true)(flags),
|
|
50763
|
+
...samplesBodyFields(true)(flags),
|
|
50764
|
+
...expectationsGuardBodyFields(flags)
|
|
50765
|
+
],
|
|
50766
|
+
bodyProperties: [
|
|
50767
|
+
"expectations",
|
|
50768
|
+
"expectation_samples",
|
|
50769
|
+
"add_expectation_samples"
|
|
50770
|
+
],
|
|
50771
|
+
errorExitCode: updateEmailRuleErrorExitCode,
|
|
48399
50772
|
fallbackExitCode: writtenFallbackExitCode,
|
|
48400
50773
|
report: writtenEmailRuleReport
|
|
48401
50774
|
},
|
|
50775
|
+
generateLookAlikes: {
|
|
50776
|
+
flags: { [LOOK_ALIKES_SAMPLES_FILE_FLAG]: Flags.string({ description: `Also write the look-alikes to this JSON file as made-up emails the rule must not match, a JSON array numbered from 1 in the order the report prints them. Save the ones you choose with create-email-rule --${SAMPLES_FILE_FLAG}, update-email-rule --${ADD_SAMPLES_FILE_FLAG} or, for a request, accept-email-rule-request --${SAMPLES_FILE_FLAG}, with --pick; test an edited rule on them with test-email-rule --${EXPECT_SAMPLES_FILE_FLAG}. Replaces the file if it exists, once the call succeeds; when no look-alikes came back it leaves the file as it was and exits 1.` }) },
|
|
50777
|
+
report: lookAlikesReport
|
|
50778
|
+
},
|
|
50779
|
+
acceptEmailRuleRequest: {
|
|
50780
|
+
flags: {
|
|
50781
|
+
[SAMPLES_FILE_FLAG]: Flags.string({ description: `${SAMPLES_FILE_SHAPE} Sets expectation_samples (at most 20), over any in --body-file or --raw-body: they are kept on the rule this accept writes, such as the look-alikes of the request it must never match.` }),
|
|
50782
|
+
[PICK_FLAG]: Flags.string({ description: pickFlagHelp([SAMPLES_FILE_FLAG]) })
|
|
50783
|
+
},
|
|
50784
|
+
bodyFields: samplesBodyFields(false),
|
|
50785
|
+
bodyProperties: ["expectation_samples"]
|
|
50786
|
+
},
|
|
50787
|
+
compileEmailRule: {
|
|
50788
|
+
flags: {},
|
|
50789
|
+
report: compileEmailRuleReport
|
|
50790
|
+
},
|
|
50791
|
+
getEmailRule: {
|
|
50792
|
+
flags: {},
|
|
50793
|
+
report: getEmailRuleReport
|
|
50794
|
+
},
|
|
50795
|
+
listEmailRules: {
|
|
50796
|
+
flags: {},
|
|
50797
|
+
report: listEmailRulesReport
|
|
50798
|
+
},
|
|
48402
50799
|
getEmailRuleScope: {
|
|
48403
50800
|
flags: {},
|
|
48404
50801
|
report: emailRuleScopeReport
|
|
@@ -49625,7 +52022,6 @@ function serializeErrorPayload(payload) {
|
|
|
49625
52022
|
* outcome is unknown.
|
|
49626
52023
|
*/
|
|
49627
52024
|
function buildThrownSendFailureEnvelope(params) {
|
|
49628
|
-
const message = params.error instanceof Error ? params.error.message : String(params.error);
|
|
49629
52025
|
const outcome = params.requestStarted ? "uncertain" : "not_sent";
|
|
49630
52026
|
return {
|
|
49631
52027
|
outcome,
|
|
@@ -49637,7 +52033,7 @@ function buildThrownSendFailureEnvelope(params) {
|
|
|
49637
52033
|
}),
|
|
49638
52034
|
sent: null,
|
|
49639
52035
|
http_status: null,
|
|
49640
|
-
error:
|
|
52036
|
+
error: thrownErrorBody(params.error),
|
|
49641
52037
|
follow_up_commands: params.requestStarted && params.attemptStartedAtIso ? buildSendCommandFollowUps({
|
|
49642
52038
|
attemptStartedAtIso: params.attemptStartedAtIso,
|
|
49643
52039
|
idempotencyKey: params.idempotencyKey,
|
|
@@ -50530,7 +52926,7 @@ function savedConnectedProfiles(configDir, exclude) {
|
|
|
50530
52926
|
*/
|
|
50531
52927
|
async function otherProfiles(configDir, session, current) {
|
|
50532
52928
|
if (session) try {
|
|
50533
|
-
const { connectedProfilesForSession } = await import("./machine-session-
|
|
52929
|
+
const { connectedProfilesForSession } = await import("./machine-session-DNnNhhFr.js").then((n) => n.s);
|
|
50534
52930
|
return {
|
|
50535
52931
|
inSession: true,
|
|
50536
52932
|
profiles: connectedProfilesForSession(configDir, session, current)
|
|
@@ -51637,7 +54033,7 @@ const OPERATION_ERROR_HINTS = {
|
|
|
51637
54033
|
inviteOrganizationMember: (code, body, payload) => inviteRefusalHint(code, body, payload),
|
|
51638
54034
|
createEndpoint: (code, body, payload) => createEndpointRuleRequestHint(code, body, payload),
|
|
51639
54035
|
updateEndpoint: (code, body, payload) => updateEndpointRuleRequestHint(code, body, payload),
|
|
51640
|
-
updateEmailRule: (code,
|
|
54036
|
+
updateEmailRule: (code, body, payload, path, flags) => updateEmailRuleErrorHint(code, payload, path?.id, body, flags),
|
|
51641
54037
|
verifyDomain: (code, _body, payload) => code === "dns_verification_failed" ? domainVerificationFailedHint(payload) : void 0,
|
|
51642
54038
|
addDomain: (code, body) => {
|
|
51643
54039
|
if (code === "mail_relay_unavailable") return MAIL_RELAY_UNAVAILABLE_MESSAGE;
|
|
@@ -51840,7 +54236,7 @@ function createOperationCommand(operation) {
|
|
|
51840
54236
|
}) : await callOperation();
|
|
51841
54237
|
if (result.error) {
|
|
51842
54238
|
const errorPayload = extractErrorPayload(result.error);
|
|
51843
|
-
const operationErrorHint = OPERATION_ERROR_HINTS[operation.sdkName]?.(extractErrorCode(errorPayload), body, errorPayload, collectValues(operation.pathParams, parsedFlags, operation.sdkName));
|
|
54239
|
+
const operationErrorHint = OPERATION_ERROR_HINTS[operation.sdkName]?.(extractErrorCode(errorPayload), body, errorPayload, collectValues(operation.pathParams, parsedFlags, operation.sdkName), parsedFlags);
|
|
51844
54240
|
writeErrorWithHints(errorPayload, result.response, { codeHint: !(operationErrorHint && OPERATION_ERROR_HINTS_REPLACING_CODE_HINT.has(operation.sdkName)) });
|
|
51845
54241
|
if (operationErrorHint) process.stderr.write(`${operationErrorHint}\n`);
|
|
51846
54242
|
if (stepUpUrl) process.stderr.write(approveThenRerunHint(stepUpUrl));
|
|
@@ -51867,7 +54263,7 @@ function createOperationCommand(operation) {
|
|
|
51867
54263
|
}
|
|
51868
54264
|
});
|
|
51869
54265
|
if (operation.sdkName === "getEmail" && extractErrorCode(errorPayload) === "not_found" && typeof parsedFlags.id === "string") await explainPendingMailNotFound(this.config.configDir, parsedFlags.id);
|
|
51870
|
-
process.exitCode = 1;
|
|
54266
|
+
process.exitCode = extension?.errorExitCode?.(errorPayload) || 1;
|
|
51871
54267
|
return;
|
|
51872
54268
|
}
|
|
51873
54269
|
if (operation.binaryResponse) {
|
|
@@ -52005,4 +54401,4 @@ const EMPTY_RESULT_HINTS = {
|
|
|
52005
54401
|
function canonicalizeCliReferences(description) {
|
|
52006
54402
|
return description.replaceAll("`primitive emails:latest`", "`primitive emails latest`").replaceAll("`primitive describe emails:get-email | jq '.responseSchema.properties'`", "`primitive describe emails:get | jq '.responseSchema.properties'`");
|
|
52007
54403
|
}
|
|
52008
|
-
export {
|
|
54404
|
+
export { readEmailInteraction as $, SAVED_CLI_OAUTH_SESSION_EXPIRED_MESSAGE as $t, explainPendingMailNotFound as A, cliInvocation as At, skillFirstLine as B, formatAlreadySentNotice as Bt, promptLine as C, sendOutcomeExitCode as Ct, formatBrowserLoginInstructions as D, successfulSendOutcome as Dt, canOfferCopyHotkey as E, serializeErrorPayload as Et, latestOwnOutbound as F, loadSkillLine as Ft, clearConsumedPendingMail as G, classificationFactLines as Gt, wakeRecipientField as H, emptyEmailListHint as Ht, readThreadContext as I, currentMailSessionKey as It, readPendingMail as J, noFactsMessage as Jt, clearReadPendingMail as K, classificationOf as Kt, sentInThread as L, detectAgentRuntime as Lt, describeWake as M, defaultConnectionName as Mt, displayAddress as N, generatedAgentName as Nt, listenForCopyHotkey as O, thrownErrorExitCode as Ot, formatMailWakeLine as P, identitySuggestions as Pt, interactionNextActions as Q, ConnectedAgentKeyRequiredError as Qt, serverMuted as R, detectRuntimeSession as Rt, approveThenRerunHint as S, sendIdentityFields as St, withApproval as T, sentHistoryWindowStart as Tt, wakeRelationship as U, mailReach as Ut, wakeReadCommand as V, withFlagSuggestion as Vt, claimPendingMail as W, readCallerMailFor as Wt, removePendingMail as X, ATTACHMENT_WAIT_MAX_MS as Xt, recordPendingMail as Y, ATTACHMENT_WAIT_FLAG_DESCRIPTION as Yt, interactionHumanLine as Z, withAttachmentWait as Zt, takesStepUp as _, setKnownCommands as _n, formatPriorRepliesWarning as _t, createOperationCommand as a, selectedOrgFromEnv as an, SEND_OUTCOME_HELP as at, STEP_UP_HEADER as b, priorRepliesThatWentOut as bt, formatErrorPayload as c, credentialsFromUserLogin as cn, buildFollowUpCommand as ct, isGrantRefusal as d, revokeUserLogin as dn, classifySendError as dt, createAuthenticatedCliApiClient as en, replyExpectation as et, parameterFlagName as f, roleKeyLabel as fn, deriveSendIdempotencyKey as ft, surfaceUnauthorizedHint as g, sanitizeForTerminal as gn, formatPriorRepliesCheckSkipped as gt, runWithTiming as h, routeWarningLines as hn, formatDeletedEarlierSendNotice as ht, TIME_FLAG_DESCRIPTION as i, resolveCliApiRequestConfig as in, MAX_SEND_WAIT_TIMEOUT_MS as it, bareAddress$1 as j, connectNextSteps as jt, openBrowser as k, DEFAULT_ENROLL_NAME as kt, hasControlCharacter as l, fetchCliOrgs as ln, buildThrownSendFailureEnvelope as lt, refusalDetails as m, switchCurrentOrg as mn, followUpCommandPrefix as mt, API_ERROR_CODES as n, refreshStoredCliCredentials as nn, wakeInteractionSentence as nt, extractErrorCode as o, CliOrgRefusedError as on, apiResultHttpStatus as ot, readTextFileFlag as p, roleKeyText as pn, excludeSignalReplies as pt, pendingMailPath as q, isEmailId as qt, OPERATION_PATH_FLAGS as r, refuseOrgSelectionForProfile as rn, IDEMPOTENCY_KEY_FLAG_DESCRIPTION as rt, extractErrorPayload as s, RESTRICTED_ROLE_NOTE as sn, assertValidIdempotencyKey as st, API_BASE_URL_FLAG_DESCRIPTION as t, createCliApiClient as tn, replyInteractionWarning as tt, headerFlagName as u, isBuiltInRoleKey as un, checkPriorReplies as ut, writeErrorWithHints as v, operationManifest as vn, formatSendFailureSummary as vt, terminalApprovalIo as w, sendWaitClampNotice as wt, STEP_UP_TOKEN_FLAG_DESCRIPTION as x, reportSendCommandResult as xt, BROWSER_FLAG_DESCRIPTION as y, formatSendRecordFailureSummary as yt, serverRelationship as z, runtimeSessionVariableList as zt };
|