@pome-sh/checks 0.1.5 → 0.1.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @pome-sh/checks
2
2
 
3
+ ## 0.1.6
4
+
5
+ `gmail.mailbox-label-count` now refuses instead of scoring a free pass (F-1441)
6
+ — the same class as 0.1.5's `slack.no-reaction-added`, found live in twin-gmail
7
+ on a worse criterion. Its polarity flips NEGATIVE at count 0, so the vacuous
8
+ pass handed a point to an agent that did the forbidden thing.
9
+
10
+ `labelIdsFor` read `state.labels ?? []` with no absence guard. The bare display
11
+ name is always added to the id set so the join survives a capped collection —
12
+ but that only holds for SYSTEM labels, where `id === name`. A USER label's
13
+ minted id differs from its display name by construction (the default seed ships
14
+ `{ id: "Label_follow_up", name: "Follow Up" }`), so with `labels` absent the
15
+ lookup degraded to a name no `messageLabels` row carries, the total came out 0,
16
+ and `0 === 0` passed over an export in which the agent DID apply the label.
17
+
18
+ `labels` is now guarded for both absence and truncation alongside `messages` and
19
+ `messageLabels`, in `gmail.mailbox-label-count` and in the second `labelIdsFor`
20
+ consumer (`oneMessagePerRecipient`, positive polarity and fail-closed today —
21
+ guarded anyway, because safe-by-polarity is how this class survives review).
22
+ `labelIdsFor`'s own comment now states that its bare-id fallback is
23
+ system-labels-only and that callers must guard; `draftRecipients` carries the
24
+ written reason its ticket asked for.
25
+
3
26
  ## 0.1.5
4
27
 
5
28
  `slack.no-reaction-added` now refuses instead of scoring a free pass (F-1159).
@@ -243,6 +243,10 @@ export type ReleaseRow = {
243
243
  prerelease: 0 | 1;
244
244
  author_login: string;
245
245
  created_at: string;
246
+ /** F-1459 — GitHub's release `updated_at`. Equal to `created_at` on a release
247
+ * that has never been edited, which is every release this twin serves: it has
248
+ * no release-update route. */
249
+ updated_at: string;
246
250
  published_at: string | null;
247
251
  };
248
252
  export type CheckRunRow = {
@@ -108,6 +108,20 @@ export declare function resolveMessage(state: GmailCheckState, id: string): Reso
108
108
  * The bare `wanted` is always included so the join still works when the `labels`
109
109
  * collection was capped away — a `messageLabels` row carries the bare id
110
110
  * regardless.
111
+ *
112
+ * ⚠️ THAT FALLBACK IS SYSTEM-LABELS-ONLY, and the sentence above used to stop
113
+ * short of saying so, which is why the gap read as intentional (F-1441). It
114
+ * holds when `id === name` — `INBOX`, `UNREAD`, `STARRED`. A USER label's
115
+ * minted id differs from its display name by construction (the default seed
116
+ * ships `{ id: "Label_follow_up", name: "Follow Up" }`), so with `labels`
117
+ * absent or capped this degrades to a name no `messageLabels` row carries and
118
+ * the join silently returns nothing.
119
+ *
120
+ * So: this function cannot refuse — it returns a Set, and an empty Set is
121
+ * indistinguishable from "the label was never applied". **Every caller must
122
+ * guard `state.labels` absence and truncation itself before calling**, and both
123
+ * callers in check-messages.ts now do. Adding a third without that guard
124
+ * reintroduces the F-1159 class.
111
125
  */
112
126
  export declare function labelIdsFor(state: GmailCheckState, wanted: string): Set<string>;
113
127
  /**
@@ -134,5 +148,13 @@ export declare function resolveLabelByName(state: GmailCheckState, name: string)
134
148
  * alone would answer "no draft is addressed to anyone", always. A draft whose
135
149
  * message did not survive the export contributes nothing rather than throwing;
136
150
  * the caller decides whether that emptiness is answerable.
151
+ *
152
+ * ⚠️ Same shape as `labelIdsFor`, same rule, and the reason is written down
153
+ * rather than left to be rediscovered (F-1441): `state.messages ?? []` cannot
154
+ * tell an absent collection from one that holds no match, so this function
155
+ * cannot refuse either. It is safe TODAY only because its sole caller
156
+ * null-checks `final.messages` first (`check-drafts.ts:63`) — safe-by-caller,
157
+ * which is precisely how this class survives review. A second caller must
158
+ * guard, or this must be changed to return a `Resolved`.
137
159
  */
138
160
  export declare function draftRecipients(state: GmailCheckState, draft: GmailCheckStateDraft): string[];
@@ -430,10 +430,10 @@ var mailboxLabelCount = defineCheck({
430
430
  },
431
431
  evaluate({ mailbox, count, label }, { final }) {
432
432
  const wanted = Number(count);
433
- if (final.messages == null || final.messageLabels == null) {
433
+ if (final.messages == null || final.messageLabels == null || final.labels == null) {
434
434
  return { passed: false, status: "skipped", reason: "state_incomplete" };
435
435
  }
436
- if (isTruncated(final, "messages") || isTruncated(final, "messageLabels")) {
436
+ if (isTruncated(final, "messages") || isTruncated(final, "messageLabels") || isTruncated(final, "labels")) {
437
437
  return { passed: false, status: "skipped", reason: "collection_truncated" };
438
438
  }
439
439
  if (final.mailboxes != null && !final.mailboxes.some((mb) => (mb.email ?? "").toLowerCase() === mailbox.toLowerCase())) {
@@ -490,10 +490,10 @@ var oneMessagePerRecipient = defineCheck({
490
490
  },
491
491
  evaluate({ label, count }, { final }) {
492
492
  const wanted = parseCount(count);
493
- if (final.messages == null || final.messageLabels == null) {
493
+ if (final.messages == null || final.messageLabels == null || final.labels == null) {
494
494
  return { passed: false, status: "skipped", reason: "state_incomplete" };
495
495
  }
496
- if (isTruncated(final, "messages") || isTruncated(final, "messageLabels")) {
496
+ if (isTruncated(final, "messages") || isTruncated(final, "messageLabels") || isTruncated(final, "labels")) {
497
497
  return { passed: false, status: "skipped", reason: "collection_truncated" };
498
498
  }
499
499
  const ids = labelIdsFor(final, label);
package/dist/gmail.js CHANGED
@@ -1,2 +1,2 @@
1
- export { GMAIL_CHECKS, defaultSeedState, gmailSeedSchema, parseSeed } from './chunk-SA23X6PB.js';
1
+ export { GMAIL_CHECKS, defaultSeedState, gmailSeedSchema, parseSeed } from './chunk-SNTLDQSZ.js';
2
2
  import './chunk-W2JNYULF.js';
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import './chunk-ZXE6LAM3.js';
2
2
  import { GITHUB_CHECKS } from './chunk-JVCGWAZQ.js';
3
3
  export { GITHUB_CHECKS, defaultSeedState as defaultGitHubSeed, seedSchema as githubSeedSchema, parseSeed as parseGitHubSeed } from './chunk-JVCGWAZQ.js';
4
- import { GMAIL_CHECKS } from './chunk-SA23X6PB.js';
5
- export { GMAIL_CHECKS, defaultSeedState as defaultGmailSeed, gmailSeedSchema, parseSeed as parseGmailSeed } from './chunk-SA23X6PB.js';
4
+ import { GMAIL_CHECKS } from './chunk-SNTLDQSZ.js';
5
+ export { GMAIL_CHECKS, defaultSeedState as defaultGmailSeed, gmailSeedSchema, parseSeed as parseGmailSeed } from './chunk-SNTLDQSZ.js';
6
6
  import { LINEAR_CHECKS } from './chunk-THOSO63W.js';
7
7
  export { LINEAR_CHECKS, defaultSeedState as defaultLinearSeed, linearSeedSchema, parseSeed as parseLinearSeed } from './chunk-THOSO63W.js';
8
8
  import { SLACK_CHECKS } from './chunk-MJOHK2NI.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pome-sh/checks",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Pome's grading vocabulary — the check declarations, seed schemas and default seeds of all five digital twins, plus the check DSL they are written in. Declarations only: no twin server, no database, no routes, no tools.",
5
5
  "private": false,
6
6
  "type": "module",