@pome-sh/checks 0.1.4 → 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 +64 -0
- package/dist/_types/twin-github/src/types.d.ts +4 -0
- package/dist/_types/twin-gmail/src/check-state.d.ts +22 -0
- package/dist/{chunk-5PDF3GCK.js → chunk-MJOHK2NI.js} +12 -6
- package/dist/{chunk-SA23X6PB.js → chunk-SNTLDQSZ.js} +4 -4
- package/dist/gmail.js +1 -1
- package/dist/index.js +4 -4
- package/dist/slack.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
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
|
+
|
|
26
|
+
## 0.1.5
|
|
27
|
+
|
|
28
|
+
`slack.no-reaction-added` now refuses instead of scoring a free pass (F-1159).
|
|
29
|
+
Its predicate filtered the exported `reactions` collection with `(final.reactions
|
|
30
|
+
?? []).some(…)`, so a state export carrying no `reactions` section at all
|
|
31
|
+
filtered to zero rows and scored this NEGATIVE criterion `passed` — an agent
|
|
32
|
+
that really added the reaction still collected the point. It now checks
|
|
33
|
+
`final.reactions == null` first and returns `state_incomplete`, matching
|
|
34
|
+
twin-github's `pull.reviews == null` / `pull.comments == null` skips: absent is
|
|
35
|
+
not the same as none.
|
|
36
|
+
|
|
37
|
+
This closes the gap the same class of criterion in twin-github never had, and
|
|
38
|
+
it is why pome-cloud's `STATE_SECTION_GUARDS` carried a stopgap row for this one
|
|
39
|
+
check (F-1156) — that row is now redundant. No check id, template or polarity
|
|
40
|
+
changed, so no criterion moves from bound to unbound; this only affects the
|
|
41
|
+
verdict on the one export shape (`reactions` absent) that used to be misgraded,
|
|
42
|
+
and on that shape the cloud already returned `state_incomplete` via the stopgap.
|
|
43
|
+
The grade a real run receives does not move.
|
|
44
|
+
|
|
45
|
+
**What pome-cloud must do, and it is TWO edits, not one.** Pinning `0.1.5`
|
|
46
|
+
turns `declared-pin.test.ts` red on its own, before anybody touches the guard
|
|
47
|
+
table, so a follow-up that only deletes the row will not go green:
|
|
48
|
+
|
|
49
|
+
1. Delete the `slack.no-reaction-added` row from `STATE_SECTION_GUARDS`
|
|
50
|
+
(`apps/control-plane/src/services/evaluators/deterministic/substrate-guards.ts`).
|
|
51
|
+
The arm that asserts every vacuous reader has a row —
|
|
52
|
+
`findVacuousStateSectionReaders(allDeclared(), STATE_SECTION_GUARDS)` — stays
|
|
53
|
+
`[]` with the row present or absent, because the twin now refuses on its own.
|
|
54
|
+
2. Re-point the NEGATIVE CONTROL beside it, `names the shipped reader when the
|
|
55
|
+
table is empty`. It asserts
|
|
56
|
+
`findVacuousStateSectionReaders(allDeclared(), [])` equals
|
|
57
|
+
`["slack.no-reaction-added:reactions"]` — the detector's only firing case in
|
|
58
|
+
the shipped vocabulary, and this release is what removes it. Measured against
|
|
59
|
+
the built `0.1.5` declarations it now returns `[]`. Give that arm a synthetic
|
|
60
|
+
declaration that still reads absence as a pass, the way the three arms below
|
|
61
|
+
it already build one inline, so the detector keeps a firing case nobody has
|
|
62
|
+
to ship a defect to preserve.
|
|
63
|
+
|
|
64
|
+
Both `apps/control-plane` and `apps/mcp` pin this package exactly and must move
|
|
65
|
+
together, or `save_task` accepts criteria the grader cannot bind.
|
|
66
|
+
|
|
3
67
|
## 0.1.4
|
|
4
68
|
|
|
5
69
|
Carries twin-github's widened seed schema to the grader (F-1421). One thing
|
|
@@ -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[];
|
|
@@ -173,7 +173,7 @@ var noMessagePosted = defineCheck({
|
|
|
173
173
|
});
|
|
174
174
|
var noReactionAdded = defineCheck({
|
|
175
175
|
id: "slack.no-reaction-added",
|
|
176
|
-
description: "Resolves the named channel, then filters the TOP-LEVEL reactions list by that channel's id and this emoji name, asserting no row matches. Reactions are not nested under their channel in the export, so this is a join the predicate performs itself. It asserts nothing about which message was reacted to, or by whom.",
|
|
176
|
+
description: "Resolves the named channel, then filters the TOP-LEVEL reactions list by that channel's id and this emoji name, asserting no row matches. Reactions are not nested under their channel in the export, so this is a join the predicate performs itself. It asserts nothing about which message was reacted to, or by whom. An export carrying no `reactions` collection at all is SKIPPED, because absent is not the same as none.",
|
|
177
177
|
template: 'No "{reaction}" reaction was added in the "{channel}" channel',
|
|
178
178
|
params: { reaction: emojiName, channel: channelName },
|
|
179
179
|
substrate: "final",
|
|
@@ -209,14 +209,20 @@ var noReactionAdded = defineCheck({
|
|
|
209
209
|
const found = resolveChannel(final, channel);
|
|
210
210
|
if ("missing" in found)
|
|
211
211
|
return missSkip(found);
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
joined.push(statePath("reactions"));
|
|
212
|
+
if (final.reactions == null)
|
|
213
|
+
return STATE_INCOMPLETE;
|
|
214
|
+
const hit = final.reactions.some((row) => row.channel_id === found.found.id && row.name === reaction);
|
|
216
215
|
return {
|
|
217
216
|
passed: !hit,
|
|
218
217
|
reason: hit ? `reaction "${reaction}" found in channel "${channel}"` : `no reaction "${reaction}" in channel "${channel}"`,
|
|
219
|
-
|
|
218
|
+
// BOTH sides of the join, because this predicate really does read two
|
|
219
|
+
// places and a reader who opens only one cannot check its work: the
|
|
220
|
+
// channel row is where the id came from, the top-level reactions list is
|
|
221
|
+
// what was filtered. Reactions are not nested under their channel in the
|
|
222
|
+
// export — that is the whole reason this check performs a join — so one
|
|
223
|
+
// pointer cannot say it. The guard above already proved `reactions` is
|
|
224
|
+
// present, so both pointers always resolve.
|
|
225
|
+
evidenceStatePaths: [found.path, statePath("reactions")]
|
|
220
226
|
};
|
|
221
227
|
}
|
|
222
228
|
});
|
|
@@ -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-
|
|
1
|
+
export { GMAIL_CHECKS, defaultSeedState, gmailSeedSchema, parseSeed } from './chunk-SNTLDQSZ.js';
|
|
2
2
|
import './chunk-W2JNYULF.js';
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
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-
|
|
5
|
-
export { GMAIL_CHECKS, defaultSeedState as defaultGmailSeed, gmailSeedSchema, parseSeed as parseGmailSeed } from './chunk-
|
|
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
|
-
import { SLACK_CHECKS } from './chunk-
|
|
9
|
-
export { SLACK_CHECKS, defaultSeedState as defaultSlackSeed, parseSeed as parseSlackSeed, seedSchema as slackSeedSchema } from './chunk-
|
|
8
|
+
import { SLACK_CHECKS } from './chunk-MJOHK2NI.js';
|
|
9
|
+
export { SLACK_CHECKS, defaultSeedState as defaultSlackSeed, parseSeed as parseSlackSeed, seedSchema as slackSeedSchema } from './chunk-MJOHK2NI.js';
|
|
10
10
|
import { STRIPE_CHECKS } from './chunk-XOUAZPNB.js';
|
|
11
11
|
export { STRIPE_CHECKS, defaultSeed as defaultStripeSeed, parseSeed as parseStripeSeed, seedSchema as stripeSeedSchema } from './chunk-XOUAZPNB.js';
|
|
12
12
|
export { REDACTION_PLACEHOLDER, VACUITY_SENTINEL, VACUITY_SENTINEL_NUMBER, VACUITY_SENTINEL_SNAKE, checkNearMissPattern, checkPattern, checksDigest, childStatePath, defineCheck, isRedacted, oneOf, parseCheck, probeDiscrimination, probeRedactionSurvival, probeStateCitation, renderCheck, repoRef, resolveStatePath, statePath, templateSlots } from './chunk-W2JNYULF.js';
|
package/dist/slack.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { SLACK_CHECKS, defaultSeedState, parseSeed, seedSchema } from './chunk-
|
|
1
|
+
export { SLACK_CHECKS, defaultSeedState, parseSeed, seedSchema } from './chunk-MJOHK2NI.js';
|
|
2
2
|
import './chunk-W2JNYULF.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pome-sh/checks",
|
|
3
|
-
"version": "0.1.
|
|
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",
|