@pome-sh/cli 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/src/cli/checks.d.ts +15 -0
  3. package/dist/src/cli/checks.js +41 -4
  4. package/node_modules/@pome-sh/twin-gmail/CHANGELOG.md +39 -0
  5. package/node_modules/@pome-sh/twin-gmail/dist/src/check-drafts.d.ts +7 -0
  6. package/node_modules/@pome-sh/twin-gmail/dist/src/check-drafts.js +138 -0
  7. package/node_modules/@pome-sh/twin-gmail/dist/src/check-drafts.js.map +1 -0
  8. package/node_modules/@pome-sh/twin-gmail/dist/src/check-kind.d.ts +3 -0
  9. package/node_modules/@pome-sh/twin-gmail/dist/src/check-kind.js +9 -0
  10. package/node_modules/@pome-sh/twin-gmail/dist/src/check-kind.js.map +1 -0
  11. package/node_modules/@pome-sh/twin-gmail/dist/src/check-labels.d.ts +4 -0
  12. package/node_modules/@pome-sh/twin-gmail/dist/src/check-labels.js +56 -0
  13. package/node_modules/@pome-sh/twin-gmail/dist/src/check-labels.js.map +1 -0
  14. package/node_modules/@pome-sh/twin-gmail/dist/src/check-messages.d.ts +14 -0
  15. package/node_modules/@pome-sh/twin-gmail/dist/src/check-messages.js +207 -0
  16. package/node_modules/@pome-sh/twin-gmail/dist/src/check-messages.js.map +1 -0
  17. package/node_modules/@pome-sh/twin-gmail/dist/src/check-params.d.ts +10 -0
  18. package/node_modules/@pome-sh/twin-gmail/dist/src/check-params.js +96 -0
  19. package/node_modules/@pome-sh/twin-gmail/dist/src/check-params.js.map +1 -0
  20. package/node_modules/@pome-sh/twin-gmail/dist/src/check-state.d.ts +113 -0
  21. package/node_modules/@pome-sh/twin-gmail/dist/src/check-state.js +145 -0
  22. package/node_modules/@pome-sh/twin-gmail/dist/src/check-state.js.map +1 -0
  23. package/node_modules/@pome-sh/twin-gmail/dist/src/check-tape.d.ts +2 -0
  24. package/node_modules/@pome-sh/twin-gmail/dist/src/check-tape.js +103 -0
  25. package/node_modules/@pome-sh/twin-gmail/dist/src/check-tape.js.map +1 -0
  26. package/node_modules/@pome-sh/twin-gmail/dist/src/check-worlds.d.ts +17 -0
  27. package/node_modules/@pome-sh/twin-gmail/dist/src/check-worlds.js +62 -0
  28. package/node_modules/@pome-sh/twin-gmail/dist/src/check-worlds.js.map +1 -0
  29. package/node_modules/@pome-sh/twin-gmail/dist/src/checks.d.ts +19 -0
  30. package/node_modules/@pome-sh/twin-gmail/dist/src/checks.js +42 -0
  31. package/node_modules/@pome-sh/twin-gmail/dist/src/checks.js.map +1 -0
  32. package/node_modules/@pome-sh/twin-gmail/dist/src/index.d.ts +2 -0
  33. package/node_modules/@pome-sh/twin-gmail/dist/src/index.js +4 -0
  34. package/node_modules/@pome-sh/twin-gmail/dist/src/index.js.map +1 -1
  35. package/node_modules/@pome-sh/twin-gmail/package.json +1 -1
  36. package/node_modules/@pome-sh/twin-linear/CHANGELOG.md +41 -0
  37. package/node_modules/@pome-sh/twin-linear/dist/src/check-comments.d.ts +10 -0
  38. package/node_modules/@pome-sh/twin-linear/dist/src/check-comments.js +117 -0
  39. package/node_modules/@pome-sh/twin-linear/dist/src/check-comments.js.map +1 -0
  40. package/node_modules/@pome-sh/twin-linear/dist/src/check-issues.d.ts +25 -0
  41. package/node_modules/@pome-sh/twin-linear/dist/src/check-issues.js +211 -0
  42. package/node_modules/@pome-sh/twin-linear/dist/src/check-issues.js.map +1 -0
  43. package/node_modules/@pome-sh/twin-linear/dist/src/check-kind.d.ts +3 -0
  44. package/node_modules/@pome-sh/twin-linear/dist/src/check-kind.js +9 -0
  45. package/node_modules/@pome-sh/twin-linear/dist/src/check-kind.js.map +1 -0
  46. package/node_modules/@pome-sh/twin-linear/dist/src/check-params.d.ts +8 -0
  47. package/node_modules/@pome-sh/twin-linear/dist/src/check-params.js +94 -0
  48. package/node_modules/@pome-sh/twin-linear/dist/src/check-params.js.map +1 -0
  49. package/node_modules/@pome-sh/twin-linear/dist/src/check-state.d.ts +98 -0
  50. package/node_modules/@pome-sh/twin-linear/dist/src/check-state.js +131 -0
  51. package/node_modules/@pome-sh/twin-linear/dist/src/check-state.js.map +1 -0
  52. package/node_modules/@pome-sh/twin-linear/dist/src/check-tape.d.ts +2 -0
  53. package/node_modules/@pome-sh/twin-linear/dist/src/check-tape.js +100 -0
  54. package/node_modules/@pome-sh/twin-linear/dist/src/check-tape.js.map +1 -0
  55. package/node_modules/@pome-sh/twin-linear/dist/src/check-worlds.d.ts +44 -0
  56. package/node_modules/@pome-sh/twin-linear/dist/src/check-worlds.js +93 -0
  57. package/node_modules/@pome-sh/twin-linear/dist/src/check-worlds.js.map +1 -0
  58. package/node_modules/@pome-sh/twin-linear/dist/src/checks.d.ts +29 -0
  59. package/node_modules/@pome-sh/twin-linear/dist/src/checks.js +52 -0
  60. package/node_modules/@pome-sh/twin-linear/dist/src/checks.js.map +1 -0
  61. package/node_modules/@pome-sh/twin-linear/dist/src/index.d.ts +2 -0
  62. package/node_modules/@pome-sh/twin-linear/dist/src/index.js +7 -0
  63. package/node_modules/@pome-sh/twin-linear/dist/src/index.js.map +1 -1
  64. package/node_modules/@pome-sh/twin-linear/package.json +1 -1
  65. package/node_modules/@pome-sh/twin-stripe/dist/src/check-kind.d.ts +3 -0
  66. package/node_modules/@pome-sh/twin-stripe/dist/src/check-kind.js +9 -0
  67. package/node_modules/@pome-sh/twin-stripe/dist/src/check-kind.js.map +1 -0
  68. package/node_modules/@pome-sh/twin-stripe/dist/src/check-params.d.ts +8 -0
  69. package/node_modules/@pome-sh/twin-stripe/dist/src/check-params.js +123 -0
  70. package/node_modules/@pome-sh/twin-stripe/dist/src/check-params.js.map +1 -0
  71. package/node_modules/@pome-sh/twin-stripe/dist/src/check-payments.d.ts +18 -0
  72. package/node_modules/@pome-sh/twin-stripe/dist/src/check-payments.js +264 -0
  73. package/node_modules/@pome-sh/twin-stripe/dist/src/check-payments.js.map +1 -0
  74. package/node_modules/@pome-sh/twin-stripe/dist/src/check-refunds.d.ts +8 -0
  75. package/node_modules/@pome-sh/twin-stripe/dist/src/check-refunds.js +122 -0
  76. package/node_modules/@pome-sh/twin-stripe/dist/src/check-refunds.js.map +1 -0
  77. package/node_modules/@pome-sh/twin-stripe/dist/src/check-state.d.ts +83 -0
  78. package/node_modules/@pome-sh/twin-stripe/dist/src/check-state.js +95 -0
  79. package/node_modules/@pome-sh/twin-stripe/dist/src/check-state.js.map +1 -0
  80. package/node_modules/@pome-sh/twin-stripe/dist/src/check-tape.d.ts +9 -0
  81. package/node_modules/@pome-sh/twin-stripe/dist/src/check-tape.js +338 -0
  82. package/node_modules/@pome-sh/twin-stripe/dist/src/check-tape.js.map +1 -0
  83. package/node_modules/@pome-sh/twin-stripe/dist/src/check-worlds.d.ts +24 -0
  84. package/node_modules/@pome-sh/twin-stripe/dist/src/check-worlds.js +131 -0
  85. package/node_modules/@pome-sh/twin-stripe/dist/src/check-worlds.js.map +1 -0
  86. package/node_modules/@pome-sh/twin-stripe/dist/src/checks.d.ts +22 -0
  87. package/node_modules/@pome-sh/twin-stripe/dist/src/checks.js +97 -0
  88. package/node_modules/@pome-sh/twin-stripe/dist/src/checks.js.map +1 -0
  89. package/node_modules/@pome-sh/twin-stripe/package.json +1 -1
  90. package/package.json +4 -4
  91. package/tasks/11-stripe-handle-failed-payment.md +2 -2
  92. package/tasks/12-stripe-reconcile-event.md +1 -1
  93. package/tasks/13-stripe-x402-payment-required.md +2 -2
  94. package/tasks/14-stripe-refund-retry.md +22 -9
  95. package/tasks/24-linear-issue-triage.md +2 -2
  96. package/tasks/25-linear-comment-label-triage.md +3 -3
  97. package/tasks/26-github-linear-handoff.md +3 -4
@@ -0,0 +1,10 @@
1
+ import type { CheckParamType } from "@pome-sh/sdk/checks";
2
+ export declare const messageId: CheckParamType;
3
+ export declare const labelRef: CheckParamType;
4
+ export declare const labelName: CheckParamType;
5
+ export declare const emailAddress: CheckParamType;
6
+ export declare const mailboxRef: CheckParamType;
7
+ export declare const exactCount: CheckParamType;
8
+ export declare const countWord: CheckParamType;
9
+ /** A `countWord` slot's value as a number, or null when it is neither. */
10
+ export declare function parseCount(raw: string): number | null;
@@ -0,0 +1,96 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // The typed slots Gmail's declared checks fill (F-1128).
4
+ //
5
+ // They live in the twin, not the sdk, for the same reason the declarations do:
6
+ // the twin owns what a Gmail message id or label name may look like. Every
7
+ // pattern is NARROW on purpose — a slot type is what turns "the author typed
8
+ // something wrong" into a corrupted instance reported by name, rather than a
9
+ // lookup that quietly finds nothing.
10
+ //
11
+ // All patterns are capture-group-free; `defineCheck` throws otherwise, because
12
+ // every consumer reads capture group i+1 as slot i and one smuggled group hands
13
+ // each predicate its neighbour's argument.
14
+ // Ids the twin mints (`nextId` → `msg_1`, `Label_3`, `draft_2`) and the ids a
15
+ // seed may pin (`msg_support`, `msg_parity`). No spaces and no dots, which is
16
+ // what separates this slot from `labelName` below.
17
+ export const messageId = {
18
+ name: "message",
19
+ pattern: "[A-Za-z0-9_-]{1,128}",
20
+ example: "msg_support",
21
+ render: (value) => value,
22
+ parse: (raw) => raw,
23
+ };
24
+ // A label REFERENCE: either the minted id (`Label_follow_up`) or a system label,
25
+ // whose id and display name are the same string (`INBOX`, `SENT`, `STARRED`).
26
+ // Deliberately excludes spaces — a label whose NAME has spaces is addressed by
27
+ // `labelName`, and keeping the two slots distinct is what stops
28
+ // `gmail.message-has-label` and `gmail.label-exists` claiming one sentence.
29
+ export const labelRef = {
30
+ name: "label",
31
+ pattern: "[A-Za-z0-9_-]{1,128}",
32
+ example: "STARRED",
33
+ render: (value) => value,
34
+ parse: (raw) => raw,
35
+ };
36
+ // A label's DISPLAY NAME, which Gmail permits spaces in (`Parity Complete`,
37
+ // `Follow Up`). Anchored to start on a word character so the template's
38
+ // surrounding literals cannot be eaten by a leading space.
39
+ export const labelName = {
40
+ name: "label",
41
+ pattern: "[A-Za-z0-9][A-Za-z0-9 _-]{0,127}",
42
+ example: "Parity Complete",
43
+ render: (value) => value,
44
+ parse: (raw) => raw,
45
+ };
46
+ // An addressee. Quote and backtick are excluded as well as whitespace so the
47
+ // slot cannot swallow a template's own delimiters.
48
+ export const emailAddress = {
49
+ name: "email",
50
+ pattern: "[^\\s@\"'`]+@[^\\s@\"'`]+",
51
+ example: "alice@example.com",
52
+ render: (value) => value,
53
+ parse: (raw) => raw,
54
+ };
55
+ // The same shape, named for the slot it fills — a mailbox is addressed by its
56
+ // email in every collection the export emits.
57
+ export const mailboxRef = {
58
+ name: "mailbox",
59
+ pattern: "[^\\s@\"'`]+@[^\\s@\"'`]+",
60
+ example: "pome-agent@pome-twin.test",
61
+ render: (value) => value,
62
+ parse: (raw) => raw,
63
+ };
64
+ // A bare digit count. Nine digits wide so `VACUITY_SENTINEL_NUMBER` re-binds:
65
+ // a mutant that stops matching evaluates to `unmatched`, which reads as "the
66
+ // verdict moved -> healthy" and blesses the very criterion the probe exists to
67
+ // catch.
68
+ export const exactCount = {
69
+ name: "count",
70
+ pattern: "\\d{1,9}",
71
+ example: "5",
72
+ render: (value) => value,
73
+ parse: (raw) => raw,
74
+ };
75
+ // Digits OR the English number words, because the corpus says both — `exactly 5
76
+ // messages` and `each of the five recipients`. Non-capturing, so the alternation
77
+ // costs no capture group.
78
+ export const countWord = {
79
+ name: "count",
80
+ pattern: "(?:\\d{1,9}|zero|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)",
81
+ example: "two",
82
+ render: (value) => value,
83
+ parse: (raw) => raw,
84
+ };
85
+ const NUMBER_WORDS = {
86
+ zero: 0, one: 1, two: 2, three: 3, four: 4, five: 5,
87
+ six: 6, seven: 7, eight: 8, nine: 9, ten: 10, eleven: 11, twelve: 12,
88
+ };
89
+ /** A `countWord` slot's value as a number, or null when it is neither. */
90
+ export function parseCount(raw) {
91
+ const trimmed = raw.trim().toLowerCase();
92
+ if (/^\d+$/.test(trimmed))
93
+ return Number(trimmed);
94
+ return NUMBER_WORDS[trimmed] ?? null;
95
+ }
96
+ //# sourceMappingURL=check-params.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-params.js","sourceRoot":"","sources":["../../src/check-params.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,113 @@
1
+ /** A resolver must be able to say WHY it found nothing: the ways of finding
2
+ * nothing get different verdicts. Same shape and same reason as twin-github's. */
3
+ export type Resolved<T> = {
4
+ found: T;
5
+ } | {
6
+ missing: string;
7
+ };
8
+ export interface GmailCheckStateMailbox {
9
+ email?: string;
10
+ displayName?: string;
11
+ }
12
+ export interface GmailCheckStateMessage {
13
+ mailboxEmail?: string;
14
+ id?: string;
15
+ threadId?: string;
16
+ from?: string;
17
+ to?: string[] | null;
18
+ cc?: string[] | null;
19
+ bcc?: string[] | null;
20
+ subject?: string;
21
+ bodyOmitted?: boolean;
22
+ }
23
+ export interface GmailCheckStateLabel {
24
+ mailboxEmail?: string;
25
+ id?: string;
26
+ name?: string;
27
+ type?: string;
28
+ }
29
+ export interface GmailCheckStateMessageLabel {
30
+ mailboxEmail?: string;
31
+ messageId?: string;
32
+ labelId?: string;
33
+ }
34
+ export interface GmailCheckStateDraft {
35
+ mailboxEmail?: string;
36
+ id?: string;
37
+ messageId?: string;
38
+ }
39
+ export interface GmailCheckStateBounds {
40
+ messageBodiesOmitted?: boolean;
41
+ largeMailbox?: boolean;
42
+ truncatedCollections?: string[] | null;
43
+ }
44
+ export interface GmailCheckState {
45
+ mailboxes?: GmailCheckStateMailbox[] | null;
46
+ messages?: GmailCheckStateMessage[] | null;
47
+ drafts?: GmailCheckStateDraft[] | null;
48
+ labels?: GmailCheckStateLabel[] | null;
49
+ messageLabels?: GmailCheckStateMessageLabel[] | null;
50
+ exportBounds?: GmailCheckStateBounds | null;
51
+ }
52
+ /**
53
+ * Was this collection capped on the way out?
54
+ *
55
+ * An export with no `exportBounds` block at all predates the cap and answers
56
+ * false — the same answer it would have given honestly. Treating absence as
57
+ * "truncated" would skip every criterion on an older snapshot.
58
+ */
59
+ export declare function isTruncated(state: GmailCheckState, collection: string): boolean;
60
+ /**
61
+ * The message a criterion names, with the outcomes a negative criterion must
62
+ * tell apart:
63
+ * - no `messages` key → `state_incomplete`; we cannot attest anything
64
+ * - present, id absent, collection CAPPED → `collection_truncated`; absent from
65
+ * a truncated list is not absent, and answering `not_found` here would
66
+ * false-fail a correct agent on a large mailbox
67
+ * - present, id absent, not capped → `message_not_found`
68
+ * - present in more than one mailbox → `message_ambiguous`
69
+ *
70
+ * The ambiguity arm is gmail's answer to github's `{repo}` slot. GitHub's legacy
71
+ * patterns took the repo as an optional qualifier and, absent it, scanned
72
+ * first-match-wins, so a two-repo world graded whichever sorted first. Gmail's
73
+ * corpus sentences carry no mailbox and ids are minted per mailbox, so rather
74
+ * than inventing a slot that would stop every existing criterion binding, this
75
+ * REFUSES on the world where the ambiguity is real.
76
+ */
77
+ export declare function resolveMessage(state: GmailCheckState, id: string): Resolved<GmailCheckStateMessage>;
78
+ /**
79
+ * Every label id that counts as `wanted`, lower-cased for comparison.
80
+ *
81
+ * Matches a label row by id OR by display name, because the corpus names both
82
+ * kinds: `Label_follow_up` is an id, `Follow Up` is the name of the same label.
83
+ * The bare `wanted` is always included so the join still works when the `labels`
84
+ * collection was capped away — a `messageLabels` row carries the bare id
85
+ * regardless.
86
+ */
87
+ export declare function labelIdsFor(state: GmailCheckState, wanted: string): Set<string>;
88
+ /**
89
+ * Does this message carry the named label?
90
+ *
91
+ * Reads the `messageLabels` join. A capped join is refused rather than answered:
92
+ * a missing row there is indistinguishable from a label that was never applied,
93
+ * and on a negative criterion that difference is a leaking agent's free point.
94
+ */
95
+ export declare function messageCarriesLabel(state: GmailCheckState, messageId: string, wanted: string): Resolved<boolean>;
96
+ /**
97
+ * The label a criterion names BY NAME.
98
+ *
99
+ * Deliberately does not fall back to the id. "A label named `Parity Complete`
100
+ * exists" asks about the display name; letting it also match an id would make
101
+ * the sentence mean two things, and `gmail.message-has-label` is the check that
102
+ * speaks ids.
103
+ */
104
+ export declare function resolveLabelByName(state: GmailCheckState, name: string): Resolved<GmailCheckStateLabel>;
105
+ /**
106
+ * Who a draft is addressed to — `to` + `cc`, from the backing MESSAGE.
107
+ *
108
+ * The exported draft row carries no addressing at all, so a predicate reading it
109
+ * alone would answer "no draft is addressed to anyone", always. A draft whose
110
+ * message did not survive the export contributes nothing rather than throwing;
111
+ * the caller decides whether that emptiness is answerable.
112
+ */
113
+ export declare function draftRecipients(state: GmailCheckState, draft: GmailCheckStateDraft): string[];
@@ -0,0 +1,145 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // How a declared check READS the exported Gmail mailbox (F-1128).
4
+ //
5
+ // pome-cloud kept a hand-maintained mirror of this shape in
6
+ // `deterministic/gmail.ts`. That mirror is deleted in the same milestone; this
7
+ // is the only model, and it lives here because the twin owns the shape.
8
+ //
9
+ // Written against the EXPORT (`state.ts exportGmailState()`), never the seed.
10
+ // The two diverge in ways that each produce a wrong verdict:
11
+ // - the seed says `labels: ["INBOX", "Build"]` on the message, mixing ids and
12
+ // display names; the export hoists them to a flat `messageLabels` JOIN keyed
13
+ // by `labelId`.
14
+ // - the seed puts `to` on the draft; the export's `drafts` row carries only
15
+ // `{mailboxEmail, id, messageId, createdAt, updatedAt}` and the addressing
16
+ // lives on the backing message.
17
+ //
18
+ // Every field is optional and every list nullable, because `exportGmailState`
19
+ // spreads raw SQLite rows: an older snapshot, a partial upload, or a schema that
20
+ // gained a column all arrive here as absent fields. A predicate that assumes
21
+ // presence throws inside the evaluator; one that reads this model returns a
22
+ // NAMED verdict instead. That is the D4 bargain — a criterion may leave the
23
+ // denominator, but it may never fabricate one.
24
+ //
25
+ // Four shapes are counter-intuitive and each has a wrong-verdict story:
26
+ // - MESSAGE BODIES ARE NEVER EXPORTED. `boundMessageExport` strips `text`,
27
+ // `html`, `snippet` and `headers` unconditionally and leaves digests plus
28
+ // `bodyOmitted: true`. A slack-style substring scan over message prose has
29
+ // no substrate here at all — `subject` is the only prose that survives.
30
+ // - COLLECTIONS TRUNCATE at `STATE_EXPORT_COLLECTION_CAP`, and the capped
31
+ // names land in `exportBounds.truncatedCollections`. Absent-from-a-truncated
32
+ // -collection is not absent, so it must skip rather than answer.
33
+ // - LABELS ARE A JOIN. A user label's `id` and `name` differ
34
+ // (`Label_follow_up` / `Follow Up`); a system label carries `id === name`.
35
+ // - EVERYTHING IS MAILBOX-SCOPED via `mailboxEmail`, and ids are minted per
36
+ // mailbox, so `deliveryMode: "seeded-mailboxes"` can put one id in two.
37
+ const lower = (value) => (value ?? "").toLowerCase();
38
+ /**
39
+ * Was this collection capped on the way out?
40
+ *
41
+ * An export with no `exportBounds` block at all predates the cap and answers
42
+ * false — the same answer it would have given honestly. Treating absence as
43
+ * "truncated" would skip every criterion on an older snapshot.
44
+ */
45
+ export function isTruncated(state, collection) {
46
+ return (state.exportBounds?.truncatedCollections ?? []).includes(collection);
47
+ }
48
+ /**
49
+ * The message a criterion names, with the outcomes a negative criterion must
50
+ * tell apart:
51
+ * - no `messages` key → `state_incomplete`; we cannot attest anything
52
+ * - present, id absent, collection CAPPED → `collection_truncated`; absent from
53
+ * a truncated list is not absent, and answering `not_found` here would
54
+ * false-fail a correct agent on a large mailbox
55
+ * - present, id absent, not capped → `message_not_found`
56
+ * - present in more than one mailbox → `message_ambiguous`
57
+ *
58
+ * The ambiguity arm is gmail's answer to github's `{repo}` slot. GitHub's legacy
59
+ * patterns took the repo as an optional qualifier and, absent it, scanned
60
+ * first-match-wins, so a two-repo world graded whichever sorted first. Gmail's
61
+ * corpus sentences carry no mailbox and ids are minted per mailbox, so rather
62
+ * than inventing a slot that would stop every existing criterion binding, this
63
+ * REFUSES on the world where the ambiguity is real.
64
+ */
65
+ export function resolveMessage(state, id) {
66
+ if (state.messages == null)
67
+ return { missing: "state_incomplete" };
68
+ const hits = state.messages.filter((message) => lower(message.id) === lower(id));
69
+ if (hits.length > 1) {
70
+ return { missing: `message_ambiguous ("${id}" exists in ${hits.length} mailboxes)` };
71
+ }
72
+ if (hits.length === 1)
73
+ return { found: hits[0] };
74
+ if (isTruncated(state, "messages"))
75
+ return { missing: 'collection_truncated ("messages")' };
76
+ return { missing: `message_not_found ("${id}")` };
77
+ }
78
+ /**
79
+ * Every label id that counts as `wanted`, lower-cased for comparison.
80
+ *
81
+ * Matches a label row by id OR by display name, because the corpus names both
82
+ * kinds: `Label_follow_up` is an id, `Follow Up` is the name of the same label.
83
+ * The bare `wanted` is always included so the join still works when the `labels`
84
+ * collection was capped away — a `messageLabels` row carries the bare id
85
+ * regardless.
86
+ */
87
+ export function labelIdsFor(state, wanted) {
88
+ const ids = new Set([lower(wanted)]);
89
+ for (const label of state.labels ?? []) {
90
+ const matches = lower(label.name) === lower(wanted) || lower(label.id) === lower(wanted);
91
+ if (matches && label.id != null)
92
+ ids.add(lower(label.id));
93
+ }
94
+ return ids;
95
+ }
96
+ /**
97
+ * Does this message carry the named label?
98
+ *
99
+ * Reads the `messageLabels` join. A capped join is refused rather than answered:
100
+ * a missing row there is indistinguishable from a label that was never applied,
101
+ * and on a negative criterion that difference is a leaking agent's free point.
102
+ */
103
+ export function messageCarriesLabel(state, messageId, wanted) {
104
+ if (state.messageLabels == null)
105
+ return { missing: "state_incomplete" };
106
+ const ids = labelIdsFor(state, wanted);
107
+ const carried = state.messageLabels.some((row) => lower(row.messageId) === lower(messageId) && ids.has(lower(row.labelId)));
108
+ if (!carried && isTruncated(state, "messageLabels")) {
109
+ return { missing: 'collection_truncated ("messageLabels")' };
110
+ }
111
+ return { found: carried };
112
+ }
113
+ /**
114
+ * The label a criterion names BY NAME.
115
+ *
116
+ * Deliberately does not fall back to the id. "A label named `Parity Complete`
117
+ * exists" asks about the display name; letting it also match an id would make
118
+ * the sentence mean two things, and `gmail.message-has-label` is the check that
119
+ * speaks ids.
120
+ */
121
+ export function resolveLabelByName(state, name) {
122
+ if (state.labels == null)
123
+ return { missing: "state_incomplete" };
124
+ const hit = state.labels.find((label) => lower(label.name) === lower(name));
125
+ if (hit)
126
+ return { found: hit };
127
+ if (isTruncated(state, "labels"))
128
+ return { missing: 'collection_truncated ("labels")' };
129
+ return { missing: `label_not_found ("${name}")` };
130
+ }
131
+ /**
132
+ * Who a draft is addressed to — `to` + `cc`, from the backing MESSAGE.
133
+ *
134
+ * The exported draft row carries no addressing at all, so a predicate reading it
135
+ * alone would answer "no draft is addressed to anyone", always. A draft whose
136
+ * message did not survive the export contributes nothing rather than throwing;
137
+ * the caller decides whether that emptiness is answerable.
138
+ */
139
+ export function draftRecipients(state, draft) {
140
+ const message = (state.messages ?? []).find((candidate) => lower(candidate.id) === lower(draft.messageId));
141
+ if (!message)
142
+ return [];
143
+ return [...(message.to ?? []), ...(message.cc ?? [])];
144
+ }
145
+ //# sourceMappingURL=check-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-state.js","sourceRoot":"","sources":["../../src/check-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Check } from "./check-kind.js";
2
+ export declare const noUnsupportedEndpoint: Check<Record<string, never>>;
@@ -0,0 +1,103 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // What Gmail's declared checks can assert about the RUN — the recorded call tape
4
+ // rather than the exported end state (F-1128).
5
+ //
6
+ // Why this class has to exist at all: an unsupported call leaves NO STATE TRACE.
7
+ // The twin answers 501 and mutates nothing, so `state_final.json` is
8
+ // byte-identical whether the examinee reached for an unimplemented route or
9
+ // never tried. The runtime stamps `fidelity: "unsupported"` on the recorded
10
+ // event instead, and that stamp is the only place the fact survives.
11
+ //
12
+ // This was the SHARED `unsupportedEndpointRule` in pome-cloud, registered by
13
+ // gmail and linear alike. Deleting `deterministic/gmail.ts` takes gmail's
14
+ // registration with it, so the declaration has to carry the assertion across or
15
+ // the criterion falls out of the vocabulary. `endpoints.ts` stays in the cloud
16
+ // for linear, which is still undeclared.
17
+ import { defineCheck } from "@pome-sh/sdk/checks";
18
+ import { tapeWorld } from "./check-worlds.js";
19
+ export const noUnsupportedEndpoint = defineCheck({
20
+ id: "gmail.no-unsupported-endpoint",
21
+ description: "Scans the recorded call tape for any request the twin answered with " +
22
+ 'fidelity "unsupported" — a route it does not implement, answered 501. It asserts nothing ' +
23
+ "about whether the run SUCCEEDED, and nothing about calls that were merely rejected: a 404 " +
24
+ "or a 422 from a route the twin does implement is a semantic answer and passes this check. " +
25
+ "The tape is scoped to this twin by the engine before the check sees it, so an unsupported " +
26
+ "call to a DIFFERENT twin in a multi-twin session cannot fail it — which matters here, " +
27
+ "because task 27 runs gmail and github together.",
28
+ // No slots, and no twin word. The legacy cloud rule accepted "No unsupported
29
+ // Gmail endpoint was called" alongside the bare form, plus plural and `were`
30
+ // variants, because an author typed English. Under position 2 an author PICKS
31
+ // the check, so those variants are retired rather than ported — the same
32
+ // decision `github.no-unsupported-endpoint` made, and this is now the same
33
+ // sentence on both twins, resolved per-twin by the engine.
34
+ template: "No unsupported endpoint was called",
35
+ params: {},
36
+ substrate: "tape",
37
+ // A prohibition. Nothing is required to happen; only the examinee reaching for
38
+ // an unimplemented route can break it.
39
+ polarity: () => "negative",
40
+ // No caller-supplied literal is hunted for in any substrate, so there is
41
+ // nothing a redactor could silently delete out from under this check.
42
+ subject: () => null,
43
+ // No slots, so the sentence carries no literal to falsify. The trigger is "a
44
+ // call with fidelity=unsupported exists", which lives on the tape and not in
45
+ // the sentence. Reported as `no_trigger`, never as clean.
46
+ vacuityMutant: () => null,
47
+ discriminatingWorlds: () => ({
48
+ passing: tapeWorld([
49
+ {
50
+ twin: "gmail",
51
+ method: "GET",
52
+ path: "/gmail/v1/users/me/messages",
53
+ status: 200,
54
+ fidelity: "semantic",
55
+ event_id: "evt_ok",
56
+ },
57
+ ]),
58
+ failing: tapeWorld([
59
+ {
60
+ twin: "gmail",
61
+ method: "POST",
62
+ path: "/gmail/v1/users/me/watch",
63
+ status: 501,
64
+ fidelity: "unsupported",
65
+ event_id: "evt_bad",
66
+ },
67
+ ]),
68
+ }),
69
+ evaluate(_args, { tape }) {
70
+ // The engine guards this before calling; the check guards too, so a consumer
71
+ // that forgets gets a named skip rather than a vacuous pass. For a NEGATIVE
72
+ // criterion that distinction is the whole ballgame — passing here would be a
73
+ // clean bill of health issued over a tape nobody read.
74
+ //
75
+ // Note `null` and `[]` are deliberately different: an EMPTY tape is a real
76
+ // world (the agent called nothing, so it called nothing unsupported) and
77
+ // reaches a real verdict below.
78
+ if (tape === null)
79
+ return { passed: false, reason: "tape_missing", status: "skipped" };
80
+ const unsupported = tape.filter((event) => event.fidelity === "unsupported");
81
+ if (unsupported.length === 0) {
82
+ // No evidence on the pass branch (F-980). This asserts a NEGATIVE over the
83
+ // whole tape and a negative over an empty set has no single call to point
84
+ // at; citing all N inspected calls would be a copy of the trace.
85
+ return {
86
+ passed: true,
87
+ reason: `no unsupported Gmail endpoint was called (${tape.length} call(s) inspected)`,
88
+ };
89
+ }
90
+ // Cite the offenders. A row with no `event_id` drops out of the CITATION but
91
+ // not out of the count or the prose: losing an id must never lose a finding.
92
+ const evidenceEventIds = unsupported
93
+ .map((event) => event.event_id)
94
+ .filter((id) => typeof id === "string" && id !== "");
95
+ const outcome = {
96
+ passed: false,
97
+ reason: `${unsupported.length} unsupported Gmail call(s): ` +
98
+ `[${unsupported.map((event) => event.path ?? "?").join(", ")}]`,
99
+ };
100
+ return evidenceEventIds.length > 0 ? { ...outcome, evidenceEventIds } : outcome;
101
+ },
102
+ });
103
+ //# sourceMappingURL=check-tape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-tape.js","sourceRoot":"","sources":["../../src/check-tape.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { CheckSubstrate, CheckTapeEvent } from "@pome-sh/sdk/checks";
2
+ import type { GmailCheckState, GmailCheckStateDraft, GmailCheckStateLabel, GmailCheckStateMessage, GmailCheckStateMessageLabel } from "./check-state.js";
3
+ export declare const FIXTURE_MAILBOX = "pome-agent@pome-twin.test";
4
+ export declare function gmailState(parts?: Partial<GmailCheckState>): GmailCheckState;
5
+ export declare function finalWorld(final: GmailCheckState): CheckSubstrate<GmailCheckState>;
6
+ export declare function tapeWorld(tape: readonly CheckTapeEvent[]): CheckSubstrate<GmailCheckState>;
7
+ export declare function message(id: string, parts?: Partial<GmailCheckStateMessage>): GmailCheckStateMessage;
8
+ export declare function systemLabel(name: string): GmailCheckStateLabel;
9
+ export declare function userLabel(id: string, name: string): GmailCheckStateLabel;
10
+ export declare function messageLabel(messageId: string, labelId: string): GmailCheckStateMessageLabel;
11
+ export declare function draft(id: string, messageId: string): GmailCheckStateDraft;
12
+ /** A draft plus the message its addressing lives on — the pair, because either
13
+ * alone is a world the export could not produce. */
14
+ export declare function draftAddressedTo(id: string, recipients: string[]): {
15
+ draft: GmailCheckStateDraft;
16
+ message: GmailCheckStateMessage;
17
+ };
@@ -0,0 +1,62 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // The fixture worlds Gmail's declarations name (F-1128).
4
+ //
5
+ // In `src/` rather than `test/` for the same reason twin-github's and
6
+ // twin-slack's are: `discriminatingWorlds` is a DECLARED field read from npm by
7
+ // pome-cloud and the CLI, so a builder that shipped only in the test tree would
8
+ // make the field unusable outside this repo.
9
+ //
10
+ // `gmailState` fills every collection explicitly, including the empty ones.
11
+ // Omitting one makes the reader answer `state_incomplete`, which is a SKIP, and
12
+ // a skip satisfies neither arm of the discrimination gate — so the explicitness
13
+ // is load-bearing, not tidiness.
14
+ export const FIXTURE_MAILBOX = "pome-agent@pome-twin.test";
15
+ export function gmailState(parts = {}) {
16
+ return {
17
+ mailboxes: [{ email: FIXTURE_MAILBOX }],
18
+ messages: [],
19
+ drafts: [],
20
+ labels: [],
21
+ messageLabels: [],
22
+ exportBounds: { messageBodiesOmitted: true, largeMailbox: false, truncatedCollections: [] },
23
+ ...parts,
24
+ };
25
+ }
26
+ export function finalWorld(final) {
27
+ return { seed: null, final, tape: null };
28
+ }
29
+ // A well-formed but EMPTY mailbox, deliberately not `{}`. A tape check must not
30
+ // read state, and handing it a shape that would satisfy a state check hides the
31
+ // difference — twin-github's `tapeWorld` makes the same choice for the same
32
+ // reason.
33
+ export function tapeWorld(tape) {
34
+ return { seed: null, final: gmailState(), tape };
35
+ }
36
+ export function message(id, parts = {}) {
37
+ return { mailboxEmail: FIXTURE_MAILBOX, id, to: [FIXTURE_MAILBOX], bodyOmitted: true, ...parts };
38
+ }
39
+ export function systemLabel(name) {
40
+ // System labels carry `id === name`. Building them any other way would make a
41
+ // fixture that the twin's own export could never produce.
42
+ return { mailboxEmail: FIXTURE_MAILBOX, id: name, name, type: "system" };
43
+ }
44
+ export function userLabel(id, name) {
45
+ return { mailboxEmail: FIXTURE_MAILBOX, id, name, type: "user" };
46
+ }
47
+ export function messageLabel(messageId, labelId) {
48
+ return { mailboxEmail: FIXTURE_MAILBOX, messageId, labelId };
49
+ }
50
+ export function draft(id, messageId) {
51
+ return { mailboxEmail: FIXTURE_MAILBOX, id, messageId };
52
+ }
53
+ /** A draft plus the message its addressing lives on — the pair, because either
54
+ * alone is a world the export could not produce. */
55
+ export function draftAddressedTo(id, recipients) {
56
+ const messageId = `${id}_message`;
57
+ return {
58
+ draft: draft(id, messageId),
59
+ message: message(messageId, { to: recipients }),
60
+ };
61
+ }
62
+ //# sourceMappingURL=check-worlds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-worlds.js","sourceRoot":"","sources":["../../src/check-worlds.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,yDAAyD;AACzD,EAAE;AACF,sEAAsE;AACtE,gFAAgF;AAChF,gFAAgF;AAChF,6CAA6C;AAC7C,EAAE;AACF,4EAA4E;AAC5E,gFAAgF;AAChF,gFAAgF;AAChF,iCAAiC;AAWjC,MAAM,CAAC,MAAM,eAAe,GAAG,2BAA2B,CAAC;AAE3D,MAAM,UAAU,UAAU,CAAC,QAAkC,EAAE;IAC7D,OAAO;QACL,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;QACvC,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,EAAE;QACV,MAAM,EAAE,EAAE;QACV,aAAa,EAAE,EAAE;QACjB,YAAY,EAAE,EAAE,oBAAoB,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,EAAE;QAC3F,GAAG,KAAK;KACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AAC3C,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,4EAA4E;AAC5E,UAAU;AACV,MAAM,UAAU,SAAS,CAAC,IAA+B;IACvD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,OAAO,CACrB,EAAU,EACV,QAAyC,EAAE;IAE3C,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,eAAe,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,CAAC;AACnG,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,8EAA8E;IAC9E,0DAA0D;IAC1D,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,EAAU,EAAE,IAAY;IAChD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,SAAiB,EAAE,OAAe;IAC7D,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,EAAU,EAAE,SAAiB;IACjD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC;AAC1D,CAAC;AAED;qDACqD;AACrD,MAAM,UAAU,gBAAgB,CAC9B,EAAU,EACV,UAAoB;IAEpB,MAAM,SAAS,GAAG,GAAG,EAAE,UAAU,CAAC;IAClC,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,SAAS,CAAC;QAC3B,OAAO,EAAE,OAAO,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC;KAChD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,19 @@
1
+ export type { Check } from "./check-kind.js";
2
+ export type { GmailCheckState, GmailCheckStateDraft, GmailCheckStateLabel, GmailCheckStateMailbox, GmailCheckStateMessage, GmailCheckStateMessageLabel, } from "./check-state.js";
3
+ export declare const GMAIL_CHECKS: readonly [import("./check-kind.js").Check<{
4
+ message: string;
5
+ label: string;
6
+ }>, import("./check-kind.js").Check<{
7
+ label: string;
8
+ }>, import("./check-kind.js").Check<{
9
+ email: string;
10
+ }>, import("./check-kind.js").Check<{
11
+ count: string;
12
+ }>, import("./check-kind.js").Check<{
13
+ mailbox: string;
14
+ count: string;
15
+ label: string;
16
+ }>, import("./check-kind.js").Check<{
17
+ label: string;
18
+ count: string;
19
+ }>, import("./check-kind.js").Check<Record<string, never>>];
@@ -0,0 +1,42 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // The Gmail twin's assertable check vocabulary (F-1128, milestone A3).
4
+ //
5
+ // These live HERE, next to the state they read, because the twin owns that
6
+ // state's shape. pome-cloud imports this module from npm and adapts each
7
+ // declaration onto its predicate engine, so there is no second copy to
8
+ // reconcile — only a pin that can fall behind, which is what its drift gate
9
+ // exists to catch. The cloud's `deterministic/gmail.ts`, and the mirror of the
10
+ // state shape inside it, are deleted in the same milestone.
11
+ //
12
+ // Gmail comes third in A3 and is the first that needed PLUMBING before
13
+ // vocabulary: unlike slack and stripe it had no in-process seed loader in the
14
+ // control plane at all, so every gmail criterion reported `no_seed_loader` —
15
+ // not a wrong verdict, an absent one. Six criteria across tasks 22, 23 and 27
16
+ // resolved zero of six before this.
17
+ //
18
+ // This file is the ASSEMBLY. Declarations are grouped by what they assert about
19
+ // — `check-messages.ts`, `check-labels.ts`, `check-drafts.ts`, `check-tape.ts` —
20
+ // with typed slots in `check-params.ts`, fixture worlds in `check-worlds.ts`,
21
+ // and the reading of the exported tree in `check-state.ts`.
22
+ import { draftAddressedTo, draftCountAtLeast } from "./check-drafts.js";
23
+ import { labelExists } from "./check-labels.js";
24
+ import { mailboxLabelCount, messageHasLabel, oneMessagePerRecipient } from "./check-messages.js";
25
+ import { noUnsupportedEndpoint } from "./check-tape.js";
26
+ // Order is not first-match-wins — the generated patterns are anchored and
27
+ // mutually exclusive, and `checks-contract.test.ts` proves no sentence is
28
+ // claimed by two. It is the order an authoring surface lists them in, running
29
+ // from the assertion an author reaches for first to the ones a specialised task
30
+ // needs.
31
+ export const GMAIL_CHECKS = [
32
+ messageHasLabel,
33
+ labelExists,
34
+ draftAddressedTo,
35
+ draftCountAtLeast,
36
+ mailboxLabelCount,
37
+ oneMessagePerRecipient,
38
+ // Last, because these are the only ones that assert about the RUN rather than
39
+ // the world it left behind — twin-github orders its tape checks the same way.
40
+ noUnsupportedEndpoint,
41
+ ];
42
+ //# sourceMappingURL=checks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checks.js","sourceRoot":"","sources":["../../src/checks.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,uEAAuE;AACvE,EAAE;AACF,2EAA2E;AAC3E,yEAAyE;AACzE,uEAAuE;AACvE,4EAA4E;AAC5E,+EAA+E;AAC/E,4DAA4D;AAC5D,EAAE;AACF,uEAAuE;AACvE,8EAA8E;AAC9E,6EAA6E;AAC7E,8EAA8E;AAC9E,oCAAoC;AACpC,EAAE;AACF,gFAAgF;AAChF,iFAAiF;AACjF,8EAA8E;AAC9E,4DAA4D;AAE5D,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACjG,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAYxD,0EAA0E;AAC1E,0EAA0E;AAC1E,8EAA8E;AAC9E,gFAAgF;AAChF,SAAS;AACT,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,eAAe;IACf,WAAW;IACX,gBAAgB;IAChB,iBAAiB;IACjB,iBAAiB;IACjB,sBAAsB;IACtB,8EAA8E;IAC9E,8EAA8E;IAC9E,qBAAqB;CACb,CAAC"}