@eco-foundation/api-schemas 0.12.0 → 0.14.0
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/README.md +98 -13
- package/dist/fixtures/v1/routes-list.response.default.json +36 -9
- package/dist/generated/build-digest.json +1 -1
- package/dist/v1/endpoints/registry.d.ts +32 -9
- package/dist/v1/endpoints/registry.js +3 -1
- package/dist/v1/endpoints/registry.js.map +1 -1
- package/dist/v1/endpoints/routes.d.ts +106 -16
- package/dist/v1/endpoints/routes.js +34 -8
- package/dist/v1/endpoints/routes.js.map +1 -1
- package/dist/v1/errors/catalog.d.ts +7 -0
- package/dist/v1/errors/catalog.js +1 -0
- package/dist/v1/errors/catalog.js.map +1 -1
- package/dist/v1/index.d.ts +1 -0
- package/dist/v1/index.js +1 -0
- package/dist/v1/index.js.map +1 -1
- package/dist/v1/maintenance/schema.d.ts +30 -0
- package/dist/v1/maintenance/schema.js +65 -0
- package/dist/v1/maintenance/schema.js.map +1 -0
- package/errors/catalog.lock.json +5 -0
- package/fixtures/maintenance-boundaries.json +405 -0
- package/openapi/v1.json +198 -62
- package/package.json +2 -2
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MaintenanceConfigSchema = exports.MaintenanceWindowSchema = exports.MAINTENANCE_MESSAGE_MAX_LENGTH = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const ISO_UTC_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
|
|
6
|
+
function isRealUtcInstant(value) {
|
|
7
|
+
const ms = Date.parse(value);
|
|
8
|
+
if (!Number.isFinite(ms))
|
|
9
|
+
return false;
|
|
10
|
+
return new Date(ms).toISOString().slice(0, 19) === value.slice(0, 19);
|
|
11
|
+
}
|
|
12
|
+
function utcMillis(value) {
|
|
13
|
+
return ISO_UTC_PATTERN.test(value) && isRealUtcInstant(value) ? Date.parse(value) : undefined;
|
|
14
|
+
}
|
|
15
|
+
const IsoUtcTimestamp = zod_1.z
|
|
16
|
+
.string({
|
|
17
|
+
error: 'must be an ISO-8601 UTC timestamp string; quote it in YAML',
|
|
18
|
+
})
|
|
19
|
+
.regex(ISO_UTC_PATTERN, 'must be an ISO-8601 UTC timestamp ending in Z, e.g. 2026-10-04T02:00:00Z')
|
|
20
|
+
.refine((value) => !ISO_UTC_PATTERN.test(value) || utcMillis(value) !== undefined, 'is not a real calendar instant');
|
|
21
|
+
exports.MAINTENANCE_MESSAGE_MAX_LENGTH = 200;
|
|
22
|
+
function rejectUnknownKeys(allowedKeys) {
|
|
23
|
+
const allowed = new Set(allowedKeys);
|
|
24
|
+
return (raw, ctx) => {
|
|
25
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
26
|
+
return raw;
|
|
27
|
+
const extra = Object.keys(raw).filter((key) => !allowed.has(key));
|
|
28
|
+
if (extra.length === 0)
|
|
29
|
+
return raw;
|
|
30
|
+
const quoted = extra.map((key) => `"${key}"`).join(', ');
|
|
31
|
+
ctx.addIssue({
|
|
32
|
+
code: 'custom',
|
|
33
|
+
message: extra.length === 1 ? `Unrecognized key: ${quoted}` : `Unrecognized keys: ${quoted}`,
|
|
34
|
+
});
|
|
35
|
+
return raw;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const MaintenanceWindowShape = zod_1.z
|
|
39
|
+
.object({
|
|
40
|
+
startsAt: IsoUtcTimestamp,
|
|
41
|
+
endsAt: IsoUtcTimestamp,
|
|
42
|
+
message: zod_1.z
|
|
43
|
+
.string()
|
|
44
|
+
.max(exports.MAINTENANCE_MESSAGE_MAX_LENGTH)
|
|
45
|
+
.refine((value) => value.trim().length > 0, 'must not be blank'),
|
|
46
|
+
})
|
|
47
|
+
.superRefine((window, ctx) => {
|
|
48
|
+
const startsMs = utcMillis(window.startsAt);
|
|
49
|
+
const endsMs = utcMillis(window.endsAt);
|
|
50
|
+
if (startsMs === undefined || endsMs === undefined)
|
|
51
|
+
return;
|
|
52
|
+
if (endsMs <= startsMs) {
|
|
53
|
+
ctx.addIssue({
|
|
54
|
+
code: 'custom',
|
|
55
|
+
path: ['endsAt'],
|
|
56
|
+
message: `endsAt ${window.endsAt} must be after startsAt ${window.startsAt}`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
exports.MaintenanceWindowSchema = zod_1.z.preprocess(rejectUnknownKeys(['startsAt', 'endsAt', 'message']), MaintenanceWindowShape);
|
|
61
|
+
const MaintenanceConfigShape = zod_1.z.object({
|
|
62
|
+
windows: zod_1.z.array(exports.MaintenanceWindowSchema).default([]),
|
|
63
|
+
});
|
|
64
|
+
exports.MaintenanceConfigSchema = zod_1.z.preprocess(rejectUnknownKeys(['windows']), MaintenanceConfigShape);
|
|
65
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/v1/maintenance/schema.ts"],"names":[],"mappings":"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"}
|
package/errors/catalog.lock.json
CHANGED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "empty window list",
|
|
4
|
+
"windows": [],
|
|
5
|
+
"nowIso": "2026-10-04T03:00:00.000Z",
|
|
6
|
+
"expected": {
|
|
7
|
+
"active": false
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "one millisecond before startsAt",
|
|
12
|
+
"windows": [
|
|
13
|
+
{
|
|
14
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
15
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
16
|
+
"message": "Scheduled maintenance: database upgrade"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"nowIso": "2026-10-04T01:59:59.999Z",
|
|
20
|
+
"expected": {
|
|
21
|
+
"active": false
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "exactly startsAt (start is inclusive)",
|
|
26
|
+
"windows": [
|
|
27
|
+
{
|
|
28
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
29
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
30
|
+
"message": "Scheduled maintenance: database upgrade"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"nowIso": "2026-10-04T02:00:00.000Z",
|
|
34
|
+
"expected": {
|
|
35
|
+
"active": true,
|
|
36
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
37
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
38
|
+
"message": "Scheduled maintenance: database upgrade",
|
|
39
|
+
"retryAfterSeconds": 7200
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "mid-window",
|
|
44
|
+
"windows": [
|
|
45
|
+
{
|
|
46
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
47
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
48
|
+
"message": "Scheduled maintenance: database upgrade"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"nowIso": "2026-10-04T03:00:00.000Z",
|
|
52
|
+
"expected": {
|
|
53
|
+
"active": true,
|
|
54
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
55
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
56
|
+
"message": "Scheduled maintenance: database upgrade",
|
|
57
|
+
"retryAfterSeconds": 3600
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "one millisecond before endsAt",
|
|
62
|
+
"windows": [
|
|
63
|
+
{
|
|
64
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
65
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
66
|
+
"message": "Scheduled maintenance: database upgrade"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"nowIso": "2026-10-04T03:59:59.999Z",
|
|
70
|
+
"expected": {
|
|
71
|
+
"active": true,
|
|
72
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
73
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
74
|
+
"message": "Scheduled maintenance: database upgrade",
|
|
75
|
+
"retryAfterSeconds": 1
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "exactly endsAt (end is exclusive)",
|
|
80
|
+
"windows": [
|
|
81
|
+
{
|
|
82
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
83
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
84
|
+
"message": "Scheduled maintenance: database upgrade"
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"nowIso": "2026-10-04T04:00:00.000Z",
|
|
88
|
+
"expected": {
|
|
89
|
+
"active": false
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "fractional remainder rounds up: 1.5 s before endsAt",
|
|
94
|
+
"windows": [
|
|
95
|
+
{
|
|
96
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
97
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
98
|
+
"message": "Scheduled maintenance: database upgrade"
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
"nowIso": "2026-10-04T03:59:58.500Z",
|
|
102
|
+
"expected": {
|
|
103
|
+
"active": true,
|
|
104
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
105
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
106
|
+
"message": "Scheduled maintenance: database upgrade",
|
|
107
|
+
"retryAfterSeconds": 2
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "whole-second remainder does not round up: 2 s before endsAt",
|
|
112
|
+
"windows": [
|
|
113
|
+
{
|
|
114
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
115
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
116
|
+
"message": "Scheduled maintenance: database upgrade"
|
|
117
|
+
}
|
|
118
|
+
],
|
|
119
|
+
"nowIso": "2026-10-04T03:59:58.000Z",
|
|
120
|
+
"expected": {
|
|
121
|
+
"active": true,
|
|
122
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
123
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
124
|
+
"message": "Scheduled maintenance: database upgrade",
|
|
125
|
+
"retryAfterSeconds": 2
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"name": "past-only window",
|
|
130
|
+
"windows": [
|
|
131
|
+
{
|
|
132
|
+
"startsAt": "2026-10-01T02:00:00Z",
|
|
133
|
+
"endsAt": "2026-10-01T04:00:00Z",
|
|
134
|
+
"message": "Last week's maintenance"
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
"nowIso": "2026-10-04T03:00:00.000Z",
|
|
138
|
+
"expected": {
|
|
139
|
+
"active": false
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"name": "future-only window",
|
|
144
|
+
"windows": [
|
|
145
|
+
{
|
|
146
|
+
"startsAt": "2026-10-11T02:00:00Z",
|
|
147
|
+
"endsAt": "2026-10-11T04:00:00Z",
|
|
148
|
+
"message": "Next week's maintenance"
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
"nowIso": "2026-10-04T03:00:00.000Z",
|
|
152
|
+
"expected": {
|
|
153
|
+
"active": false
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"name": "past and future windows, now between them",
|
|
158
|
+
"windows": [
|
|
159
|
+
{
|
|
160
|
+
"startsAt": "2026-10-01T02:00:00Z",
|
|
161
|
+
"endsAt": "2026-10-01T04:00:00Z",
|
|
162
|
+
"message": "Last week's maintenance"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"startsAt": "2026-10-11T02:00:00Z",
|
|
166
|
+
"endsAt": "2026-10-11T04:00:00Z",
|
|
167
|
+
"message": "Next week's maintenance"
|
|
168
|
+
}
|
|
169
|
+
],
|
|
170
|
+
"nowIso": "2026-10-04T03:00:00.000Z",
|
|
171
|
+
"expected": {
|
|
172
|
+
"active": false
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"name": "overlapping windows, only the first active",
|
|
177
|
+
"windows": [
|
|
178
|
+
{
|
|
179
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
180
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
181
|
+
"message": "A: database upgrade"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"startsAt": "2026-10-04T03:00:00Z",
|
|
185
|
+
"endsAt": "2026-10-04T05:00:00Z",
|
|
186
|
+
"message": "B: key rotation"
|
|
187
|
+
}
|
|
188
|
+
],
|
|
189
|
+
"nowIso": "2026-10-04T02:30:00.000Z",
|
|
190
|
+
"expected": {
|
|
191
|
+
"active": true,
|
|
192
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
193
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
194
|
+
"message": "A: database upgrade",
|
|
195
|
+
"retryAfterSeconds": 5400
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"name": "overlapping windows, both active: latest endsAt wins",
|
|
200
|
+
"windows": [
|
|
201
|
+
{
|
|
202
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
203
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
204
|
+
"message": "A: database upgrade"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"startsAt": "2026-10-04T03:00:00Z",
|
|
208
|
+
"endsAt": "2026-10-04T05:00:00Z",
|
|
209
|
+
"message": "B: key rotation"
|
|
210
|
+
}
|
|
211
|
+
],
|
|
212
|
+
"nowIso": "2026-10-04T03:30:00.000Z",
|
|
213
|
+
"expected": {
|
|
214
|
+
"active": true,
|
|
215
|
+
"startsAt": "2026-10-04T03:00:00Z",
|
|
216
|
+
"endsAt": "2026-10-04T05:00:00Z",
|
|
217
|
+
"message": "B: key rotation",
|
|
218
|
+
"retryAfterSeconds": 5400
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"name": "overlapping windows, both active, list reversed: latest endsAt still wins",
|
|
223
|
+
"windows": [
|
|
224
|
+
{
|
|
225
|
+
"startsAt": "2026-10-04T03:00:00Z",
|
|
226
|
+
"endsAt": "2026-10-04T05:00:00Z",
|
|
227
|
+
"message": "B: key rotation"
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
231
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
232
|
+
"message": "A: database upgrade"
|
|
233
|
+
}
|
|
234
|
+
],
|
|
235
|
+
"nowIso": "2026-10-04T03:30:00.000Z",
|
|
236
|
+
"expected": {
|
|
237
|
+
"active": true,
|
|
238
|
+
"startsAt": "2026-10-04T03:00:00Z",
|
|
239
|
+
"endsAt": "2026-10-04T05:00:00Z",
|
|
240
|
+
"message": "B: key rotation",
|
|
241
|
+
"retryAfterSeconds": 5400
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"name": "overlapping windows, after the first ends",
|
|
246
|
+
"windows": [
|
|
247
|
+
{
|
|
248
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
249
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
250
|
+
"message": "A: database upgrade"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"startsAt": "2026-10-04T03:00:00Z",
|
|
254
|
+
"endsAt": "2026-10-04T05:00:00Z",
|
|
255
|
+
"message": "B: key rotation"
|
|
256
|
+
}
|
|
257
|
+
],
|
|
258
|
+
"nowIso": "2026-10-04T04:00:00.000Z",
|
|
259
|
+
"expected": {
|
|
260
|
+
"active": true,
|
|
261
|
+
"startsAt": "2026-10-04T03:00:00Z",
|
|
262
|
+
"endsAt": "2026-10-04T05:00:00Z",
|
|
263
|
+
"message": "B: key rotation",
|
|
264
|
+
"retryAfterSeconds": 3600
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"name": "overlapping windows with equal endsAt: first in list wins",
|
|
269
|
+
"windows": [
|
|
270
|
+
{
|
|
271
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
272
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
273
|
+
"message": "A: database upgrade"
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"startsAt": "2026-10-04T03:00:00Z",
|
|
277
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
278
|
+
"message": "B: key rotation"
|
|
279
|
+
}
|
|
280
|
+
],
|
|
281
|
+
"nowIso": "2026-10-04T03:30:00.000Z",
|
|
282
|
+
"expected": {
|
|
283
|
+
"active": true,
|
|
284
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
285
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
286
|
+
"message": "A: database upgrade",
|
|
287
|
+
"retryAfterSeconds": 1800
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"name": "overlapping windows with equal endsAt, list reversed: first in list wins",
|
|
292
|
+
"windows": [
|
|
293
|
+
{
|
|
294
|
+
"startsAt": "2026-10-04T03:00:00Z",
|
|
295
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
296
|
+
"message": "B: key rotation"
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
300
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
301
|
+
"message": "A: database upgrade"
|
|
302
|
+
}
|
|
303
|
+
],
|
|
304
|
+
"nowIso": "2026-10-04T03:30:00.000Z",
|
|
305
|
+
"expected": {
|
|
306
|
+
"active": true,
|
|
307
|
+
"startsAt": "2026-10-04T03:00:00Z",
|
|
308
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
309
|
+
"message": "B: key rotation",
|
|
310
|
+
"retryAfterSeconds": 1800
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"name": "adjacent windows, one millisecond before the handoff",
|
|
315
|
+
"windows": [
|
|
316
|
+
{
|
|
317
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
318
|
+
"endsAt": "2026-10-04T03:00:00Z",
|
|
319
|
+
"message": "A: database upgrade"
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
"startsAt": "2026-10-04T03:00:00Z",
|
|
323
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
324
|
+
"message": "B: key rotation"
|
|
325
|
+
}
|
|
326
|
+
],
|
|
327
|
+
"nowIso": "2026-10-04T02:59:59.999Z",
|
|
328
|
+
"expected": {
|
|
329
|
+
"active": true,
|
|
330
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
331
|
+
"endsAt": "2026-10-04T03:00:00Z",
|
|
332
|
+
"message": "A: database upgrade",
|
|
333
|
+
"retryAfterSeconds": 1
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
"name": "adjacent windows at the handoff instant: the second window",
|
|
338
|
+
"windows": [
|
|
339
|
+
{
|
|
340
|
+
"startsAt": "2026-10-04T02:00:00Z",
|
|
341
|
+
"endsAt": "2026-10-04T03:00:00Z",
|
|
342
|
+
"message": "A: database upgrade"
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"startsAt": "2026-10-04T03:00:00Z",
|
|
346
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
347
|
+
"message": "B: key rotation"
|
|
348
|
+
}
|
|
349
|
+
],
|
|
350
|
+
"nowIso": "2026-10-04T03:00:00.000Z",
|
|
351
|
+
"expected": {
|
|
352
|
+
"active": true,
|
|
353
|
+
"startsAt": "2026-10-04T03:00:00Z",
|
|
354
|
+
"endsAt": "2026-10-04T04:00:00Z",
|
|
355
|
+
"message": "B: key rotation",
|
|
356
|
+
"retryAfterSeconds": 3600
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
"name": "sub-second bounds, one millisecond before startsAt",
|
|
361
|
+
"windows": [
|
|
362
|
+
{
|
|
363
|
+
"startsAt": "2026-10-04T02:00:00.250Z",
|
|
364
|
+
"endsAt": "2026-10-04T02:00:01.750Z",
|
|
365
|
+
"message": "Sub-second window"
|
|
366
|
+
}
|
|
367
|
+
],
|
|
368
|
+
"nowIso": "2026-10-04T02:00:00.249Z",
|
|
369
|
+
"expected": {
|
|
370
|
+
"active": false
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
"name": "sub-second bounds, exactly startsAt",
|
|
375
|
+
"windows": [
|
|
376
|
+
{
|
|
377
|
+
"startsAt": "2026-10-04T02:00:00.250Z",
|
|
378
|
+
"endsAt": "2026-10-04T02:00:01.750Z",
|
|
379
|
+
"message": "Sub-second window"
|
|
380
|
+
}
|
|
381
|
+
],
|
|
382
|
+
"nowIso": "2026-10-04T02:00:00.250Z",
|
|
383
|
+
"expected": {
|
|
384
|
+
"active": true,
|
|
385
|
+
"startsAt": "2026-10-04T02:00:00.250Z",
|
|
386
|
+
"endsAt": "2026-10-04T02:00:01.750Z",
|
|
387
|
+
"message": "Sub-second window",
|
|
388
|
+
"retryAfterSeconds": 2
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
"name": "sub-second bounds, exactly endsAt",
|
|
393
|
+
"windows": [
|
|
394
|
+
{
|
|
395
|
+
"startsAt": "2026-10-04T02:00:00.250Z",
|
|
396
|
+
"endsAt": "2026-10-04T02:00:01.750Z",
|
|
397
|
+
"message": "Sub-second window"
|
|
398
|
+
}
|
|
399
|
+
],
|
|
400
|
+
"nowIso": "2026-10-04T02:00:01.750Z",
|
|
401
|
+
"expected": {
|
|
402
|
+
"active": false
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
]
|