@boxxy/pepys 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.
@@ -51,7 +51,7 @@ export interface BoxxyItemFiledPayloadAcceptanceItem {
51
51
  evidence: string[];
52
52
  }
53
53
  export interface BoxxyItemFiledPayloadFields {
54
- designMode: unknown;
54
+ designMode?: 'agent' | 'attended';
55
55
  }
56
56
  export interface BoxxyItemNotedPayload {
57
57
  seq: number;
@@ -59,15 +59,23 @@ export interface BoxxyItemNotedPayload {
59
59
  storeVersion: number;
60
60
  }
61
61
  export interface BoxxyItemEditedPayload {
62
- title: unknown;
63
- outcome: unknown;
64
- acceptance: unknown;
65
- kind: unknown;
66
- fields: unknown;
67
- labels: unknown;
62
+ title?: string;
63
+ outcome?: string;
64
+ acceptance?: BoxxyItemEditedPayloadAcceptanceItem[];
65
+ kind?: string;
66
+ fields?: BoxxyItemEditedPayloadFields;
67
+ labels?: string[];
68
68
  byAttempt: string | null;
69
69
  storeVersion: number;
70
70
  }
71
+ export interface BoxxyItemEditedPayloadAcceptanceItem {
72
+ id: string;
73
+ text: string;
74
+ evidence: string[];
75
+ }
76
+ export interface BoxxyItemEditedPayloadFields {
77
+ designMode?: 'agent' | 'attended';
78
+ }
71
79
  export interface BoxxyItemClosedPayload {
72
80
  reason: string;
73
81
  byAttempt: string | null;
@@ -62,8 +62,8 @@ interface NullableColumn<Kind extends ColumnKind> {
62
62
  }
63
63
  type TelemetryColumns = Readonly<{
64
64
  session_id: Column<'entityID'>;
65
- origin_issuer: NullableColumn<'text'>;
66
- origin_subject: NullableColumn<'text'>;
65
+ origin_issuer: Column<'text'>;
66
+ origin_subject: Column<'text'>;
67
67
  product: Column<'text'>;
68
68
  at: Column<'timestamp'>;
69
69
  }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxxy/pepys",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "The pepys plug-in of boxxy, its fact sink and its report reader",
5
5
  "homepage": "https://github.com/zgeoff/boxxy/tree/main/packages/pepys#readme",
6
6
  "bugs": "https://github.com/zgeoff/boxxy/issues",
@@ -13,7 +13,6 @@
13
13
  "files": [
14
14
  "src",
15
15
  "!src/**/*.test.ts",
16
- "!src/test-utils",
17
16
  "dist"
18
17
  ],
19
18
  "type": "module",
@@ -29,7 +28,8 @@
29
28
  "./reader": {
30
29
  "types": "./dist/create-pepys-reader.d.ts",
31
30
  "default": "./src/create-pepys-reader.ts"
32
- }
31
+ },
32
+ "./test-utils/*": "./src/test-utils/*.ts"
33
33
  },
34
34
  "publishConfig": {
35
35
  "access": "public"
@@ -40,20 +40,49 @@
40
40
  "typecheck": "tsrs --noEmit"
41
41
  },
42
42
  "dependencies": {
43
- "@boxxy/model": "0.1.3"
43
+ "@boxxy/model": "0.1.5"
44
44
  },
45
45
  "devDependencies": {
46
- "@pepys/cli": "https://github.com/zgeoff/pepys/releases/download/preview-kit-0.0.1/pepys-cli-0.0.1.tgz",
47
- "@pepys/kit": "https://github.com/zgeoff/pepys/releases/download/preview-kit-0.0.1/pepys-kit-0.0.1.tgz",
48
- "@pepys/kysely-turso": "https://github.com/zgeoff/pepys/releases/download/preview-kit-0.0.1/pepys-kysely-turso-0.0.1.tgz",
49
- "@pepys/schema": "https://github.com/zgeoff/pepys/releases/download/preview-kit-0.0.1/pepys-schema-0.0.1.tgz",
50
- "@pepys/testing": "https://github.com/zgeoff/pepys/releases/download/preview-kit-0.0.1/pepys-testing-0.0.1.tgz",
46
+ "@orpc/client": "2.0.0-beta.50",
47
+ "@pepys/api": "0.1.0",
48
+ "@pepys/cli": "0.1.0",
49
+ "@pepys/ingest": "0.1.0",
50
+ "@pepys/kit": "0.1.0",
51
+ "@pepys/source-github": "0.1.0",
52
+ "@pepys/source-telemetry": "0.1.0",
53
+ "@pepys/testing": "0.1.0",
51
54
  "zod": "4.6.7"
52
55
  },
53
56
  "peerDependencies": {
54
- "@pepys/kit": "0.0.1",
57
+ "@orpc/client": "2.0.0-beta.50",
58
+ "@pepys/api": "^0.1.0",
59
+ "@pepys/ingest": "^0.1.0",
60
+ "@pepys/kit": "^0.1.0",
61
+ "@pepys/source-github": "^0.1.0",
62
+ "@pepys/source-telemetry": "^0.1.0",
63
+ "@pepys/testing": "^0.1.0",
55
64
  "zod": "4.6.7"
56
65
  },
66
+ "peerDependenciesMeta": {
67
+ "@orpc/client": {
68
+ "optional": true
69
+ },
70
+ "@pepys/api": {
71
+ "optional": true
72
+ },
73
+ "@pepys/ingest": {
74
+ "optional": true
75
+ },
76
+ "@pepys/source-github": {
77
+ "optional": true
78
+ },
79
+ "@pepys/source-telemetry": {
80
+ "optional": true
81
+ },
82
+ "@pepys/testing": {
83
+ "optional": true
84
+ }
85
+ },
57
86
  "engines": {
58
87
  "bun": ">=1.3",
59
88
  "node": ">=24"
@@ -125,7 +125,7 @@ export interface BoxxyItemFiledPayloadAcceptanceItem {
125
125
  }
126
126
 
127
127
  export interface BoxxyItemFiledPayloadFields {
128
- designMode: unknown;
128
+ designMode?: 'agent' | 'attended';
129
129
  }
130
130
 
131
131
  export interface BoxxyItemNotedPayload {
@@ -135,16 +135,26 @@ export interface BoxxyItemNotedPayload {
135
135
  }
136
136
 
137
137
  export interface BoxxyItemEditedPayload {
138
- title: unknown;
139
- outcome: unknown;
140
- acceptance: unknown;
141
- kind: unknown;
142
- fields: unknown;
143
- labels: unknown;
138
+ title?: string;
139
+ outcome?: string;
140
+ acceptance?: BoxxyItemEditedPayloadAcceptanceItem[];
141
+ kind?: string;
142
+ fields?: BoxxyItemEditedPayloadFields;
143
+ labels?: string[];
144
144
  byAttempt: string | null;
145
145
  storeVersion: number;
146
146
  }
147
147
 
148
+ export interface BoxxyItemEditedPayloadAcceptanceItem {
149
+ id: string;
150
+ text: string;
151
+ evidence: string[];
152
+ }
153
+
154
+ export interface BoxxyItemEditedPayloadFields {
155
+ designMode?: 'agent' | 'attended';
156
+ }
157
+
148
158
  export interface BoxxyItemClosedPayload {
149
159
  reason: string;
150
160
  byAttempt: string | null;
@@ -955,10 +965,15 @@ function decodeBoxxyItemFiledPayloadFields(value: unknown): BoxxyItemFiledPayloa
955
965
  return undefined;
956
966
  }
957
967
 
958
- const field0 = 'designMode' in value ? value.designMode : undefined;
968
+ const raw0 = 'designMode' in value ? value.designMode : undefined;
969
+ const field0 = raw0 === undefined ? undefined : raw0 === 'agent' || raw0 === 'attended' ? raw0 : undefined;
970
+
971
+ if (raw0 !== undefined && field0 === undefined) {
972
+ return undefined;
973
+ }
959
974
 
960
975
  return {
961
- designMode: field0,
976
+ ...(field0 === undefined ? {} : { designMode: field0 }),
962
977
  };
963
978
  }
964
979
 
@@ -1021,17 +1036,47 @@ function decodeBoxxyItemEditedPayload(value: unknown): BoxxyItemEditedPayload |
1021
1036
  return undefined;
1022
1037
  }
1023
1038
 
1024
- const field0 = 'title' in value ? value.title : undefined;
1039
+ const raw0 = 'title' in value ? value.title : undefined;
1040
+ const field0 = raw0 === undefined ? undefined : typeof raw0 === 'string' ? raw0 : undefined;
1041
+
1042
+ if (raw0 !== undefined && field0 === undefined) {
1043
+ return undefined;
1044
+ }
1045
+
1046
+ const raw1 = 'outcome' in value ? value.outcome : undefined;
1047
+ const field1 = raw1 === undefined ? undefined : typeof raw1 === 'string' ? raw1 : undefined;
1048
+
1049
+ if (raw1 !== undefined && field1 === undefined) {
1050
+ return undefined;
1051
+ }
1052
+
1053
+ const raw2 = 'acceptance' in value ? value.acceptance : undefined;
1054
+ const field2 = raw2 === undefined ? undefined : decodeBoxxyItemEditedPayloadAcceptance(raw2);
1025
1055
 
1026
- const field1 = 'outcome' in value ? value.outcome : undefined;
1056
+ if (raw2 !== undefined && field2 === undefined) {
1057
+ return undefined;
1058
+ }
1027
1059
 
1028
- const field2 = 'acceptance' in value ? value.acceptance : undefined;
1060
+ const raw3 = 'kind' in value ? value.kind : undefined;
1061
+ const field3 = raw3 === undefined ? undefined : typeof raw3 === 'string' ? raw3 : undefined;
1029
1062
 
1030
- const field3 = 'kind' in value ? value.kind : undefined;
1063
+ if (raw3 !== undefined && field3 === undefined) {
1064
+ return undefined;
1065
+ }
1031
1066
 
1032
- const field4 = 'fields' in value ? value.fields : undefined;
1067
+ const raw4 = 'fields' in value ? value.fields : undefined;
1068
+ const field4 = raw4 === undefined ? undefined : decodeBoxxyItemEditedPayloadFields(raw4);
1033
1069
 
1034
- const field5 = 'labels' in value ? value.labels : undefined;
1070
+ if (raw4 !== undefined && field4 === undefined) {
1071
+ return undefined;
1072
+ }
1073
+
1074
+ const raw5 = 'labels' in value ? value.labels : undefined;
1075
+ const field5 = raw5 === undefined ? undefined : decodeBoxxyItemEditedPayloadLabels(raw5);
1076
+
1077
+ if (raw5 !== undefined && field5 === undefined) {
1078
+ return undefined;
1079
+ }
1035
1080
 
1036
1081
  const raw6 = 'byAttempt' in value ? value.byAttempt : undefined;
1037
1082
  const field6 = raw6 === null ? null : typeof raw6 === 'string' ? raw6 : undefined;
@@ -1048,17 +1093,130 @@ function decodeBoxxyItemEditedPayload(value: unknown): BoxxyItemEditedPayload |
1048
1093
  }
1049
1094
 
1050
1095
  return {
1051
- title: field0,
1052
- outcome: field1,
1053
- acceptance: field2,
1054
- kind: field3,
1055
- fields: field4,
1056
- labels: field5,
1096
+ ...(field0 === undefined ? {} : { title: field0 }),
1097
+ ...(field1 === undefined ? {} : { outcome: field1 }),
1098
+ ...(field2 === undefined ? {} : { acceptance: field2 }),
1099
+ ...(field3 === undefined ? {} : { kind: field3 }),
1100
+ ...(field4 === undefined ? {} : { fields: field4 }),
1101
+ ...(field5 === undefined ? {} : { labels: field5 }),
1057
1102
  byAttempt: field6,
1058
1103
  storeVersion: field7,
1059
1104
  };
1060
1105
  }
1061
1106
 
1107
+ function decodeBoxxyItemEditedPayloadAcceptance(value: unknown): BoxxyItemEditedPayloadAcceptanceItem[] | undefined {
1108
+ if (!Array.isArray(value)) {
1109
+ return undefined;
1110
+ }
1111
+
1112
+ const items: BoxxyItemEditedPayloadAcceptanceItem[] = [];
1113
+
1114
+ for (let index = 0; index < value.length; index += 1) {
1115
+ const element: unknown = value[index];
1116
+ const item = decodeBoxxyItemEditedPayloadAcceptanceItem(element);
1117
+
1118
+ if (item === undefined) {
1119
+ return undefined;
1120
+ }
1121
+
1122
+ items.push(item);
1123
+ }
1124
+
1125
+ return items;
1126
+ }
1127
+
1128
+ function decodeBoxxyItemEditedPayloadAcceptanceItem(value: unknown): BoxxyItemEditedPayloadAcceptanceItem | undefined {
1129
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
1130
+ return undefined;
1131
+ }
1132
+
1133
+ const raw0 = 'id' in value ? value.id : undefined;
1134
+ const field0 = typeof raw0 === 'string' ? raw0 : undefined;
1135
+
1136
+ if (field0 === undefined) {
1137
+ return undefined;
1138
+ }
1139
+
1140
+ const raw1 = 'text' in value ? value.text : undefined;
1141
+ const field1 = typeof raw1 === 'string' ? raw1 : undefined;
1142
+
1143
+ if (field1 === undefined) {
1144
+ return undefined;
1145
+ }
1146
+
1147
+ const raw2 = 'evidence' in value ? value.evidence : undefined;
1148
+ const field2 = decodeBoxxyItemEditedPayloadAcceptanceItemEvidence(raw2);
1149
+
1150
+ if (field2 === undefined) {
1151
+ return undefined;
1152
+ }
1153
+
1154
+ return {
1155
+ id: field0,
1156
+ text: field1,
1157
+ evidence: field2,
1158
+ };
1159
+ }
1160
+
1161
+ function decodeBoxxyItemEditedPayloadAcceptanceItemEvidence(value: unknown): string[] | undefined {
1162
+ if (!Array.isArray(value)) {
1163
+ return undefined;
1164
+ }
1165
+
1166
+ const items: string[] = [];
1167
+
1168
+ for (let index = 0; index < value.length; index += 1) {
1169
+ const element: unknown = value[index];
1170
+ const item = typeof element === 'string' ? element : undefined;
1171
+
1172
+ if (item === undefined) {
1173
+ return undefined;
1174
+ }
1175
+
1176
+ items.push(item);
1177
+ }
1178
+
1179
+ return items;
1180
+ }
1181
+
1182
+ function decodeBoxxyItemEditedPayloadFields(value: unknown): BoxxyItemEditedPayloadFields | undefined {
1183
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
1184
+ return undefined;
1185
+ }
1186
+
1187
+ const raw0 = 'designMode' in value ? value.designMode : undefined;
1188
+ const field0 = raw0 === undefined ? undefined : raw0 === 'agent' || raw0 === 'attended' ? raw0 : undefined;
1189
+
1190
+ if (raw0 !== undefined && field0 === undefined) {
1191
+ return undefined;
1192
+ }
1193
+
1194
+ return {
1195
+ ...(field0 === undefined ? {} : { designMode: field0 }),
1196
+ };
1197
+ }
1198
+
1199
+ function decodeBoxxyItemEditedPayloadLabels(value: unknown): string[] | undefined {
1200
+ if (!Array.isArray(value)) {
1201
+ return undefined;
1202
+ }
1203
+
1204
+ const items: string[] = [];
1205
+
1206
+ for (let index = 0; index < value.length; index += 1) {
1207
+ const element: unknown = value[index];
1208
+ const item = typeof element === 'string' ? element : undefined;
1209
+
1210
+ if (item === undefined) {
1211
+ return undefined;
1212
+ }
1213
+
1214
+ items.push(item);
1215
+ }
1216
+
1217
+ return items;
1218
+ }
1219
+
1062
1220
  function decodeBoxxyItemClosedPayload(value: unknown): BoxxyItemClosedPayload | undefined {
1063
1221
  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
1064
1222
  return undefined;
@@ -170,16 +170,23 @@ interface SettledAttempt extends MeasuredAttempt {
170
170
  readonly ended_at: string;
171
171
  }
172
172
 
173
- // For each settled build that was built: the claim of the next review attempt, by number, less
174
- // the build's end. null when no build has a review after it.
173
+ // For each settled build that was built: the claim of the review claimed next at or after the
174
+ // build's end, less that end. An attempt's number counts within its phase, so it cannot order a
175
+ // build against a review. null when no build has a review after it.
175
176
  function measureReviewWait(attempts: readonly MeasuredAttempt[]): number | null {
176
177
  const waits = attempts
177
178
  .filter((attempt): attempt is SettledAttempt => attempt.ended_at !== null)
178
179
  .filter((build) => build.phase === 'build' && build.outcome === outcomeNames.built)
179
180
  .flatMap((build) => {
180
181
  const [review] = attempts
181
- .filter((attempt) => attempt.phase === 'review' && attempt.number > build.number)
182
- .toSorted((left, right) => left.number - right.number);
182
+ .filter(
183
+ (attempt) =>
184
+ attempt.phase === 'review' && measureMinutes(build.ended_at, attempt.claimed_at) >= 0,
185
+ )
186
+ .toSorted(
187
+ (left, right) =>
188
+ measureMinutes(right.claimed_at, left.claimed_at) || left.id.localeCompare(right.id),
189
+ );
183
190
 
184
191
  return review === undefined ? [] : [measureMinutes(build.ended_at, review.claimed_at)];
185
192
  });
@@ -87,8 +87,8 @@ interface NullableColumn<Kind extends ColumnKind> {
87
87
  // columns fits. A table's columns take a type alias: an interface has no index signature.
88
88
  type TelemetryColumns = Readonly<{
89
89
  session_id: Column<'entityID'>;
90
- origin_issuer: NullableColumn<'text'>;
91
- origin_subject: NullableColumn<'text'>;
90
+ origin_issuer: Column<'text'>;
91
+ origin_subject: Column<'text'>;
92
92
  product: Column<'text'>;
93
93
  at: Column<'timestamp'>;
94
94
  }>;
@@ -0,0 +1,139 @@
1
+ import {
2
+ defineConsumer,
3
+ defineFactFamily,
4
+ definePlugin,
5
+ defineSource,
6
+ defineTable,
7
+ } from '@pepys/kit';
8
+ import type { DeliveryReadRow, ForeignReads } from '../queries/types.ts';
9
+
10
+ // No fact reaches a stand-in consumer: a test writes its rows. Its entry is this file, which
11
+ // exports no `applyFact`, so a fact sent to one fails the apply.
12
+ const entry = new URL(import.meta.url);
13
+
14
+ const logRecordFamily = defineFactFamily({
15
+ stream: 'facts.telemetry.log_record',
16
+ api: 'otlp-logs/1',
17
+ entityKind: 'telemetry:session',
18
+ types: {},
19
+ });
20
+
21
+ const pullRequestFamily = defineFactFamily({
22
+ stream: 'facts.github.pull_request',
23
+ api: 'github-webhook/1',
24
+ entityKind: 'github:pull_request',
25
+ types: {},
26
+ });
27
+
28
+ const telemetryColumns = {
29
+ id: { kind: 'entityID' },
30
+ session_id: { kind: 'entityID' },
31
+ origin_issuer: { kind: 'text' },
32
+ origin_subject: { kind: 'text' },
33
+ product: { kind: 'text' },
34
+ at: { kind: 'timestamp' },
35
+ } as const;
36
+
37
+ const telemetryModelRequestConsumer = defineConsumer({
38
+ name: 'telemetry_model_request',
39
+ entry,
40
+ input: { mode: 'fact', streams: [logRecordFamily.stream] },
41
+ tables: {
42
+ telemetry_model_request: defineTable({
43
+ columns: {
44
+ ...telemetryColumns,
45
+ model: { kind: 'text' },
46
+ cost_basis: { kind: 'text' },
47
+ cost_usd: { kind: 'real', nullable: true },
48
+ input_tokens: { kind: 'integer' },
49
+ cache_read_tokens: { kind: 'integer' },
50
+ cache_creation_tokens: { kind: 'integer' },
51
+ output_tokens: { kind: 'integer' },
52
+ },
53
+ primaryKey: ['id'],
54
+ indexes: [{ columns: ['session_id'] }],
55
+ }),
56
+ },
57
+ entityViews: [],
58
+ reads: [],
59
+ links: [],
60
+ });
61
+
62
+ const telemetryTurnCostConsumer = defineConsumer({
63
+ name: 'telemetry_turn_cost',
64
+ entry,
65
+ input: { mode: 'fact', streams: [logRecordFamily.stream] },
66
+ tables: {
67
+ telemetry_turn_cost: defineTable({
68
+ columns: {
69
+ ...telemetryColumns,
70
+ model: { kind: 'text', nullable: true },
71
+ cost_usd: { kind: 'real' },
72
+ },
73
+ primaryKey: ['id'],
74
+ indexes: [{ columns: ['session_id'] }],
75
+ }),
76
+ },
77
+ entityViews: [],
78
+ reads: [],
79
+ links: [],
80
+ });
81
+
82
+ const githubPullRequestConsumer = defineConsumer({
83
+ name: 'github_pull_request',
84
+ entry,
85
+ input: { mode: 'fact', streams: [pullRequestFamily.stream] },
86
+ tables: {
87
+ github_pull_request: defineTable({
88
+ columns: {
89
+ id: { kind: 'entityID' },
90
+ merged_at: { kind: 'timestamp', nullable: true },
91
+ },
92
+ primaryKey: ['id'],
93
+ indexes: [],
94
+ }),
95
+ },
96
+ entityViews: [],
97
+ reads: ['github_pull_request'],
98
+ links: [],
99
+ });
100
+
101
+ // The telemetry and GitHub plug-ins as far as the queries read them: the three consumers with the
102
+ // columns the queries name, and the GitHub delivery read as rows the test sets per item.
103
+ export function buildForeignStandIn() {
104
+ const deliveries = new Map<string, readonly DeliveryReadRow[]>();
105
+
106
+ const foreign: ForeignReads = {
107
+ telemetryModelRequest: telemetryModelRequestConsumer,
108
+ telemetryTurnCost: telemetryTurnCostConsumer,
109
+ githubPullRequest: githubPullRequestConsumer,
110
+ delivery: {
111
+ consumers: [githubPullRequestConsumer],
112
+ readDeliveries: (_db, input) => Promise.resolve(deliveries.get(input.item) ?? []),
113
+ },
114
+ };
115
+
116
+ return {
117
+ foreign,
118
+ plugins: [
119
+ definePlugin({
120
+ name: 'telemetry',
121
+ sources: [defineSource({ name: 'telemetry', families: [logRecordFamily] })],
122
+ consumers: [telemetryModelRequestConsumer, telemetryTurnCostConsumer],
123
+ }),
124
+ definePlugin({
125
+ name: 'github',
126
+ sources: [defineSource({ name: 'github', families: [pullRequestFamily] })],
127
+ consumers: [githubPullRequestConsumer],
128
+ }),
129
+ ],
130
+ consumers: {
131
+ telemetryModelRequest: telemetryModelRequestConsumer,
132
+ telemetryTurnCost: telemetryTurnCostConsumer,
133
+ githubPullRequest: githubPullRequestConsumer,
134
+ },
135
+ setDeliveries: (item: string, rows: readonly DeliveryReadRow[]) => {
136
+ deliveries.set(item, rows);
137
+ },
138
+ };
139
+ }
@@ -0,0 +1,39 @@
1
+ import type { EntityRef } from '@boxxy/model';
2
+ import { buildMockWorkFact } from '@boxxy/model/test-utils/build-mock-work-fact';
3
+ import type { MockedFactType } from '@boxxy/model/test-utils/build-mock-work-fact';
4
+ import type { OutgoingFact } from '@pepys/kit';
5
+ import type { boxxySource } from '../boxxy-source.ts';
6
+ import { isOutgoingFact } from '../sink/is-outgoing-fact.ts';
7
+ import { toWireFact } from '../sink/to-wire-fact.ts';
8
+
9
+ interface AppliedFactOverrides {
10
+ readonly payload?: Readonly<Record<string, unknown>>;
11
+
12
+ // ISO 8601, as boxxy's sink writes it
13
+ readonly at?: string;
14
+ readonly entity?: EntityRef;
15
+ readonly event?: string;
16
+ }
17
+
18
+ // One fact of a batch as boxxy's sink sends it: a model fact whose payload its schema accepts, in
19
+ // the sink's wire form. Ingest gives it the fact ID `event:boxxy:<event>`.
20
+ export function buildMockAppliedFact(
21
+ type: MockedFactType,
22
+ overrides: AppliedFactOverrides = {},
23
+ ): OutgoingFact<typeof boxxySource> {
24
+ const wire = toWireFact(
25
+ buildMockWorkFact(type, {
26
+ ...(overrides.payload === undefined ? {} : { payload: overrides.payload }),
27
+ ...(overrides.entity === undefined ? {} : { entity: overrides.entity }),
28
+ at: Date.parse(overrides.at ?? '2026-11-11T02:00:00.000Z'),
29
+ }),
30
+ );
31
+
32
+ const fact = { ...wire, event: overrides.event ?? wire.event };
33
+
34
+ if (!isOutgoingFact(fact)) {
35
+ throw new Error(`a mock ${type} fact fails the schema of the boxxy fact family`);
36
+ }
37
+
38
+ return fact;
39
+ }
@@ -0,0 +1,17 @@
1
+ import type { AttemptReadRow } from '../queries/types.ts';
2
+
3
+ export function buildMockAttemptReadRow(overrides: Partial<AttemptReadRow> = {}): AttemptReadRow {
4
+ return {
5
+ id: 'boxxy:attempt:0193a1b2-0000-7000-8000-0000000000a1',
6
+ phase: 'build',
7
+ number: 1,
8
+ round: 1,
9
+ state: 'succeeded',
10
+ outcome: null,
11
+ failure: null,
12
+ claimed_at: '2026-11-11T02:00:00.000Z',
13
+ ended_at: null,
14
+ sleeps: [],
15
+ ...overrides,
16
+ };
17
+ }
@@ -0,0 +1,12 @@
1
+ import type { BindingReadRow } from '../queries/types.ts';
2
+
3
+ export function buildMockBindingReadRow(overrides: Partial<BindingReadRow> = {}): BindingReadRow {
4
+ return {
5
+ attempt_id: 'boxxy:attempt:0193a1b2-0000-7000-8000-0000000000a1',
6
+ session_id: 'telemetry:session:s-build',
7
+ bound_at: '2026-11-11T02:00:05.000Z',
8
+ caller_issuer: 'https://broker.example',
9
+ caller_subject: 'imp-builder',
10
+ ...overrides,
11
+ };
12
+ }