@boxxy/model 0.1.2 → 0.1.4

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
@@ -1,128 +0,0 @@
1
- import { deriveItemState } from './derive-item-state.ts';
2
- import { liveLevers } from './live-levers.ts';
3
- import type {
4
- AttemptId,
5
- AttemptRow,
6
- Instant,
7
- ItemId,
8
- ItemKindName,
9
- ItemRow,
10
- ItemRows,
11
- ItemSetRows,
12
- ItemState,
13
- ItemWithLevers,
14
- LeaseRow,
15
- LeverRow,
16
- LinkRow,
17
- MaxAttemptsByKind,
18
- PullRequestRow,
19
- } from './types/core.ts';
20
-
21
- export function buildItemViews(
22
- rows: ItemSetRows,
23
- now: Instant,
24
- maxAttemptsByKind: MaxAttemptsByKind,
25
- ): ItemWithLevers[] {
26
- const groups = collectItemGroups(rows, now);
27
-
28
- // A blocking source counts only when it is done or archived, and no link makes an item either,
29
- // so each source's state derived without its links settles its targets.
30
- const ownStates = new Map<ItemId, ItemState>(
31
- rows.items.map((item) => [
32
- item.id,
33
- deriveItemState(
34
- item,
35
- buildItemRows(item, groups.get(item.id), maxAttemptsByKind, {
36
- links: [],
37
- blockerStates: new Map(),
38
- }),
39
- ).state,
40
- ]),
41
- );
42
-
43
- return rows.items.map((item) => {
44
- const group = groups.get(item.id);
45
-
46
- const itemRows = buildItemRows(item, group, maxAttemptsByKind, {
47
- links: group?.links ?? [],
48
- blockerStates: ownStates,
49
- });
50
-
51
- return { ...item, derived: deriveItemState(item, itemRows), levers: group?.levers ?? [] };
52
- });
53
- }
54
-
55
- interface ItemGroup {
56
- readonly links: LinkRow[];
57
- readonly attempts: AttemptRow[];
58
- readonly pullRequests: PullRequestRow[];
59
- levers: LeverRow[];
60
- liveLease: LeaseRow | null;
61
- }
62
-
63
- function collectItemGroups(rows: ItemSetRows, now: Instant): Map<ItemId, ItemGroup> {
64
- const groups = new Map<ItemId, ItemGroup>(
65
- rows.items.map((item) => [
66
- item.id,
67
- { links: [], attempts: [], pullRequests: [], levers: [], liveLease: null },
68
- ]),
69
- );
70
-
71
- const attemptItems = new Map<AttemptId, ItemId>();
72
-
73
- for (const link of rows.links) {
74
- groups.get(link.target)?.links.push(link);
75
- }
76
-
77
- for (const attempt of rows.attempts) {
78
- attemptItems.set(attempt.id, attempt.item);
79
- groups.get(attempt.item)?.attempts.push(attempt);
80
- }
81
-
82
- for (const pullRequest of rows.pullRequests) {
83
- const item = attemptItems.get(pullRequest.attempt);
84
-
85
- if (item !== undefined) {
86
- groups.get(item)?.pullRequests.push(pullRequest);
87
- }
88
- }
89
-
90
- for (const lease of rows.liveLeases) {
91
- const group = groups.get(lease.item);
92
-
93
- if (group !== undefined) {
94
- group.liveLease = lease;
95
- }
96
- }
97
-
98
- for (const [item, group] of groups) {
99
- group.levers = liveLevers(
100
- rows.levers.filter((lever) => lever.item === item),
101
- now,
102
- );
103
- }
104
-
105
- return groups;
106
- }
107
-
108
- function buildItemRows(
109
- item: ItemRow,
110
- group: ItemGroup | undefined,
111
- maxAttemptsByKind: MaxAttemptsByKind,
112
- blocking: Pick<ItemRows, 'links' | 'blockerStates'>,
113
- ): ItemRows {
114
- return {
115
- ...blocking,
116
- levers: group?.levers ?? [],
117
- attempts: group?.attempts ?? [],
118
- liveLease: group?.liveLease ?? null,
119
- pullRequests: group?.pullRequests ?? [],
120
- maxAttempts: pickMaxAttempts(item.kind, maxAttemptsByKind),
121
- };
122
- }
123
-
124
- function pickMaxAttempts(kind: ItemKindName, maxAttemptsByKind: MaxAttemptsByKind): number {
125
- const [first] = maxAttemptsByKind.get(kind)?.values() ?? [];
126
-
127
- return first ?? Number.POSITIVE_INFINITY;
128
- }
@@ -1,76 +0,0 @@
1
- import type { AttemptRow, DerivedState, ItemRow, ItemRows } from './types/core.ts';
2
-
3
- interface StateInput {
4
- readonly item: ItemRow;
5
- readonly rows: ItemRows;
6
-
7
- // the succeeded and failed attempts that ended after the item's latest `item.retry` lever
8
- readonly counted: readonly AttemptRow[];
9
- }
10
-
11
- interface StateRule {
12
- readonly derived: DerivedState;
13
- readonly holds: (input: StateInput) => boolean;
14
- }
15
-
16
- const stateRules: readonly StateRule[] = [
17
- { derived: { state: 'archived' }, holds: (input) => input.item.closedAt !== null },
18
- { derived: { state: 'done' }, holds: (input) => hasMergedPullRequest(input.rows) },
19
- { derived: { state: 'running' }, holds: (input) => input.rows.liveLease !== null },
20
- { derived: { state: 'waiting', reason: 'PR open' }, holds: hasOpenPullRequest },
21
- { derived: { state: 'waiting', reason: 'no PR linked' }, holds: hasSucceededWithoutPullRequest },
22
- { derived: { state: 'waiting', reason: 'attempts used up' }, holds: hasUsedUpAttempts },
23
- { derived: { state: 'idea' }, holds: (input) => input.item.acceptance.length === 0 },
24
- { derived: { state: 'blocked' }, holds: (input) => hasOpenBlocker(input.rows) },
25
- ];
26
-
27
- export function deriveItemState(item: ItemRow, rows: ItemRows): DerivedState {
28
- const input = { item, rows, counted: collectCountedAttempts(rows) };
29
-
30
- return stateRules.find((rule) => rule.holds(input))?.derived ?? { state: 'ready' };
31
- }
32
-
33
- function collectCountedAttempts(rows: ItemRows): AttemptRow[] {
34
- const retryAt = Math.max(
35
- Number.NEGATIVE_INFINITY,
36
- ...rows.levers.filter((lever) => lever.kind === 'item.retry').map((lever) => lever.at),
37
- );
38
-
39
- return rows.attempts.filter((attempt) => attempt.endedAt !== null && attempt.endedAt > retryAt);
40
- }
41
-
42
- function hasMergedPullRequest(rows: ItemRows): boolean {
43
- const succeeded = new Set(collectSucceeded(rows.attempts).map((attempt) => attempt.id));
44
-
45
- return rows.pullRequests.some((pr) => pr.state === 'merged' && succeeded.has(pr.attempt));
46
- }
47
-
48
- function collectSucceeded(attempts: readonly AttemptRow[]): AttemptRow[] {
49
- return attempts.filter((attempt) => attempt.state === 'succeeded');
50
- }
51
-
52
- function hasOpenPullRequest(input: StateInput): boolean {
53
- const succeeded = new Set(collectSucceeded(input.counted).map((attempt) => attempt.id));
54
-
55
- return input.rows.pullRequests.some((pr) => pr.state === 'open' && succeeded.has(pr.attempt));
56
- }
57
-
58
- function hasSucceededWithoutPullRequest(input: StateInput): boolean {
59
- const linked = new Set(input.rows.pullRequests.map((pr) => pr.attempt));
60
-
61
- return collectSucceeded(input.counted).some((attempt) => !linked.has(attempt.id));
62
- }
63
-
64
- function hasUsedUpAttempts(input: StateInput): boolean {
65
- const failed = input.counted.filter((attempt) => attempt.state === 'failed');
66
-
67
- return failed.length >= input.rows.maxAttempts;
68
- }
69
-
70
- function hasOpenBlocker(rows: ItemRows): boolean {
71
- return rows.links.some((link) => {
72
- const blockerState = rows.blockerStates.get(link.source);
73
-
74
- return link.type === 'blocks' && blockerState !== 'done' && blockerState !== 'archived';
75
- });
76
- }
@@ -1,132 +0,0 @@
1
- import { z } from 'zod';
2
- import { actorRefSchema } from './actor-ref-schema.ts';
3
- import { brandSchemas } from './brand-schemas.ts';
4
- import { buildCloseOutSchema } from './build-close-out-schema.ts';
5
- import { frictionSchema } from './friction-schema.ts';
6
- import type { FactType, SliceOneFacts } from './types/facts.ts';
7
-
8
- const criterionSchema = z.object({
9
- id: brandSchemas.criterionId,
10
- text: z.string().min(1),
11
- evidence: z.array(brandSchemas.factId),
12
- });
13
-
14
- const linkPayloadSchema = z.object({
15
- source: brandSchemas.itemId,
16
- target: brandSchemas.itemId,
17
- type: brandSchemas.linkTypeName,
18
- });
19
-
20
- const faultSchema = z.object({
21
- plugin: z.string().min(1),
22
- kind: z.enum(['signal', 'gate', 'waiver', 'admission', 'propagation', 'filter-field']),
23
- item: brandSchemas.itemId.nullable(),
24
- error: z.string(),
25
- });
26
-
27
- const copiedEvidenceSchema = z.object({
28
- criterion: brandSchemas.criterionId,
29
- evidence: z.array(brandSchemas.factId),
30
- });
31
-
32
- const pullRequestNumberSchema = z.number().int().positive();
33
-
34
- export const factSchemas: { readonly [T in FactType]: z.ZodType<SliceOneFacts[T]> } = {
35
- 'boxxy.item.filed': z.object({
36
- id: brandSchemas.itemId,
37
- kind: brandSchemas.itemKindName,
38
- repo: brandSchemas.repoName,
39
- title: z.string().min(1),
40
- outcome: z.string().min(1),
41
- acceptance: z.array(criterionSchema),
42
- foundInAttempt: brandSchemas.attemptId.nullable(),
43
- filedBy: actorRefSchema,
44
- filedAt: brandSchemas.instant,
45
- closedAt: brandSchemas.instant.nullable(),
46
- closeReason: z.string().nullable(),
47
- }),
48
- 'boxxy.item.edited': z
49
- .object({
50
- title: z.string().min(1).exactOptional(),
51
- outcome: z.string().min(1).exactOptional(),
52
- acceptance: z.array(criterionSchema).exactOptional(),
53
- })
54
- .refine((fields) => Object.keys(fields).length > 0, 'an edit changes at least one field'),
55
- 'boxxy.item.closed': z.object({ reason: z.string().min(1) }),
56
- 'boxxy.link.added': linkPayloadSchema,
57
- 'boxxy.link.removed': linkPayloadSchema,
58
- 'boxxy.lever.recorded': z.object({
59
- seq: z.number().int().nonnegative(),
60
- kind: z.templateLiteral([z.string().min(1), '.', z.string().min(1)]),
61
- item: brandSchemas.itemId,
62
- until: brandSchemas.instant.nullable(),
63
- expiresAt: brandSchemas.instant.nullable(),
64
- reason: z.string().nullable(),
65
- actor: actorRefSchema,
66
- at: brandSchemas.instant,
67
- }),
68
- 'boxxy.attempt.claimed': z.object({
69
- item: brandSchemas.itemId,
70
- phase: brandSchemas.phaseName,
71
- number: z.number().int().positive(),
72
- pool: brandSchemas.poolName,
73
- slot: z.number().int().nonnegative(),
74
- gen: z.number().int().nonnegative(),
75
- recipeHash: brandSchemas.recipeHash,
76
- rulesVersion: brandSchemas.rulesVersion,
77
- }),
78
- 'boxxy.decision.recorded': z.object({
79
- id: brandSchemas.decisionId,
80
- attempt: brandSchemas.attemptId,
81
- item: brandSchemas.itemId,
82
- pool: brandSchemas.poolName,
83
- rulesVersion: brandSchemas.rulesVersion,
84
- snapshotHash: brandSchemas.snapshotHash,
85
- storeVersion: z.number().int().nonnegative(),
86
- pepysOffsets: z.record(brandSchemas.streamName, z.number().int().nonnegative()),
87
- faults: z.array(faultSchema),
88
- at: brandSchemas.instant,
89
- }),
90
- 'boxxy.attempt.started': z.object({ atcSession: brandSchemas.atcSessionId }),
91
- 'boxxy.attempt.reconciling': z.object({ reason: z.string().min(1) }),
92
- 'boxxy.attempt.reconciled': z.object({ atcSession: brandSchemas.atcSessionId }),
93
- 'boxxy.attempt.session_bound': z.object({
94
- atcSession: brandSchemas.atcSessionId,
95
- agentSession: brandSchemas.agentSessionId,
96
- seenAt: brandSchemas.instant,
97
- }),
98
- 'boxxy.attempt.closed_out': z.object({ closeOut: buildCloseOutSchema }),
99
- 'boxxy.attempt.settled': z.object({
100
- state: z.enum(['succeeded', 'failed']),
101
- failure: z.string().min(1).nullable(),
102
- endedAt: brandSchemas.instant,
103
- criteria: z.array(copiedEvidenceSchema),
104
- matched: z.array(brandSchemas.itemId),
105
- frictionDropped: z.number().int().nonnegative(),
106
- }),
107
- 'boxxy.evidence.recorded': z.object({
108
- kind: z.enum(['pr', 'commit', 'test', 'ci-run', 'live-check']),
109
- ref: z.string().min(1),
110
- observed: z.string().nullable(),
111
- }),
112
- 'boxxy.evidence.pr_linked': z.object({
113
- repo: brandSchemas.repoName,
114
- number: pullRequestNumberSchema,
115
- url: z.url(),
116
- }),
117
- 'boxxy.evidence.commit_linked': z.object({
118
- repo: brandSchemas.repoName,
119
- sha: brandSchemas.commitSha,
120
- pr: pullRequestNumberSchema,
121
- }),
122
- 'boxxy.pr.merged': z.object({
123
- repo: brandSchemas.repoName,
124
- number: pullRequestNumberSchema,
125
- mergedAt: brandSchemas.instant,
126
- mergeCommit: brandSchemas.commitSha,
127
- }),
128
- 'boxxy.pr.closed': z.object({ repo: brandSchemas.repoName, number: pullRequestNumberSchema }),
129
- 'boxxy.proposal.recorded': frictionSchema,
130
- 'boxxy.runner.paused': z.object({ runner: brandSchemas.runnerId }),
131
- 'boxxy.runner.resumed': z.object({ runner: brandSchemas.runnerId }),
132
- };
@@ -1,12 +0,0 @@
1
- import invariant from 'tiny-invariant';
2
- import type { RowWrite } from './types/ports.ts';
3
-
4
- export type SlotTake = Extract<RowWrite, { op: 'take-slot' }>;
5
-
6
- export function findSlotTake(writes: readonly RowWrite[]): SlotTake | null {
7
- const takes = writes.filter((write): write is SlotTake => write.op === 'take-slot');
8
-
9
- invariant(takes.length <= 1, 'a change takes one slot at most');
10
-
11
- return takes.at(0) ?? null;
12
- }
@@ -1,10 +0,0 @@
1
- import { z } from 'zod';
2
- import type { Friction } from './types/facts.ts';
3
-
4
- export const frictionSchema: z.ZodType<Friction> = z.object({
5
- target: z.object({
6
- kind: z.enum(['skill', 'rule', 'lint', 'agents-md', 'tool']),
7
- name: z.string().min(1),
8
- }),
9
- change: z.string().min(1),
10
- });
@@ -1,25 +0,0 @@
1
- import { z } from 'zod';
2
- import { actorRefSchema } from './actor-ref-schema.ts';
3
- import { brandSchemas } from './brand-schemas.ts';
4
- import type { OutboxFact } from './types/facts.ts';
5
-
6
- const idSchema = z.string();
7
-
8
- const entityRefSchema = z.union([
9
- z.templateLiteral(['boxxy:item:', idSchema]),
10
- z.templateLiteral(['boxxy:link:', idSchema, ':', idSchema, ':', idSchema]),
11
- z.templateLiteral(['boxxy:attempt:', idSchema]),
12
- z.templateLiteral(['boxxy:decision:', idSchema]),
13
- z.templateLiteral(['boxxy:runner:', idSchema]),
14
- ]);
15
-
16
- export const outboxFactSchema: z.ZodType<OutboxFact> = z.object({
17
- seq: z.number().int().positive(),
18
- factId: brandSchemas.factId,
19
- type: z.string().min(1),
20
- entity: entityRefSchema,
21
- actor: actorRefSchema,
22
- at: brandSchemas.instant,
23
- payload: z.json(),
24
- sentAt: brandSchemas.instant.nullable(),
25
- });
@@ -1,89 +0,0 @@
1
- import { contentHashSchema } from '@boxxy/content-hash';
2
- import { z } from 'zod';
3
- import { actorRefSchema } from './actor-ref-schema.ts';
4
- import { brandSchemas } from './brand-schemas.ts';
5
- import { buildCloseOutSchema } from './build-close-out-schema.ts';
6
- import { factSchemas } from './fact-schemas.ts';
7
- import type { TableName, Tables } from './types/ports.ts';
8
-
9
- const countSchema = z.number().int().nonnegative();
10
-
11
- export const rowSchemas: { readonly [T in TableName]: z.ZodType<Tables[T]> } = {
12
- items: factSchemas['boxxy.item.filed'],
13
- links: z.object({
14
- source: brandSchemas.itemId,
15
- target: brandSchemas.itemId,
16
- type: brandSchemas.linkTypeName,
17
- addedBy: actorRefSchema,
18
- addedAt: brandSchemas.instant,
19
- removedAt: brandSchemas.instant.nullable(),
20
- }),
21
- levers: factSchemas['boxxy.lever.recorded'],
22
- attempts: z.object({
23
- id: brandSchemas.attemptId,
24
- item: brandSchemas.itemId,
25
- phase: brandSchemas.phaseName,
26
- number: z.number().int().positive(),
27
- runner: brandSchemas.runnerId,
28
- recipeHash: brandSchemas.recipeHash,
29
- rulesVersion: brandSchemas.rulesVersion,
30
- pool: brandSchemas.poolName,
31
- slot: countSchema,
32
- slotGen: countSchema,
33
- leaseGen: countSchema,
34
- spawnRequest: z.json(),
35
- spawnRequestHash: contentHashSchema,
36
- state: z.enum(['starting', 'running', 'reconciling', 'succeeded', 'failed']),
37
- failure: z.string().nullable(),
38
- atcSession: brandSchemas.atcSessionId.nullable(),
39
- closeOut: buildCloseOutSchema.nullable(),
40
- claimedAt: brandSchemas.instant,
41
- startedAt: brandSchemas.instant.nullable(),
42
- endedAt: brandSchemas.instant.nullable(),
43
- }),
44
- attempt_sessions: z.object({
45
- attempt: brandSchemas.attemptId,
46
- agentSession: brandSchemas.agentSessionId,
47
- atcSession: brandSchemas.atcSessionId,
48
- seenAt: brandSchemas.instant,
49
- }),
50
- leases: z.object({
51
- item: brandSchemas.itemId,
52
- phase: brandSchemas.phaseName,
53
- attempt: brandSchemas.attemptId,
54
- gen: countSchema,
55
- takenAt: brandSchemas.instant,
56
- releasedAt: brandSchemas.instant.nullable(),
57
- }),
58
- pool_slots: z.object({
59
- pool: brandSchemas.poolName,
60
- slot: countSchema,
61
- holder: brandSchemas.attemptId.nullable(),
62
- gen: countSchema,
63
- retired: z.boolean(),
64
- }),
65
- decisions: factSchemas['boxxy.decision.recorded'],
66
- evidence: z.object({
67
- factId: brandSchemas.factId,
68
- attempt: brandSchemas.attemptId,
69
- kind: z.enum(['pr', 'commit', 'test', 'ci-run', 'live-check']),
70
- ref: z.string().min(1),
71
- observed: z.string().nullable(),
72
- source: z.enum(['hook', 'trailer', 'agent', 'watch']),
73
- at: brandSchemas.instant,
74
- }),
75
- pull_requests: z.object({
76
- repo: brandSchemas.repoName,
77
- number: z.number().int().positive(),
78
- attempt: brandSchemas.attemptId,
79
- state: z.enum(['open', 'closed', 'merged']),
80
- mergedAt: brandSchemas.instant.nullable(),
81
- mergeCommit: brandSchemas.commitSha.nullable(),
82
- checkedAt: brandSchemas.instant,
83
- }),
84
- runners: z.object({
85
- id: brandSchemas.runnerId,
86
- heartbeatAt: brandSchemas.instant,
87
- paused: z.boolean(),
88
- }),
89
- };
@@ -1,14 +0,0 @@
1
- import type { ItemRows } from '../types/core.ts';
2
-
3
- export function buildMockItemRows(overrides: Partial<ItemRows> = {}): ItemRows {
4
- return {
5
- links: [],
6
- blockerStates: new Map(),
7
- levers: [],
8
- attempts: [],
9
- liveLease: null,
10
- pullRequests: [],
11
- maxAttempts: 2,
12
- ...overrides,
13
- };
14
- }
@@ -1,8 +0,0 @@
1
- import type { ItemView } from '../types/core.ts';
2
- import { buildMockItemRow } from './build-mock-item-row.ts';
3
-
4
- export function buildMockItemView(overrides: Partial<ItemView> = {}): ItemView {
5
- const { derived, ...row } = overrides;
6
-
7
- return { ...buildMockItemRow(row), derived: derived ?? { state: 'ready' } };
8
- }
@@ -1,16 +0,0 @@
1
- import { faker } from '@faker-js/faker';
2
- import { brandSchemas } from '../brand-schemas.ts';
3
- import type { PullRequestRow } from '../types/core.ts';
4
-
5
- export function buildMockPullRequestRow(overrides: Partial<PullRequestRow> = {}): PullRequestRow {
6
- return {
7
- repo: brandSchemas.repoName.parse(`${faker.string.alpha(8)}/${faker.string.alpha(8)}`),
8
- number: faker.number.int({ min: 1, max: 10_000 }),
9
- attempt: brandSchemas.attemptId.parse(faker.string.uuid({ version: 7 })),
10
- state: 'open',
11
- mergedAt: null,
12
- mergeCommit: null,
13
- checkedAt: brandSchemas.instant.parse(faker.number.int({ max: 1_800_000_000_000 })),
14
- ...overrides,
15
- };
16
- }
@@ -1,95 +0,0 @@
1
- import type { ContentHash } from '@boxxy/content-hash';
2
- import type {
3
- AttemptId,
4
- AttemptRow,
5
- CriterionId,
6
- EvidenceRow,
7
- FactId,
8
- ForcedFailure,
9
- Instant,
10
- ItemView,
11
- KnownFailure,
12
- LiveAttemptState,
13
- Minutes,
14
- RecipeName,
15
- RepoName,
16
- } from './core.ts';
17
- import type { Friction, OpenItem } from './facts.ts';
18
- import type { SessionReading, SpawnRequest } from './ports.ts';
19
-
20
- export interface SpawnContext {
21
- readonly repoPaths: ReadonlyMap<RepoName, string>;
22
- readonly pluginFolder: { readonly path: string; readonly hash: ContentHash };
23
- readonly promptTemplates: ReadonlyMap<RecipeName, string>;
24
- }
25
-
26
- export interface Claim {
27
- readonly attempt: AttemptRow;
28
- readonly spawn: SpawnRequest;
29
- }
30
-
31
- export type ReconcileAction =
32
- | {
33
- readonly kind: 'move-to-running';
34
- readonly fact: 'boxxy.attempt.started' | 'boxxy.attempt.reconciled';
35
- }
36
- | { readonly kind: 'bind-session' }
37
- | { readonly kind: 'settle' }
38
- | { readonly kind: 'fail'; readonly failure: Extract<KnownFailure, 'session-lost'> }
39
- | {
40
- readonly kind: 'respawn';
41
- readonly giveUpAfter: Minutes;
42
- readonly then: Extract<KnownFailure, 'spawn-lost'>;
43
- };
44
-
45
- export type ReconcileTable = Readonly<
46
- Record<SessionReading['kind'], Readonly<Record<LiveAttemptState, ReconcileAction>>>
47
- >;
48
-
49
- export interface Settlement {
50
- readonly state: 'succeeded' | 'failed';
51
- readonly failure: string | null;
52
- readonly openItems: readonly OpenItem[];
53
- readonly criteria: readonly {
54
- readonly criterion: CriterionId;
55
- readonly evidence: readonly FactId[];
56
- }[];
57
- readonly friction: readonly Friction[];
58
- readonly frictionDropped: number;
59
- }
60
-
61
- export interface SettleRule<Out = unknown> {
62
- settle: (input: SettleInput<Out>) => Settlement;
63
- }
64
-
65
- export interface SettleInput<Out> {
66
- readonly attempt: AttemptRow;
67
- readonly item: ItemView;
68
- readonly closeOut: CloseOutParse<Out> | null;
69
-
70
- // a `forced` reason wins over the close-out's state
71
- readonly forced: ForcedFailure | null;
72
- readonly now: Instant;
73
- }
74
-
75
- export interface CloseOutContext {
76
- readonly item: ItemView;
77
- readonly attempt: AttemptId;
78
-
79
- // the attempt's own evidence rows
80
- readonly evidence: readonly EvidenceRow[];
81
- }
82
-
83
- export type CloseOutProblem =
84
- | { readonly kind: 'schema'; readonly path: string; readonly message: string }
85
- | { readonly kind: 'unknown-criterion'; readonly criterion: CriterionId }
86
- | { readonly kind: 'foreign-evidence'; readonly evidence: FactId }
87
- | { readonly kind: 'built-without-evidence'; readonly criteria: readonly CriterionId[] };
88
-
89
- export type CloseOutParse<Out> =
90
- | { readonly kind: 'valid'; readonly closeOut: Out }
91
- | { readonly kind: 'invalid'; readonly problems: readonly CloseOutProblem[] };
92
-
93
- export interface CloseOutParser<Out> {
94
- parse: (raw: unknown, ctx: CloseOutContext) => CloseOutParse<Out>;
95
- }