@boxxy/model 0.1.9 → 0.2.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.
@@ -87,7 +87,9 @@ export interface WorkFacts {
87
87
  readonly skillsRef: CommitSha | null;
88
88
  readonly rulesVersion: RulesVersion;
89
89
  };
90
- 'boxxy.decision.recorded': DecisionRow;
90
+ 'boxxy.decision.recorded': Omit<DecisionRow, 'storeVersion'> & {
91
+ readonly snapshotStoreVersion: number;
92
+ };
91
93
  'boxxy.attempt.started': OfAttempt & {
92
94
  readonly atcSession: AtcSessionId;
93
95
  readonly resumeFallback: boolean;
@@ -99,7 +99,6 @@ export declare const workFactSchemas: {
99
99
  pool: z.core.$ZodBranded<z.ZodString, "PoolName", "out">;
100
100
  rulesVersion: z.core.$ZodBranded<z.core.$ZodBranded<z.ZodString, "ContentHash", "out">, "RulesVersion", "out">;
101
101
  snapshotHash: z.core.$ZodBranded<z.core.$ZodBranded<z.ZodString, "ContentHash", "out">, "SnapshotHash", "out">;
102
- storeVersion: z.ZodNumber;
103
102
  pepysOffsets: z.ZodRecord<z.core.$ZodBranded<z.ZodString, "StreamName", "out">, z.ZodNumber>;
104
103
  faults: z.ZodArray<z.ZodObject<{
105
104
  plugin: z.ZodString;
@@ -120,6 +119,7 @@ export declare const workFactSchemas: {
120
119
  max: z.ZodNumber;
121
120
  }, z.core.$strip>>;
122
121
  at: z.core.$ZodBranded<z.ZodNumber, "Instant", "out">;
122
+ snapshotStoreVersion: z.ZodNumber;
123
123
  }, z.core.$strip>;
124
124
  'boxxy.attempt.started': z.ZodObject<{
125
125
  attempt: z.core.$ZodBranded<z.ZodString, "AttemptId", "out">;
package/dist/index.d.ts CHANGED
@@ -58,7 +58,7 @@ export type { AcceptanceInput, BuildCloseOut, BuildInput, CloseOut, CloseOutAnsw
58
58
  export type { Claim } from './dispatch/types.ts';
59
59
  export type { AnyNewFact, EntityRef, FactEnvelope, FactType, NewFact, OutboxFact, RelationPayload, WorkFacts, } from './facts/types.ts';
60
60
  export type { Admission, AdmissionGrant, AdmissionName, Decision, Fault, Filter, Frame, FrameItem, FrameValues, Gate, GateName, Hold, Lift, Limit, LimitFill, Pick, PlanContext, Pool, PropagationLine, SeatRef, SeatRelease, SeatTake, Signal, SignalKey, SignalName, Snapshot, StartPick, View, ViewName, Wait, WaitReason, Waiver, WaiverName, Wake, WakePick, } from './planning/types.ts';
61
- export type { ApplyResult, Attention, CallerProof, CapacityBackoff, ClaimStore, Clock, FactSink, ForgetOutcome, Forge, GitSource, HoldNotice, ItemDetailRows, ItemFilter, NewId, NoticeOutcome, Notifier, OutboxReader, PepysAnswer, PepysReader, SinkAnswer, PullRequestReading, RowWrite, RunnerFault, SessionFault, SessionReading, SpawnRequest, StartOutcome, StopOutcome, StoreChange, StoreReader, StoreTx, SuspendOutcome, TableName, Tables, WakeSource, WorkerKind, WorkerRefusal, } from './ports/types.ts';
61
+ export type { ApplyResult, Attention, CallerProof, CapacityBackoff, ClaimStore, Clock, FactSink, ForgetOutcome, Forge, GitSource, HoldNotice, ItemDetailRows, ItemFilter, NewId, NoticeOutcome, NoticeTryUpdate, Notifier, OutboxReader, PepysAnswer, PepysReader, SinkAnswer, PullRequestReading, RowWrite, RunnerFault, SessionFault, SessionReading, SpawnRequest, StartOutcome, StopOutcome, StoreChange, StoreReader, StoreTx, SuspendOutcome, TableName, Tables, WakeSource, WorkerKind, WorkerRefusal, } from './ports/types.ts';
62
62
  export type { CostToLand, StageTime } from './reports/types.ts';
63
63
  export type { OutboxStatus } from './reports/outbox-status.ts';
64
64
  export type { ItemSetRows, ItemWithLevers } from './items/types.ts';
@@ -32,6 +32,7 @@ export interface StoreReader extends OutboxReader {
32
32
  listLimitSeats: () => Promise<LimitSeatRow[]>;
33
33
  listItemNotes: (item: ItemId) => Promise<NoteRow[]>;
34
34
  listUnsentNotices: (limit: number) => Promise<NoticeRow[]>;
35
+ listNoticesDue: (now: Instant, limit: number) => Promise<NoticeRow[]>;
35
36
  listKeptSessions: () => Promise<{
36
37
  readonly item: ItemId;
37
38
  readonly kept: KeptSession;
@@ -67,7 +68,7 @@ export interface StoreTx extends StoreReader {
67
68
  writeHeartbeat: (runner: RunnerId, at: Instant) => Promise<void>;
68
69
  syncLimits: (limits: readonly Limit[], at: Instant) => Promise<void>;
69
70
  markPullRequestChecked: (ref: PullRequestRef, at: Instant) => Promise<void>;
70
- markNoticeTried: (id: number, at: Instant, sent: boolean) => Promise<void>;
71
+ markNoticeTried: (id: number, update: NoticeTryUpdate) => Promise<void>;
71
72
  }
72
73
  export interface StoreChange {
73
74
  readonly at: Instant;
@@ -230,13 +231,23 @@ export interface HoldNotice {
230
231
  readonly item: ItemId;
231
232
  readonly reason: HoldReason;
232
233
  readonly at: Instant;
234
+ readonly key: string;
233
235
  }
234
236
  export type NoticeOutcome = {
235
237
  readonly kind: 'sent';
236
238
  } | {
237
- readonly kind: 'failed';
239
+ readonly kind: 'refused';
240
+ readonly reason: string;
241
+ } | {
242
+ readonly kind: 'uncertain';
238
243
  readonly reason: string;
239
244
  };
245
+ export interface NoticeTryUpdate {
246
+ readonly sentAt: Instant | null;
247
+ readonly keySeq: number;
248
+ readonly keyTries: number;
249
+ readonly nextTryAt: Instant;
250
+ }
240
251
  export interface CallerProof {
241
252
  prove: (request: Request) => Promise<CallerIdentity | null>;
242
253
  }
@@ -1,9 +1,14 @@
1
- import type { HoldNotice, Notifier } from '../ports/types.ts';
1
+ import type { HoldNotice, NoticeOutcome, Notifier } from '../ports/types.ts';
2
2
  export interface FakeNotifier extends Notifier {
3
3
  readonly told: readonly HoldNotice[];
4
+ readonly attempts: readonly HoldNotice[];
4
5
  readonly calls: () => number;
5
- failNext: (reason: string) => void;
6
- failAlways: (reason: string) => void;
6
+ failNext: (outcome: Exclude<NoticeOutcome, {
7
+ kind: 'sent';
8
+ }>) => void;
9
+ failAlways: (outcome: Exclude<NoticeOutcome, {
10
+ kind: 'sent';
11
+ }>) => void;
7
12
  recover: () => void;
8
13
  }
9
14
  export declare function createFakeNotifier(): FakeNotifier;
package/dist/types.d.ts CHANGED
@@ -261,6 +261,9 @@ export interface NoticeRow {
261
261
  readonly at: Instant;
262
262
  readonly tries: number;
263
263
  readonly sentAt: Instant | null;
264
+ readonly keySeq: number;
265
+ readonly keyTries: number;
266
+ readonly nextTryAt: Instant;
264
267
  }
265
268
  export type Effort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
266
269
  export interface Recipe {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxxy/model",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "description": "The types that boxxy's packages share",
5
5
  "homepage": "https://github.com/zgeoff/boxxy/tree/main/libs/model#readme",
6
6
  "bugs": "https://github.com/zgeoff/boxxy/issues",
@@ -31,7 +31,7 @@
31
31
  "typecheck": "tsrs --noEmit"
32
32
  },
33
33
  "dependencies": {
34
- "@boxxy/content-hash": "0.1.9",
34
+ "@boxxy/content-hash": "0.2.0",
35
35
  "tiny-invariant": "1.3.3",
36
36
  "zod": "4.6.7"
37
37
  },
@@ -133,7 +133,11 @@ export interface WorkFacts {
133
133
  readonly skillsRef: CommitSha | null;
134
134
  readonly rulesVersion: RulesVersion;
135
135
  };
136
- 'boxxy.decision.recorded': DecisionRow;
136
+
137
+ // The sink adds `storeVersion` to every payload, so the decision's own field takes another name.
138
+ 'boxxy.decision.recorded': Omit<DecisionRow, 'storeVersion'> & {
139
+ readonly snapshotStoreVersion: number;
140
+ };
137
141
  'boxxy.attempt.started': OfAttempt & {
138
142
  readonly atcSession: AtcSessionId;
139
143
  readonly resumeFallback: boolean;
@@ -93,7 +93,9 @@ export const workFactSchemas = {
93
93
  skillsRef: brandSchemas.commitSha.nullable(),
94
94
  rulesVersion: brandSchemas.rulesVersion,
95
95
  }),
96
- 'boxxy.decision.recorded': decisionRowSchema,
96
+ 'boxxy.decision.recorded': decisionRowSchema
97
+ .omit({ storeVersion: true })
98
+ .extend({ snapshotStoreVersion: countSchema }),
97
99
  'boxxy.attempt.started': z.object({
98
100
  ...ofAttempt,
99
101
  atcSession: brandSchemas.atcSessionId,
package/src/index.ts CHANGED
@@ -229,6 +229,7 @@ export type {
229
229
  ItemFilter,
230
230
  NewId,
231
231
  NoticeOutcome,
232
+ NoticeTryUpdate,
232
233
  Notifier,
233
234
  OutboxReader,
234
235
  PepysAnswer,
@@ -95,6 +95,9 @@ export interface StoreReader extends OutboxReader {
95
95
  // the rows not yet sent, whatever their tries, oldest first
96
96
  listUnsentNotices: (limit: number) => Promise<NoticeRow[]>;
97
97
 
98
+ // the rows not yet sent whose `nextTryAt` has passed, oldest `nextTryAt` first
99
+ listNoticesDue: (now: Instant, limit: number) => Promise<NoticeRow[]>;
100
+
98
101
  // oldest kept first
99
102
  listKeptSessions: () => Promise<{ readonly item: ItemId; readonly kept: KeptSession }[]>;
100
103
 
@@ -152,8 +155,8 @@ export interface StoreTx extends StoreReader {
152
155
  syncLimits: (limits: readonly Limit[], at: Instant) => Promise<void>;
153
156
  markPullRequestChecked: (ref: PullRequestRef, at: Instant) => Promise<void>;
154
157
 
155
- // one more try of the notice; `sent` also sets its `sentAt`
156
- markNoticeTried: (id: number, at: Instant, sent: boolean) => Promise<void>;
158
+ // one more try of the notice, with the new key state and next try time; a `sentAt` also marks it sent
159
+ markNoticeTried: (id: number, update: NoticeTryUpdate) => Promise<void>;
157
160
  }
158
161
 
159
162
  // A change may write no fact, such as the suspend mark; such a change moves no store version. The
@@ -343,11 +346,27 @@ export interface HoldNotice {
343
346
  readonly item: ItemId;
344
347
  readonly reason: HoldReason;
345
348
  readonly at: Instant;
349
+
350
+ // the row's current key; the adapter sends it unchanged
351
+ readonly key: string;
346
352
  }
347
353
 
348
354
  export type NoticeOutcome =
349
355
  | { readonly kind: 'sent' }
350
- | { readonly kind: 'failed'; readonly reason: string };
356
+
357
+ // atc ran nothing
358
+ | { readonly kind: 'refused'; readonly reason: string }
359
+
360
+ // atc may have sent the message
361
+ | { readonly kind: 'uncertain'; readonly reason: string };
362
+
363
+ // What one try writes to its row, besides one more `tries`.
364
+ export interface NoticeTryUpdate {
365
+ readonly sentAt: Instant | null;
366
+ readonly keySeq: number;
367
+ readonly keyTries: number;
368
+ readonly nextTryAt: Instant;
369
+ }
351
370
 
352
371
  // Proves who sent a request to the server. The server's root middleware calls it for every
353
372
  // session and hook procedure, and knows no header and no token itself. null: no proof, so the
@@ -17,7 +17,7 @@ const limitCountSchema = z.object({
17
17
  max: countSchema,
18
18
  });
19
19
 
20
- // A plain object, so that the wire schema of `boxxy.decision.recorded` can extend it.
20
+ // A plain object, so that the schema of `boxxy.decision.recorded` can rename one field of it.
21
21
  export const decisionRowSchema = z.object({
22
22
  id: brandSchemas.decisionId,
23
23
  attempt: brandSchemas.attemptId,
@@ -14,4 +14,7 @@ export const noticeRowSchema: z.ZodType<NoticeRow> = z.object({
14
14
  at: brandSchemas.instant,
15
15
  tries: countSchema,
16
16
  sentAt: brandSchemas.instant.nullable(),
17
+ keySeq: countSchema,
18
+ keyTries: countSchema,
19
+ nextTryAt: brandSchemas.instant,
17
20
  });
@@ -4,46 +4,48 @@ export interface FakeNotifier extends Notifier {
4
4
  // each notice that was sent, in order
5
5
  readonly told: readonly HoldNotice[];
6
6
 
7
- // the number of calls, sent or failed
7
+ // every call, sent or not, in order
8
+ readonly attempts: readonly HoldNotice[];
9
+
10
+ // the number of calls, sent or not
8
11
  readonly calls: () => number;
9
12
 
10
- // the next call fails with the reason
11
- failNext: (reason: string) => void;
13
+ // the next call ends with the outcome
14
+ failNext: (outcome: Exclude<NoticeOutcome, { kind: 'sent' }>) => void;
12
15
 
13
- // every call fails with the reason until `recover`
14
- failAlways: (reason: string) => void;
16
+ // every call ends with the outcome until `recover`
17
+ failAlways: (outcome: Exclude<NoticeOutcome, { kind: 'sent' }>) => void;
15
18
  recover: () => void;
16
19
  }
17
20
 
18
21
  export function createFakeNotifier(): FakeNotifier {
19
22
  const told: HoldNotice[] = [];
20
- let calls = 0;
21
- let next: string | null = null;
22
- let always: string | null = null;
23
+ const attempts: HoldNotice[] = [];
24
+ let next: NoticeOutcome | null = null;
25
+ let always: NoticeOutcome | null = null;
23
26
 
24
27
  return {
25
28
  told,
26
- calls: () => calls,
27
- failNext: (reason) => {
28
- next = reason;
29
+ attempts,
30
+ calls: () => attempts.length,
31
+ failNext: (outcome) => {
32
+ next = outcome;
29
33
  },
30
- failAlways: (reason) => {
31
- always = reason;
34
+ failAlways: (outcome) => {
35
+ always = outcome;
32
36
  },
33
37
  recover: () => {
34
38
  always = null;
35
39
  },
36
40
  tellHold: (notice) => {
37
- calls += 1;
41
+ attempts.push(notice);
38
42
 
39
- const reason = next ?? always;
43
+ const outcome = next ?? always;
40
44
 
41
45
  next = null;
42
46
 
43
- if (reason !== null) {
44
- const failed: NoticeOutcome = { kind: 'failed', reason };
45
-
46
- return Promise.resolve(failed);
47
+ if (outcome !== null) {
48
+ return Promise.resolve(outcome);
47
49
  }
48
50
 
49
51
  told.push(notice);
package/src/types.ts CHANGED
@@ -386,7 +386,7 @@ export interface RunnerRow {
386
386
  readonly paused: boolean;
387
387
  }
388
388
 
389
- // A hold notice for the `Notifier` port. The tick tries it again until `tries` reaches 5.
389
+ // A hold notice for the `Notifier` port. The tick tries it again, with no cap, until atc takes it.
390
390
  export interface NoticeRow {
391
391
  readonly id: number;
392
392
  readonly item: ItemId;
@@ -394,6 +394,15 @@ export interface NoticeRow {
394
394
  readonly at: Instant;
395
395
  readonly tries: number;
396
396
  readonly sentAt: Instant | null;
397
+
398
+ // the row's current key is `boxxy-notice:<id>:<keySeq>`
399
+ readonly keySeq: number;
400
+
401
+ // uncertain tries under the current key; 0 after each change of key
402
+ readonly keyTries: number;
403
+
404
+ // a tick sends the row once now >= nextTryAt; a new row holds its `at`
405
+ readonly nextTryAt: Instant;
397
406
  }
398
407
 
399
408
  export type Effort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';