@pome-sh/checks 0.1.3 → 0.1.5

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,72 @@
1
1
  # @pome-sh/checks
2
2
 
3
+ ## 0.1.5
4
+
5
+ `slack.no-reaction-added` now refuses instead of scoring a free pass (F-1159).
6
+ Its predicate filtered the exported `reactions` collection with `(final.reactions
7
+ ?? []).some(…)`, so a state export carrying no `reactions` section at all
8
+ filtered to zero rows and scored this NEGATIVE criterion `passed` — an agent
9
+ that really added the reaction still collected the point. It now checks
10
+ `final.reactions == null` first and returns `state_incomplete`, matching
11
+ twin-github's `pull.reviews == null` / `pull.comments == null` skips: absent is
12
+ not the same as none.
13
+
14
+ This closes the gap the same class of criterion in twin-github never had, and
15
+ it is why pome-cloud's `STATE_SECTION_GUARDS` carried a stopgap row for this one
16
+ check (F-1156) — that row is now redundant. No check id, template or polarity
17
+ changed, so no criterion moves from bound to unbound; this only affects the
18
+ verdict on the one export shape (`reactions` absent) that used to be misgraded,
19
+ and on that shape the cloud already returned `state_incomplete` via the stopgap.
20
+ The grade a real run receives does not move.
21
+
22
+ **What pome-cloud must do, and it is TWO edits, not one.** Pinning `0.1.5`
23
+ turns `declared-pin.test.ts` red on its own, before anybody touches the guard
24
+ table, so a follow-up that only deletes the row will not go green:
25
+
26
+ 1. Delete the `slack.no-reaction-added` row from `STATE_SECTION_GUARDS`
27
+ (`apps/control-plane/src/services/evaluators/deterministic/substrate-guards.ts`).
28
+ The arm that asserts every vacuous reader has a row —
29
+ `findVacuousStateSectionReaders(allDeclared(), STATE_SECTION_GUARDS)` — stays
30
+ `[]` with the row present or absent, because the twin now refuses on its own.
31
+ 2. Re-point the NEGATIVE CONTROL beside it, `names the shipped reader when the
32
+ table is empty`. It asserts
33
+ `findVacuousStateSectionReaders(allDeclared(), [])` equals
34
+ `["slack.no-reaction-added:reactions"]` — the detector's only firing case in
35
+ the shipped vocabulary, and this release is what removes it. Measured against
36
+ the built `0.1.5` declarations it now returns `[]`. Give that arm a synthetic
37
+ declaration that still reads absence as a pass, the way the three arms below
38
+ it already build one inline, so the detector keeps a firing case nobody has
39
+ to ship a defect to preserve.
40
+
41
+ Both `apps/control-plane` and `apps/mcp` pin this package exactly and must move
42
+ together, or `save_task` accepts criteria the grader cannot bind.
43
+
44
+ ## 0.1.4
45
+
46
+ Carries twin-github's widened seed schema to the grader (F-1421). One thing
47
+ moves in `dist/`: `@pome-sh/checks/github`'s re-exported `seedSchema` — and with
48
+ it `parseSeed` — now accepts five entity types it used to strip silently.
49
+
50
+ - `repositories[].milestones[]`, `repositories[].tags[]`,
51
+ `repositories[].releases[]`
52
+ - `repositories[].issues[].comments[]` and
53
+ `repositories[].pull_requests[].comments[]`
54
+ - `repositories[].pull_requests[].review_comments[]`
55
+
56
+ Every one is optional with a `[]` default, so a seed that parsed before parses
57
+ to the same value now. This is a widening, not a tightening: nothing a consumer
58
+ already sends can start failing.
59
+
60
+ Why it is owed a release rather than left to the twin: this package's job is to
61
+ carry the twins' seed schemas to a consumer that has no twin, and a consumer
62
+ validating a seed against 0.1.3 would strip exactly the keys twin-github 0.10.2
63
+ now honors — reporting a seed as accepted and a world as seeded while five
64
+ entities were dropped on the way. The two halves have to move together or the
65
+ copy on the grader's side becomes the one that decides what a seed may say.
66
+
67
+ No check declaration, template, polarity or vacuity sentinel changed, so no
68
+ existing criterion's verdict moves.
69
+
3
70
  ## 0.1.3
4
71
 
5
72
  No change to the published surface — `dist/` is byte-identical to 0.1.2, so no
@@ -26,6 +26,29 @@ export declare const seedSchema: z.ZodObject<{
26
26
  content: z.ZodString;
27
27
  branch: z.ZodOptional<z.ZodString>;
28
28
  }, z.core.$strip>>>;
29
+ milestones: z.ZodDefault<z.ZodArray<z.ZodObject<{
30
+ number: z.ZodOptional<z.ZodNumber>;
31
+ title: z.ZodString;
32
+ description: z.ZodDefault<z.ZodString>;
33
+ state: z.ZodDefault<z.ZodEnum<{
34
+ open: "open";
35
+ closed: "closed";
36
+ }>>;
37
+ due_on: z.ZodOptional<z.ZodString>;
38
+ }, z.core.$strip>>>;
39
+ tags: z.ZodDefault<z.ZodArray<z.ZodObject<{
40
+ name: z.ZodString;
41
+ target: z.ZodOptional<z.ZodString>;
42
+ }, z.core.$strip>>>;
43
+ releases: z.ZodDefault<z.ZodArray<z.ZodObject<{
44
+ tag_name: z.ZodString;
45
+ name: z.ZodOptional<z.ZodString>;
46
+ body: z.ZodDefault<z.ZodString>;
47
+ target_commitish: z.ZodOptional<z.ZodString>;
48
+ draft: z.ZodDefault<z.ZodBoolean>;
49
+ prerelease: z.ZodDefault<z.ZodBoolean>;
50
+ author: z.ZodOptional<z.ZodString>;
51
+ }, z.core.$strip>>>;
29
52
  issues: z.ZodDefault<z.ZodArray<z.ZodObject<{
30
53
  number: z.ZodOptional<z.ZodNumber>;
31
54
  title: z.ZodString;
@@ -36,6 +59,10 @@ export declare const seedSchema: z.ZodObject<{
36
59
  }>>;
37
60
  labels: z.ZodDefault<z.ZodArray<z.ZodString>>;
38
61
  assignees: z.ZodDefault<z.ZodArray<z.ZodString>>;
62
+ comments: z.ZodDefault<z.ZodArray<z.ZodObject<{
63
+ body: z.ZodString;
64
+ author: z.ZodOptional<z.ZodString>;
65
+ }, z.core.$strip>>>;
39
66
  }, z.core.$strip>>>;
40
67
  pull_requests: z.ZodDefault<z.ZodArray<z.ZodObject<{
41
68
  number: z.ZodOptional<z.ZodNumber>;
@@ -67,6 +94,20 @@ export declare const seedSchema: z.ZodObject<{
67
94
  }>>;
68
95
  description: z.ZodDefault<z.ZodString>;
69
96
  }, z.core.$strip>>>;
97
+ comments: z.ZodDefault<z.ZodArray<z.ZodObject<{
98
+ body: z.ZodString;
99
+ author: z.ZodOptional<z.ZodString>;
100
+ }, z.core.$strip>>>;
101
+ review_comments: z.ZodDefault<z.ZodArray<z.ZodObject<{
102
+ body: z.ZodString;
103
+ path: z.ZodString;
104
+ line: z.ZodDefault<z.ZodNumber>;
105
+ side: z.ZodDefault<z.ZodEnum<{
106
+ LEFT: "LEFT";
107
+ RIGHT: "RIGHT";
108
+ }>>;
109
+ author: z.ZodOptional<z.ZodString>;
110
+ }, z.core.$strip>>>;
70
111
  }, z.core.$strip>>>;
71
112
  }, z.core.$strip>>;
72
113
  }, z.core.$strip>;
@@ -27,6 +27,26 @@ export type SeedRepository = {
27
27
  content: string;
28
28
  branch?: string;
29
29
  }>;
30
+ milestones?: Array<{
31
+ number?: number;
32
+ title: string;
33
+ description?: string;
34
+ state?: "open" | "closed";
35
+ due_on?: string;
36
+ }>;
37
+ tags?: Array<{
38
+ name: string;
39
+ target?: string;
40
+ }>;
41
+ releases?: Array<{
42
+ tag_name: string;
43
+ name?: string;
44
+ body?: string;
45
+ target_commitish?: string;
46
+ draft?: boolean;
47
+ prerelease?: boolean;
48
+ author?: string;
49
+ }>;
30
50
  issues?: Array<{
31
51
  number?: number;
32
52
  title: string;
@@ -34,6 +54,7 @@ export type SeedRepository = {
34
54
  state?: "open" | "closed";
35
55
  labels?: string[];
36
56
  assignees?: string[];
57
+ comments?: SeedComment[];
37
58
  }>;
38
59
  pull_requests?: Array<{
39
60
  number?: number;
@@ -53,8 +74,24 @@ export type SeedRepository = {
53
74
  state?: "error" | "failure" | "pending" | "success";
54
75
  description?: string;
55
76
  }>;
77
+ comments?: SeedComment[];
78
+ review_comments?: Array<{
79
+ body: string;
80
+ path: string;
81
+ line?: number;
82
+ side?: "LEFT" | "RIGHT";
83
+ author?: string;
84
+ }>;
56
85
  }>;
57
86
  };
87
+ /**
88
+ * A conversation comment on an issue OR a pull request — one shape, because
89
+ * they are one table and one route on GitHub too (F-1151).
90
+ */
91
+ export type SeedComment = {
92
+ body: string;
93
+ author?: string;
94
+ };
58
95
  export type RepoRow = {
59
96
  id: number;
60
97
  owner: string;
@@ -863,13 +863,61 @@ var seedSchema = z.object({
863
863
  description: z.string().default("")
864
864
  })).default([]),
865
865
  files: z.array(z.object({ path: z.string().min(1), content: z.string(), branch: z.string().optional() })).default([]),
866
+ // F-1421 — milestones, tags and releases are repository-level entities the
867
+ // twin already SERVES (`GET /milestones`, `/tags`, `/releases`,
868
+ // `/releases/latest`, `/releases/tags/:tag`) and the seed could not
869
+ // express. Zod strips unknown keys, so a seed naming one reached the
870
+ // domain as nothing at all and those routes could only ever answer `[]`
871
+ // — a shape of infidelity no shape-diff can see, because an empty array
872
+ // on both sides compares zero elements.
873
+ milestones: z.array(z.object({
874
+ // Assigned sequentially from 1 in seed order when omitted, the way
875
+ // `nextMilestoneNumber` hands them out. Honored when given, so a
876
+ // seed that pins `PATCH /milestones/2` addresses the milestone it
877
+ // named rather than one silently renumbered under it.
878
+ number: z.number().int().positive().optional(),
879
+ title: z.string().min(1),
880
+ description: z.string().default(""),
881
+ state: z.enum(["open", "closed"]).default("open"),
882
+ // GitHub's own spelling: an ISO 8601 timestamp. Absent means the
883
+ // milestone has no due date (`due_on: null` on the wire).
884
+ due_on: z.string().optional()
885
+ })).default([]),
886
+ // A tag names a commit. `target` is any ref the twin resolves — a branch
887
+ // name or a SHA — and defaults to the repository's default-branch head. A
888
+ // release whose `tag_name` matches an entry here reuses that tag rather
889
+ // than minting a second one: `createRelease` looks the tag up first.
890
+ tags: z.array(z.object({
891
+ name: z.string().min(1),
892
+ target: z.string().min(1).optional()
893
+ })).default([]),
894
+ releases: z.array(z.object({
895
+ tag_name: z.string().min(1),
896
+ // Nullable upstream, so an absent `name` means `null` — not `""`.
897
+ name: z.string().optional(),
898
+ body: z.string().default(""),
899
+ target_commitish: z.string().min(1).optional(),
900
+ draft: z.boolean().default(false),
901
+ prerelease: z.boolean().default(false),
902
+ author: z.string().min(1).optional()
903
+ })).default([]),
866
904
  issues: z.array(z.object({
867
905
  number: z.number().int().positive().optional(),
868
906
  title: z.string().min(1),
869
907
  body: z.string().default(""),
870
908
  state: z.enum(["open", "closed"]).default("open"),
871
909
  labels: z.array(z.string().min(1)).default([]),
872
- assignees: z.array(z.string().min(1)).default([])
910
+ assignees: z.array(z.string().min(1)).default([]),
911
+ // The issue's conversation timeline, served at
912
+ // `GET /repos/:o/:r/issues/:n/comments` (F-1421). `author` is
913
+ // seeded honestly rather than taken from the write path, which
914
+ // stamps every comment `pome-agent`: a world in which the agent
915
+ // under test wrote every comment on the issue it is being asked to
916
+ // read is not one worth testing against.
917
+ comments: z.array(z.object({
918
+ body: z.string().min(1),
919
+ author: z.string().min(1).optional()
920
+ })).default([])
873
921
  })).default([]),
874
922
  pull_requests: z.array(z.object({
875
923
  number: z.number().int().positive().optional(),
@@ -893,6 +941,30 @@ var seedSchema = z.object({
893
941
  context: z.string().min(1).default("ci/build"),
894
942
  state: z.enum(["error", "failure", "pending", "success"]).default("success"),
895
943
  description: z.string().default("")
944
+ })).default([]),
945
+ // F-1421 — the PR's CONVERSATION timeline. Same table, same route
946
+ // and same number space as an issue's comments, because GitHub
947
+ // models a pull request as an issue (F-1151). This is the third
948
+ // thing a reader could call "a comment on the PR" and the seed
949
+ // keeps all three apart: `reviews[].body` is the prose on a review
950
+ // verdict, `review_comments[]` below is anchored to a file and
951
+ // line, and THIS one is the timeline.
952
+ comments: z.array(z.object({
953
+ body: z.string().min(1),
954
+ author: z.string().min(1).optional()
955
+ })).default([]),
956
+ // F-1421 — inline review comments, served at
957
+ // `GET /repos/:o/:r/pulls/:n/comments`. Seeded through the domain's
958
+ // own write path, so `path` must name a file the PR changes and
959
+ // `line` must exist in it: a seeded review comment is one
960
+ // `POST /pulls/:n/comments` could have produced, not a row only the
961
+ // seeder can make.
962
+ review_comments: z.array(z.object({
963
+ body: z.string().min(1),
964
+ path: z.string().min(1),
965
+ line: z.number().int().positive().default(1),
966
+ side: z.enum(["LEFT", "RIGHT"]).default("RIGHT"),
967
+ author: z.string().min(1).optional()
896
968
  })).default([])
897
969
  })).default([])
898
970
  }))
@@ -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
- const hit = (final.reactions ?? []).some((row) => row.channel_id === found.found.id && row.name === reaction);
213
- const joined = [found.path];
214
- if (final.reactions !== void 0)
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
- evidenceStatePaths: joined
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
  });
package/dist/github.js CHANGED
@@ -1,2 +1,2 @@
1
- export { GITHUB_CHECKS, defaultSeedState, parseSeed, seedSchema } from './chunk-UCV6YLN2.js';
1
+ export { GITHUB_CHECKS, defaultSeedState, parseSeed, seedSchema } from './chunk-JVCGWAZQ.js';
2
2
  import './chunk-W2JNYULF.js';
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import './chunk-ZXE6LAM3.js';
2
- import { GITHUB_CHECKS } from './chunk-UCV6YLN2.js';
3
- export { GITHUB_CHECKS, defaultSeedState as defaultGitHubSeed, seedSchema as githubSeedSchema, parseSeed as parseGitHubSeed } from './chunk-UCV6YLN2.js';
2
+ import { GITHUB_CHECKS } from './chunk-JVCGWAZQ.js';
3
+ export { GITHUB_CHECKS, defaultSeedState as defaultGitHubSeed, seedSchema as githubSeedSchema, parseSeed as parseGitHubSeed } from './chunk-JVCGWAZQ.js';
4
4
  import { GMAIL_CHECKS } from './chunk-SA23X6PB.js';
5
5
  export { GMAIL_CHECKS, defaultSeedState as defaultGmailSeed, gmailSeedSchema, parseSeed as parseGmailSeed } from './chunk-SA23X6PB.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-5PDF3GCK.js';
9
- export { SLACK_CHECKS, defaultSeedState as defaultSlackSeed, parseSeed as parseSlackSeed, seedSchema as slackSeedSchema } from './chunk-5PDF3GCK.js';
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-5PDF3GCK.js';
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",
3
+ "version": "0.1.5",
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",