@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.
- package/dist/generated/boxxy-work-boundary.d.ts +15 -7
- package/dist/queries/types.d.ts +2 -2
- package/package.json +39 -10
- package/src/generated/boxxy-work-boundary.ts +179 -21
- package/src/queries/build-stage-time.ts +11 -4
- package/src/queries/types.ts +2 -2
- package/src/test-utils/build-foreign-stand-in.ts +139 -0
- package/src/test-utils/build-mock-applied-fact.ts +39 -0
- package/src/test-utils/build-mock-attempt-read-row.ts +17 -0
- package/src/test-utils/build-mock-binding-read-row.ts +12 -0
- package/src/test-utils/build-mock-item-facts.ts +149 -0
- package/src/test-utils/build-mock-request-read-row.ts +20 -0
- package/src/test-utils/build-mock-scope-fact.ts +13 -0
- package/src/test-utils/create-ingest-stand-in.ts +56 -0
- package/src/test-utils/create-query-stand-in.ts +51 -0
- package/src/test-utils/create-real-pepys.ts +286 -0
- package/src/test-utils/create-test-pepys.ts +228 -0
- package/src/test-utils/to-entity-id.ts +14 -0
- package/src/test-utils/to-scope-fact.ts +92 -0
|
@@ -51,7 +51,7 @@ export interface BoxxyItemFiledPayloadAcceptanceItem {
|
|
|
51
51
|
evidence: string[];
|
|
52
52
|
}
|
|
53
53
|
export interface BoxxyItemFiledPayloadFields {
|
|
54
|
-
designMode
|
|
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
|
|
63
|
-
outcome
|
|
64
|
-
acceptance
|
|
65
|
-
kind
|
|
66
|
-
fields
|
|
67
|
-
labels
|
|
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;
|
package/dist/queries/types.d.ts
CHANGED
|
@@ -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:
|
|
66
|
-
origin_subject:
|
|
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
|
+
"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.
|
|
43
|
+
"@boxxy/model": "0.1.5"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@
|
|
47
|
-
"@pepys/
|
|
48
|
-
"@pepys/
|
|
49
|
-
"@pepys/
|
|
50
|
-
"@pepys/
|
|
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
|
-
"@
|
|
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
|
|
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
|
|
139
|
-
outcome
|
|
140
|
-
acceptance
|
|
141
|
-
kind
|
|
142
|
-
fields
|
|
143
|
-
labels
|
|
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
|
|
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
|
|
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
|
-
|
|
1056
|
+
if (raw2 !== undefined && field2 === undefined) {
|
|
1057
|
+
return undefined;
|
|
1058
|
+
}
|
|
1027
1059
|
|
|
1028
|
-
const
|
|
1060
|
+
const raw3 = 'kind' in value ? value.kind : undefined;
|
|
1061
|
+
const field3 = raw3 === undefined ? undefined : typeof raw3 === 'string' ? raw3 : undefined;
|
|
1029
1062
|
|
|
1030
|
-
|
|
1063
|
+
if (raw3 !== undefined && field3 === undefined) {
|
|
1064
|
+
return undefined;
|
|
1065
|
+
}
|
|
1031
1066
|
|
|
1032
|
-
const
|
|
1067
|
+
const raw4 = 'fields' in value ? value.fields : undefined;
|
|
1068
|
+
const field4 = raw4 === undefined ? undefined : decodeBoxxyItemEditedPayloadFields(raw4);
|
|
1033
1069
|
|
|
1034
|
-
|
|
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
|
|
174
|
-
//
|
|
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(
|
|
182
|
-
|
|
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
|
});
|
package/src/queries/types.ts
CHANGED
|
@@ -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:
|
|
91
|
-
origin_subject:
|
|
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
|
+
}
|