@boxxy/model 0.1.2 → 0.1.3

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.
Files changed (234) hide show
  1. package/README.md +8 -6
  2. package/dist/closeouts/build-close-out-schema.d.ts +31 -0
  3. package/dist/closeouts/close-out-answer-schema.d.ts +3 -0
  4. package/dist/closeouts/close-out-schema.d.ts +3 -0
  5. package/dist/closeouts/design-close-out-schema.d.ts +30 -0
  6. package/dist/closeouts/friction-schema.d.ts +14 -0
  7. package/dist/closeouts/open-item-schema.d.ts +3 -0
  8. package/dist/closeouts/review-close-out-schema.d.ts +40 -0
  9. package/dist/closeouts/triage-close-out-schema.d.ts +40 -0
  10. package/dist/closeouts/types.d.ts +221 -0
  11. package/dist/dispatch/build-applied-change.d.ts +6 -0
  12. package/dist/dispatch/collect-seat-sync.d.ts +14 -0
  13. package/dist/dispatch/pick-seat-take.d.ts +10 -0
  14. package/dist/dispatch/types.d.ts +11 -0
  15. package/dist/{outbox-fact-schema.d.ts → facts/outbox-fact-schema.d.ts} +1 -1
  16. package/dist/facts/parse-new-fact.d.ts +2 -0
  17. package/dist/facts/types.d.ts +191 -0
  18. package/dist/facts/work-fact-schemas.d.ts +293 -0
  19. package/dist/index.d.ts +64 -23
  20. package/dist/items/build-item-views.d.ts +4 -0
  21. package/dist/items/collect-retries.d.ts +3 -0
  22. package/dist/items/collect-settled.d.ts +3 -0
  23. package/dist/items/derive-item-state.d.ts +17 -0
  24. package/dist/{live-levers.d.ts → items/live-levers.d.ts} +1 -1
  25. package/dist/items/types.d.ts +12 -0
  26. package/dist/pipeline/advance.d.ts +10 -0
  27. package/dist/pipeline/constants.d.ts +2 -0
  28. package/dist/pipeline/count-trailing.d.ts +1 -0
  29. package/dist/pipeline/find-position.d.ts +9 -0
  30. package/dist/pipeline/find-step.d.ts +3 -0
  31. package/dist/pipeline/merge-by-time.d.ts +9 -0
  32. package/dist/pipeline/position-schema.d.ts +3 -0
  33. package/dist/pipeline/types.d.ts +28 -0
  34. package/dist/planning/define-admission.d.ts +3 -0
  35. package/dist/planning/define-gate.d.ts +3 -0
  36. package/dist/planning/define-signal.d.ts +3 -0
  37. package/dist/planning/is-filter-match.d.ts +3 -0
  38. package/dist/planning/limit-fill-schema.d.ts +3 -0
  39. package/dist/{signal-key.d.ts → planning/signal-key.d.ts} +1 -1
  40. package/dist/planning/types.d.ts +155 -0
  41. package/dist/ports/types.d.ts +289 -0
  42. package/dist/reports/constants.d.ts +3 -0
  43. package/dist/reports/cost-to-land-schema.d.ts +29 -0
  44. package/dist/reports/outbox-status.d.ts +16 -0
  45. package/dist/reports/stage-time-schema.d.ts +25 -0
  46. package/dist/reports/types.d.ts +5 -0
  47. package/dist/rows/actor-ref-schema.d.ts +2 -0
  48. package/dist/rows/attempt-row-schema.d.ts +3 -0
  49. package/dist/rows/attempt-session-row-schema.d.ts +3 -0
  50. package/dist/{brand-schemas.d.ts → rows/brand-schemas.d.ts} +5 -2
  51. package/dist/rows/caller-identity-schema.d.ts +3 -0
  52. package/dist/rows/criterion-schema.d.ts +3 -0
  53. package/dist/rows/decision-row-schema.d.ts +34 -0
  54. package/dist/rows/evidence-row-schema.d.ts +3 -0
  55. package/dist/rows/item-row-schema.d.ts +28 -0
  56. package/dist/rows/item-view-schema.d.ts +3 -0
  57. package/dist/rows/lease-row-schema.d.ts +3 -0
  58. package/dist/rows/lever-row-schema.d.ts +12 -0
  59. package/dist/rows/limit-schema.d.ts +3 -0
  60. package/dist/rows/limit-seat-row-schema.d.ts +3 -0
  61. package/dist/rows/note-row-schema.d.ts +3 -0
  62. package/dist/rows/notice-row-schema.d.ts +3 -0
  63. package/dist/rows/pull-request-row-schema.d.ts +3 -0
  64. package/dist/rows/relation-row-schema.d.ts +3 -0
  65. package/dist/{row-schemas.d.ts → rows/row-schemas.d.ts} +2 -2
  66. package/dist/rows/runner-row-schema.d.ts +3 -0
  67. package/dist/rows/spawn-request-schema.d.ts +3 -0
  68. package/dist/rules/define-phase.d.ts +3 -0
  69. package/dist/rules/load-rejection-schema.d.ts +3 -0
  70. package/dist/rules/types.d.ts +244 -0
  71. package/dist/test-utils/build-mock-cost-to-land.d.ts +2 -0
  72. package/dist/test-utils/build-mock-evidence-row.d.ts +1 -1
  73. package/dist/test-utils/build-mock-fact-id.d.ts +1 -1
  74. package/dist/test-utils/build-mock-frame-item.d.ts +1 -1
  75. package/dist/test-utils/build-mock-frame.d.ts +2 -0
  76. package/dist/test-utils/build-mock-item-rules.d.ts +2 -0
  77. package/dist/test-utils/build-mock-lease-row.d.ts +1 -1
  78. package/dist/test-utils/build-mock-outbox-fact.d.ts +2 -0
  79. package/dist/test-utils/build-mock-phase-kind.d.ts +7 -0
  80. package/dist/test-utils/build-mock-pipeline.d.ts +7 -0
  81. package/dist/test-utils/build-mock-relation-row.d.ts +2 -0
  82. package/dist/test-utils/build-mock-settled.d.ts +9 -0
  83. package/dist/test-utils/build-mock-snapshot.d.ts +1 -1
  84. package/dist/test-utils/build-mock-v1-attempt-row.d.ts +2 -0
  85. package/dist/test-utils/build-mock-v1-item-row.d.ts +2 -0
  86. package/dist/test-utils/build-mock-v1-item-view.d.ts +2 -0
  87. package/dist/test-utils/build-mock-v1-lever-row.d.ts +2 -0
  88. package/dist/test-utils/build-mock-v1-pull-request-row.d.ts +2 -0
  89. package/dist/test-utils/build-mock-work-fact.d.ts +12 -0
  90. package/dist/test-utils/create-fake-notifier.d.ts +9 -0
  91. package/dist/test-utils/create-fake-pepys-reader.d.ts +13 -0
  92. package/dist/test-utils/fact-sink-contract.d.ts +14 -0
  93. package/dist/test-utils/pepys-reader-contract.d.ts +17 -0
  94. package/dist/{types/core.d.ts → types.d.ts} +93 -43
  95. package/package.json +13 -3
  96. package/src/{build-close-out-schema.ts → closeouts/build-close-out-schema.ts} +5 -10
  97. package/src/closeouts/close-out-answer-schema.ts +42 -0
  98. package/src/closeouts/close-out-schema.ts +13 -0
  99. package/src/closeouts/design-close-out-schema.ts +20 -0
  100. package/src/closeouts/friction-schema.ts +11 -0
  101. package/src/closeouts/open-item-schema.ts +8 -0
  102. package/src/closeouts/review-close-out-schema.ts +38 -0
  103. package/src/closeouts/triage-close-out-schema.ts +29 -0
  104. package/src/closeouts/types.ts +230 -0
  105. package/src/dispatch/build-applied-change.ts +57 -0
  106. package/src/dispatch/collect-seat-sync.ts +86 -0
  107. package/src/dispatch/pick-seat-take.ts +39 -0
  108. package/src/dispatch/types.ts +14 -0
  109. package/src/facts/outbox-fact-schema.ts +22 -0
  110. package/src/facts/parse-new-fact.ts +16 -0
  111. package/src/facts/types.ts +235 -0
  112. package/src/facts/work-fact-schemas.ts +201 -0
  113. package/src/index.ts +147 -55
  114. package/src/items/build-item-views.ts +146 -0
  115. package/src/items/collect-retries.ts +12 -0
  116. package/src/items/collect-settled.ts +20 -0
  117. package/src/items/derive-item-state.ts +95 -0
  118. package/src/{live-levers.ts → items/live-levers.ts} +1 -1
  119. package/src/items/types.ts +26 -0
  120. package/src/pipeline/advance.ts +78 -0
  121. package/src/pipeline/constants.ts +12 -0
  122. package/src/pipeline/count-trailing.ts +16 -0
  123. package/src/pipeline/find-position.ts +74 -0
  124. package/src/pipeline/find-step.ts +6 -0
  125. package/src/pipeline/merge-by-time.ts +33 -0
  126. package/src/pipeline/position-schema.ts +27 -0
  127. package/src/pipeline/types.ts +47 -0
  128. package/src/{define-admission.ts → planning/define-admission.ts} +2 -2
  129. package/src/{define-gate.ts → planning/define-gate.ts} +2 -2
  130. package/src/{define-signal.ts → planning/define-signal.ts} +2 -2
  131. package/src/planning/is-filter-match.ts +10 -0
  132. package/src/planning/limit-fill-schema.ts +12 -0
  133. package/src/{signal-key.ts → planning/signal-key.ts} +1 -1
  134. package/src/planning/types.ts +216 -0
  135. package/src/ports/types.ts +412 -0
  136. package/src/reports/constants.ts +6 -0
  137. package/src/reports/cost-to-land-schema.ts +47 -0
  138. package/src/reports/outbox-status.ts +22 -0
  139. package/src/reports/stage-time-schema.ts +21 -0
  140. package/src/reports/types.ts +7 -0
  141. package/src/rows/actor-ref-schema.ts +17 -0
  142. package/src/rows/attempt-row-schema.ts +69 -0
  143. package/src/rows/attempt-session-row-schema.ts +12 -0
  144. package/src/{brand-schemas.ts → rows/brand-schemas.ts} +5 -2
  145. package/src/rows/caller-identity-schema.ts +7 -0
  146. package/src/rows/criterion-schema.ts +9 -0
  147. package/src/rows/decision-row-schema.ts +34 -0
  148. package/src/rows/evidence-row-schema.ts +13 -0
  149. package/src/rows/item-row-schema.ts +32 -0
  150. package/src/rows/item-view-schema.ts +31 -0
  151. package/src/rows/lease-row-schema.ts +12 -0
  152. package/src/rows/lever-row-schema.ts +17 -0
  153. package/src/rows/limit-schema.ts +12 -0
  154. package/src/rows/limit-seat-row-schema.ts +12 -0
  155. package/src/rows/note-row-schema.ts +12 -0
  156. package/src/rows/notice-row-schema.ts +17 -0
  157. package/src/rows/pull-request-row-schema.ts +15 -0
  158. package/src/rows/relation-row-schema.ts +13 -0
  159. package/src/rows/row-schemas.ts +30 -0
  160. package/src/rows/runner-row-schema.ts +9 -0
  161. package/src/rows/spawn-request-schema.ts +15 -0
  162. package/src/rules/define-phase.ts +12 -0
  163. package/src/rules/load-rejection-schema.ts +51 -0
  164. package/src/rules/types.ts +291 -0
  165. package/src/test-utils/build-mock-cost-to-land.ts +31 -0
  166. package/src/test-utils/build-mock-evidence-row.ts +2 -2
  167. package/src/test-utils/build-mock-fact-id.ts +3 -3
  168. package/src/test-utils/build-mock-frame-item.ts +10 -5
  169. package/src/test-utils/build-mock-frame.ts +13 -0
  170. package/src/test-utils/build-mock-item-rules.ts +17 -0
  171. package/src/test-utils/build-mock-lease-row.ts +2 -2
  172. package/src/test-utils/build-mock-outbox-fact.ts +22 -0
  173. package/src/test-utils/build-mock-phase-kind.ts +29 -0
  174. package/src/test-utils/build-mock-pipeline.ts +52 -0
  175. package/src/test-utils/{build-mock-link-row.ts → build-mock-relation-row.ts} +4 -4
  176. package/src/test-utils/build-mock-settled.ts +18 -0
  177. package/src/test-utils/build-mock-snapshot.ts +10 -2
  178. package/src/test-utils/{build-mock-attempt-row.ts → build-mock-v1-attempt-row.ts} +28 -9
  179. package/src/test-utils/{build-mock-item-row.ts → build-mock-v1-item-row.ts} +7 -3
  180. package/src/test-utils/build-mock-v1-item-view.ts +29 -0
  181. package/src/test-utils/{build-mock-lever-row.ts → build-mock-v1-lever-row.ts} +5 -3
  182. package/src/test-utils/build-mock-v1-pull-request-row.ts +26 -0
  183. package/src/test-utils/build-mock-work-fact.ts +179 -0
  184. package/src/test-utils/create-fake-notifier.ts +54 -0
  185. package/src/test-utils/create-fake-pepys-reader.ts +73 -0
  186. package/src/test-utils/fact-sink-contract.ts +103 -0
  187. package/src/test-utils/pepys-reader-contract.ts +117 -0
  188. package/src/{types/core.ts → types.ts} +183 -59
  189. package/dist/actor-ref-schema.d.ts +0 -2
  190. package/dist/build-applied-change.d.ts +0 -13
  191. package/dist/build-close-out-schema.d.ts +0 -3
  192. package/dist/build-item-views.d.ts +0 -2
  193. package/dist/define-admission.d.ts +0 -3
  194. package/dist/define-gate.d.ts +0 -3
  195. package/dist/define-signal.d.ts +0 -3
  196. package/dist/derive-item-state.d.ts +0 -2
  197. package/dist/fact-schemas.d.ts +0 -5
  198. package/dist/find-slot-take.d.ts +0 -5
  199. package/dist/friction-schema.d.ts +0 -3
  200. package/dist/test-utils/build-mock-attempt-row.d.ts +0 -2
  201. package/dist/test-utils/build-mock-item-row.d.ts +0 -2
  202. package/dist/test-utils/build-mock-item-rows.d.ts +0 -2
  203. package/dist/test-utils/build-mock-item-view.d.ts +0 -2
  204. package/dist/test-utils/build-mock-lever-row.d.ts +0 -2
  205. package/dist/test-utils/build-mock-link-row.d.ts +0 -2
  206. package/dist/test-utils/build-mock-pull-request-row.d.ts +0 -2
  207. package/dist/types/dispatch.d.ts +0 -82
  208. package/dist/types/facts.d.ts +0 -154
  209. package/dist/types/planning.d.ts +0 -94
  210. package/dist/types/ports.d.ts +0 -218
  211. package/dist/types/registries.d.ts +0 -150
  212. package/src/actor-ref-schema.ts +0 -9
  213. package/src/build-applied-change.ts +0 -71
  214. package/src/build-item-views.ts +0 -128
  215. package/src/derive-item-state.ts +0 -76
  216. package/src/fact-schemas.ts +0 -132
  217. package/src/find-slot-take.ts +0 -12
  218. package/src/friction-schema.ts +0 -10
  219. package/src/outbox-fact-schema.ts +0 -25
  220. package/src/row-schemas.ts +0 -89
  221. package/src/test-utils/build-mock-item-rows.ts +0 -14
  222. package/src/test-utils/build-mock-item-view.ts +0 -8
  223. package/src/test-utils/build-mock-pull-request-row.ts +0 -16
  224. package/src/types/dispatch.ts +0 -95
  225. package/src/types/facts.ts +0 -166
  226. package/src/types/planning.ts +0 -136
  227. package/src/types/ports.ts +0 -262
  228. package/src/types/registries.ts +0 -185
  229. /package/dist/test-utils/{seed-faker.d.ts → pin-faker-seed.d.ts} +0 -0
  230. /package/dist/{format-key-path.d.ts → utils/format-key-path.d.ts} +0 -0
  231. /package/dist/{to-json-value.d.ts → utils/to-json-value.d.ts} +0 -0
  232. /package/src/test-utils/{seed-faker.ts → pin-faker-seed.ts} +0 -0
  233. /package/src/{format-key-path.ts → utils/format-key-path.ts} +0 -0
  234. /package/src/{to-json-value.ts → utils/to-json-value.ts} +0 -0
@@ -0,0 +1,230 @@
1
+ import type { JsonValue } from '@boxxy/content-hash';
2
+ import type {
3
+ AttemptId,
4
+ CommitSha,
5
+ Criterion,
6
+ CriterionId,
7
+ DesignMode,
8
+ EvidenceRow,
9
+ FactId,
10
+ ItemId,
11
+ ItemKindName,
12
+ ItemView,
13
+ RelationTypeName,
14
+ RepoName,
15
+ } from '../types.ts';
16
+
17
+ // The four close-out bodies, one per v1 phase. Every body carries `friction` and
18
+ // `frictionDropped`. The top-level keys of the four differ, so a stored close-out parses as one
19
+ // of them only.
20
+ export type CloseOut = TriageCloseOut | DesignCloseOut | BuildCloseOut | ReviewCloseOut;
21
+
22
+ export interface TriageCloseOut {
23
+ readonly result:
24
+ | { readonly kind: 'accepted' }
25
+ | { readonly kind: 'duplicate'; readonly of: ItemId }
26
+ | { readonly kind: 'declined'; readonly reason: string };
27
+ readonly set?: {
28
+ readonly kind?: ItemKindName;
29
+ readonly designMode?: DesignMode;
30
+
31
+ // required with `accepted` when the item has no acceptance list (`no-acceptance`)
32
+ readonly acceptance?: readonly { readonly text: string }[];
33
+ };
34
+ readonly relations: readonly RelationDraft[];
35
+ readonly friction: readonly Friction[];
36
+ readonly frictionDropped: number;
37
+ }
38
+
39
+ export interface DesignCloseOut {
40
+ readonly result:
41
+ | { readonly kind: 'designed'; readonly design: DesignRef }
42
+ | { readonly kind: 'blocked'; readonly reason: string }
43
+ | { readonly kind: 'abandoned'; readonly reason: string };
44
+ readonly friction: readonly Friction[];
45
+ readonly frictionDropped: number;
46
+ }
47
+
48
+ // Where the design of an item is, and what it says in short. The build and the review get it in
49
+ // their input.
50
+ export interface DesignRef {
51
+ readonly summary: string;
52
+
53
+ // a URL or a repo path of the whole design; null when the summary is the whole design
54
+ readonly location: string | null;
55
+ }
56
+
57
+ export interface BuildCloseOut {
58
+ readonly result:
59
+ | { readonly kind: 'built' }
60
+ | { readonly kind: 'blocked'; readonly reason: string }
61
+ | { readonly kind: 'abandoned'; readonly reason: string };
62
+ readonly criteria: readonly { readonly criterion: CriterionId; readonly evidence: FactId }[];
63
+ readonly openItems: readonly OpenItem[];
64
+ readonly friction: readonly Friction[];
65
+ readonly frictionDropped: number;
66
+ }
67
+
68
+ // The review close-out as stored: the review session sends noslop's whole result, and the parser
69
+ // keeps `NoslopSummary` in its place.
70
+ export interface ReviewCloseOut {
71
+ readonly verdict:
72
+ | { readonly kind: 'passed' }
73
+ | { readonly kind: 'changes-requested'; readonly findings: readonly Finding[] };
74
+ readonly noslop: NoslopSummary;
75
+ readonly friction: readonly Friction[];
76
+ readonly frictionDropped: number;
77
+ }
78
+
79
+ export interface NoslopSummary {
80
+ // the schema version of noslop's result
81
+ readonly version: number;
82
+ readonly reviewID: string;
83
+ readonly head: CommitSha;
84
+ readonly counts: {
85
+ readonly findings: number;
86
+ readonly failedCalls: number;
87
+ readonly fixChecks: number;
88
+ };
89
+
90
+ // the UTF-8 bytes of the JSON of the parsed result, as a stored copy would take them
91
+ readonly reportBytes: number;
92
+
93
+ // the whole result when `reportBytes` is within the close-out limit; else null, never a part
94
+ readonly report: JsonValue | null;
95
+ }
96
+
97
+ export interface Finding {
98
+ readonly title: string;
99
+ readonly body: string;
100
+ readonly path?: string;
101
+ readonly line?: number;
102
+ }
103
+
104
+ // A relation that a close-out asks for, from the attempt's item to `target`.
105
+ export interface RelationDraft {
106
+ readonly type: RelationTypeName;
107
+ readonly target: ItemId;
108
+ }
109
+
110
+ export interface OpenItem {
111
+ readonly kind: 'core.issue';
112
+ readonly title: string;
113
+ readonly body: string;
114
+ }
115
+
116
+ export type FrictionTargetKind = 'skill' | 'rule' | 'lint' | 'agents-md' | 'tool';
117
+
118
+ export interface Friction {
119
+ readonly target: { readonly kind: FrictionTargetKind; readonly name: string };
120
+ readonly change: string;
121
+ }
122
+
123
+ // What a phase kind's settle asks for. The settle of an attempt whose outcome maps to a hold
124
+ // withholds each effect that changes an item, and keeps `record-proposal`.
125
+ export type Effect =
126
+ | { readonly kind: 'file-item'; readonly item: OpenItem }
127
+ | { readonly kind: 'close-item'; readonly reason: string; readonly of: ItemId | null }
128
+ | { readonly kind: 'add-relation'; readonly relation: RelationDraft }
129
+ | { readonly kind: 'set-kind'; readonly itemKind: ItemKindName }
130
+ | { readonly kind: 'set-field'; readonly field: 'designMode'; readonly value: DesignMode }
131
+ | { readonly kind: 'set-acceptance'; readonly criteria: readonly Criterion[] }
132
+ | { readonly kind: 'record-proposal'; readonly friction: Friction };
133
+
134
+ export type CloseOutProblem =
135
+ | { readonly kind: 'schema'; readonly path: string; readonly message: string }
136
+ | { readonly kind: 'unknown-criterion'; readonly criterion: CriterionId }
137
+ | { readonly kind: 'foreign-evidence'; readonly evidence: FactId }
138
+ | { readonly kind: 'built-without-evidence'; readonly criteria: readonly CriterionId[] }
139
+ | { readonly kind: 'no-acceptance' }
140
+ | { readonly kind: 'unknown-item'; readonly item: string }
141
+ | { readonly kind: 'unknown-kind'; readonly itemKind: string }
142
+ | {
143
+ readonly kind: 'refused-relation';
144
+ readonly type: string;
145
+ readonly target: string;
146
+ readonly reason: 'unknown-type' | 'self';
147
+ }
148
+
149
+ // a rule of one phase kind that its schema cannot hold, such as a finding for `changes-requested`
150
+ | { readonly kind: 'phase-rule'; readonly message: string }
151
+
152
+ // over `closeOutBytes` as stored; `bytes` tells the agent how much to cut
153
+ | { readonly kind: 'too-large'; readonly bytes: number; readonly max: number };
154
+
155
+ export type CloseOutParse<Body> =
156
+ | { readonly kind: 'valid'; readonly closeOut: Body }
157
+ | { readonly kind: 'invalid'; readonly problems: readonly CloseOutProblem[] };
158
+
159
+ export interface CloseOutParser<Body> {
160
+ parse: (raw: unknown, ctx: CloseOutContext) => CloseOutParse<Body>;
161
+
162
+ // the JSON Schema of the body that the agent sends, which `attempt.current` gives the session
163
+ readonly jsonSchema: JsonValue;
164
+ }
165
+
166
+ export interface CloseOutContext {
167
+ readonly item: ItemView;
168
+ readonly attempt: AttemptId;
169
+
170
+ // the attempt's own evidence rows
171
+ readonly evidence: readonly EvidenceRow[];
172
+ readonly world: CloseOutWorld;
173
+ }
174
+
175
+ // What a parser may check a named item, kind or relation type against.
176
+ export interface CloseOutWorld {
177
+ // every item of the store, open or closed
178
+ readonly items: ReadonlySet<ItemId>;
179
+ readonly kinds: ReadonlySet<ItemKindName>;
180
+ readonly relationTypes: ReadonlySet<RelationTypeName>;
181
+ }
182
+
183
+ export interface CriterionText {
184
+ readonly id: CriterionId;
185
+ readonly text: string;
186
+ }
187
+
188
+ // The input of a triage or a design attempt: the item's acceptance list, which may be empty
189
+ // before triage.
190
+ export interface AcceptanceInput {
191
+ readonly acceptance: readonly CriterionText[];
192
+ }
193
+
194
+ // The input of a build attempt. In round 2 and later it holds the findings of the review that
195
+ // sent the item back; in round 1 it holds none.
196
+ export interface BuildInput {
197
+ readonly acceptance: readonly CriterionText[];
198
+ readonly design: DesignRef | null;
199
+ readonly findings: readonly Finding[];
200
+
201
+ // the item's latest pull request, which a build of round 2 pushes to
202
+ readonly pullRequest: PullRequestLink | null;
203
+ }
204
+
205
+ export interface ReviewInput {
206
+ readonly acceptance: readonly CriterionText[];
207
+ readonly design: DesignRef | null;
208
+ readonly pullRequest: PullRequestLink | null;
209
+ }
210
+
211
+ export interface PullRequestLink {
212
+ readonly repo: RepoName;
213
+ readonly number: number;
214
+ readonly url: string;
215
+ }
216
+
217
+ // What `attempt.closeOut` answers. `recorded` carries `repeat`: true when the same close-out
218
+ // arrived again. `rejected` carries every problem at once.
219
+ export type CloseOutAnswer =
220
+ | {
221
+ readonly kind: 'recorded';
222
+ readonly attempt: AttemptId;
223
+ readonly factId: FactId;
224
+ readonly repeat: boolean;
225
+ }
226
+ | { readonly kind: 'rejected'; readonly problems: readonly CloseOutProblem[] }
227
+ | {
228
+ readonly kind: 'refused';
229
+ readonly reason: 'no-attempt' | 'already-closed-out' | 'not-current';
230
+ };
@@ -0,0 +1,57 @@
1
+ import type { AnyNewFact } from '../facts/types.ts';
2
+ import type { RowWrite, StoreChange } from '../ports/types.ts';
3
+
4
+ export interface ChangeAssignments {
5
+ // 0 when the store holds no lever
6
+ readonly lastLeverSeq: number;
7
+
8
+ // 0 when the store holds no notice
9
+ readonly lastNoticeId: number;
10
+ }
11
+
12
+ // The store numbers each lever and each notice, so the caller's `seq` and `id` are replaced.
13
+ export function buildAppliedChange(
14
+ change: StoreChange,
15
+ assignments: ChangeAssignments,
16
+ ): Pick<StoreChange, 'writes' | 'facts'> {
17
+ const leverSeqs: number[] = [];
18
+ let notices = 0;
19
+
20
+ const writes = change.writes.map((write): RowWrite => {
21
+ if (write.op !== 'insert') {
22
+ return write;
23
+ }
24
+
25
+ if (write.table === 'levers') {
26
+ const seq = assignments.lastLeverSeq + leverSeqs.length + 1;
27
+
28
+ leverSeqs.push(seq);
29
+
30
+ return { ...write, row: { ...write.row, seq } };
31
+ }
32
+
33
+ if (write.table === 'notices') {
34
+ notices += 1;
35
+
36
+ return { ...write, row: { ...write.row, id: assignments.lastNoticeId + notices } };
37
+ }
38
+
39
+ return write;
40
+ });
41
+
42
+ let leverFacts = 0;
43
+
44
+ const facts = change.facts.map((fact): AnyNewFact => {
45
+ if (fact.type !== 'boxxy.lever.recorded') {
46
+ return fact;
47
+ }
48
+
49
+ const seq = leverSeqs[leverFacts];
50
+
51
+ leverFacts += 1;
52
+
53
+ return seq === undefined ? fact : { ...fact, payload: { ...fact.payload, seq } };
54
+ });
55
+
56
+ return { writes, facts };
57
+ }
@@ -0,0 +1,86 @@
1
+ import { isFilterMatch } from '../planning/is-filter-match.ts';
2
+ import type { Limit } from '../planning/types.ts';
3
+ import type { AttemptRow, AttemptState, Instant, LimitSeatRow } from '../types.ts';
4
+
5
+ export interface SeatSync {
6
+ // held seats to release
7
+ readonly release: readonly LimitSeatRow[];
8
+ readonly take: readonly LimitSeatRow[];
9
+ }
10
+
11
+ interface SeatSyncInput {
12
+ // the limits as the rules now load them
13
+ readonly limits: readonly Limit[];
14
+
15
+ // the seats with no release time
16
+ readonly held: readonly LimitSeatRow[];
17
+ readonly attempts: readonly AttemptRow[];
18
+ readonly at: Instant;
19
+ }
20
+
21
+ // The seats follow the loaded limits, matched against each attempt's stored fields: a seat of a
22
+ // limit that went, or that no longer counts its attempt, is released; a seated attempt gets a seat
23
+ // in each limit that now counts it, past `max` when the limit is full.
24
+ export function collectSeatSync(input: SeatSyncInput): SeatSync {
25
+ const seated = new Map(
26
+ input.attempts.filter((attempt) => isSeated(attempt)).map((attempt) => [attempt.id, attempt]),
27
+ );
28
+
29
+ const release = input.held.filter((seat) => {
30
+ const limit = input.limits.find((candidate) => candidate.name === seat.limit);
31
+ const attempt = seated.get(seat.attempt);
32
+
33
+ return (
34
+ limit === undefined || (attempt !== undefined && !isFilterMatch(limit.counts, attempt.fields))
35
+ );
36
+ });
37
+
38
+ const kept = input.held.filter((seat) => !release.includes(seat));
39
+ const take: LimitSeatRow[] = [];
40
+
41
+ for (const limit of input.limits) {
42
+ let count = kept.filter((seat) => seat.limit === limit.name).length;
43
+
44
+ for (const attempt of seated.values()) {
45
+ const hasSeat = kept.some(
46
+ (seat) =>
47
+ seat.limit === limit.name &&
48
+ seat.attempt === attempt.id &&
49
+ seat.holdGen === attempt.holdGen,
50
+ );
51
+
52
+ if (!hasSeat && isFilterMatch(limit.counts, attempt.fields)) {
53
+ take.push({
54
+ limit: limit.name,
55
+ attempt: attempt.id,
56
+ holdGen: attempt.holdGen,
57
+ overCap: count >= limit.max,
58
+ takenAt: input.at,
59
+ releasedAt: null,
60
+ });
61
+
62
+ count += 1;
63
+ }
64
+ }
65
+ }
66
+
67
+ return { release, take };
68
+ }
69
+
70
+ // A sleeping attempt holds no seat, and a settled one has released its seats.
71
+ const seatedStates: ReadonlySet<AttemptState> = new Set([
72
+ 'starting',
73
+ 'reconciling',
74
+ 'running',
75
+ 'waking',
76
+ 'closing',
77
+ ]);
78
+
79
+ // A sleeping attempt that closes out moves to `closing` and takes no seat, so its last sleep,
80
+ // later than its last wake, keeps it out.
81
+ function isSeated(attempt: AttemptRow): boolean {
82
+ const isAsleep =
83
+ attempt.sleptAt !== null && (attempt.wokenAt === null || attempt.wokenAt < attempt.sleptAt);
84
+
85
+ return seatedStates.has(attempt.state) && !isAsleep;
86
+ }
@@ -0,0 +1,39 @@
1
+ import type { LimitFill, SeatTake } from '../planning/types.ts';
2
+ import type { Instant, LimitName, LimitSeatRow } from '../types.ts';
3
+
4
+ export type SeatTakeAnswer =
5
+ | { readonly kind: 'take'; readonly seats: readonly LimitSeatRow[] }
6
+ | { readonly kind: 'limit-full' | 'unknown-limit'; readonly limit: LimitName };
7
+
8
+ // All or none: one seat in each named limit, or the first limit that refuses. A take over the
9
+ // cap ignores `max`, and still refuses a limit that the store does not hold.
10
+ export function pickSeatTake(
11
+ take: SeatTake,
12
+ limits: readonly LimitFill[],
13
+ at: Instant,
14
+ ): SeatTakeAnswer {
15
+ const names = [...new Set(take.limits)];
16
+
17
+ for (const name of names) {
18
+ const limit = limits.find((candidate) => candidate.name === name);
19
+
20
+ if (limit === undefined) {
21
+ return { kind: 'unknown-limit', limit: name };
22
+ }
23
+
24
+ if (!take.overCap && limit.held >= limit.max) {
25
+ return { kind: 'limit-full', limit: name };
26
+ }
27
+ }
28
+
29
+ const seats = names.map((limit): LimitSeatRow => ({
30
+ limit,
31
+ attempt: take.attempt,
32
+ holdGen: take.holdGen,
33
+ overCap: take.overCap,
34
+ takenAt: at,
35
+ releasedAt: null,
36
+ }));
37
+
38
+ return { kind: 'take', seats };
39
+ }
@@ -0,0 +1,14 @@
1
+ import type { SpawnRequest } from '../ports/types.ts';
2
+ import type { AttemptRow, KeptSession } from '../types.ts';
3
+
4
+ // The second kind is `wake`: the claim of a sleeping attempt, which the worker resumes.
5
+ export type Claim =
6
+ | {
7
+ readonly kind: 'start';
8
+ readonly attempt: AttemptRow;
9
+ readonly spawn: SpawnRequest;
10
+
11
+ // the item's kept session, when the claim came back through an edge with `resume: true`
12
+ readonly kept: KeptSession | null;
13
+ }
14
+ | { readonly kind: 'wake'; readonly attempt: AttemptRow };
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+ import { actorRefSchema } from '../rows/actor-ref-schema.ts';
3
+ import { brandSchemas } from '../rows/brand-schemas.ts';
4
+ import type { FactType, OutboxFact } from './types.ts';
5
+ import { workFactSchemas } from './work-fact-schemas.ts';
6
+
7
+ const factTypes = Object.keys(workFactSchemas).filter((type): type is FactType =>
8
+ Object.hasOwn(workFactSchemas, type),
9
+ );
10
+
11
+ export const outboxFactSchema: z.ZodType<OutboxFact> = z.object({
12
+ seq: z.number().int().positive(),
13
+ factId: brandSchemas.factId,
14
+ type: z.enum(factTypes),
15
+ entity: z.templateLiteral(['boxxy:item:', z.string().min(1)]),
16
+ actor: actorRefSchema,
17
+ at: brandSchemas.instant,
18
+ payload: z.json(),
19
+ sentAt: brandSchemas.instant.nullable(),
20
+ parkedAt: brandSchemas.instant.nullable(),
21
+ parkedReason: z.string().min(1).nullable(),
22
+ });
@@ -0,0 +1,16 @@
1
+ import type { z } from 'zod';
2
+ import type { AnyNewFact } from './types.ts';
3
+ import { workFactSchemas } from './work-fact-schemas.ts';
4
+
5
+ // The store's check of each fact before the change writes it: a payload that its type's schema
6
+ // refuses throws, and the change writes nothing.
7
+ export function parseNewFact(fact: AnyNewFact): AnyNewFact {
8
+ const schema: z.ZodType = workFactSchemas[fact.type];
9
+ const parsed = schema.safeParse(fact.payload);
10
+
11
+ if (!parsed.success) {
12
+ throw new Error(`the payload of a ${fact.type} fact is not valid`, { cause: parsed.error });
13
+ }
14
+
15
+ return fact;
16
+ }