@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,31 @@
1
+ import { faker } from '@faker-js/faker';
2
+ import type { CostToLand } from '../reports/types.ts';
3
+
4
+ // The cost of a landed item: one build line with a reported cost.
5
+ export function buildMockCostToLand(overrides: Partial<CostToLand> = {}): CostToLand {
6
+ const costUSD = faker.number.float({ min: 0.5, max: 20, fractionDigits: 2 });
7
+ const requests = faker.number.int({ min: 1, max: 80 });
8
+
9
+ return {
10
+ item: `boxxy:item:${faker.string.uuid({ version: 7 })}`,
11
+ landedAt: '2026-11-11T06:00:00Z',
12
+ attempts: 1,
13
+ lines: [
14
+ {
15
+ phase: 'build',
16
+ rework: false,
17
+ product: 'claude_code',
18
+ model: 'model-large',
19
+ requests,
20
+ inputTokens: requests * 10,
21
+ cacheReadTokens: requests * 1000,
22
+ cacheCreationTokens: requests * 50,
23
+ outputTokens: requests * 300,
24
+ costUSD,
25
+ costBasis: 'reported',
26
+ },
27
+ ],
28
+ totals: { requests, costUSD, reworkCostUSD: 0, requestsWithoutCost: 0 },
29
+ ...overrides,
30
+ };
31
+ }
@@ -1,6 +1,6 @@
1
1
  import { faker } from '@faker-js/faker';
2
- import { brandSchemas } from '../brand-schemas.ts';
3
- import type { EvidenceRow } from '../types/core.ts';
2
+ import { brandSchemas } from '../rows/brand-schemas.ts';
3
+ import type { EvidenceRow } from '../types.ts';
4
4
  import { buildMockFactId } from './build-mock-fact-id.ts';
5
5
 
6
6
  export function buildMockEvidenceRow(overrides: Partial<EvidenceRow> = {}): EvidenceRow {
@@ -1,7 +1,7 @@
1
1
  import { faker } from '@faker-js/faker';
2
- import { brandSchemas } from '../brand-schemas.ts';
3
- import type { FactId } from '../types/core.ts';
2
+ import { brandSchemas } from '../rows/brand-schemas.ts';
3
+ import type { FactId } from '../types.ts';
4
4
 
5
5
  export function buildMockFactId(): FactId {
6
- return brandSchemas.factId.parse(`manual:${faker.string.uuid({ version: 7 })}`);
6
+ return brandSchemas.factId.parse(faker.string.uuid({ version: 7 }));
7
7
  }
@@ -1,9 +1,14 @@
1
- import { brandSchemas } from '../brand-schemas.ts';
2
- import type { FrameItem } from '../types/planning.ts';
3
- import { buildMockItemView } from './build-mock-item-view.ts';
1
+ import type { FrameItem } from '../planning/types.ts';
2
+ import { brandSchemas } from '../rows/brand-schemas.ts';
3
+ import { buildMockV1ItemView } from './build-mock-v1-item-view.ts';
4
4
 
5
+ // The item's position is the phase and round of the frame item, unless an override sets it.
5
6
  export function buildMockFrameItem(overrides: Partial<FrameItem> = {}): FrameItem {
6
- const { phase, ...view } = overrides;
7
+ const { phase = brandSchemas.phaseName.parse('build'), round = 1, ...view } = overrides;
7
8
 
8
- return { ...buildMockItemView(view), phase: phase ?? brandSchemas.phaseName.parse('build') };
9
+ return {
10
+ ...buildMockV1ItemView({ position: { kind: 'phase', phase, round }, ...view }),
11
+ phase,
12
+ round,
13
+ };
9
14
  }
@@ -0,0 +1,13 @@
1
+ import type { Frame } from '../planning/types.ts';
2
+
3
+ export function buildMockFrame(overrides: Partial<Frame> = {}): Frame {
4
+ return {
5
+ candidates: [],
6
+ fields: new Map(),
7
+ values: new Map(),
8
+ holds: new Map(),
9
+ lifted: new Map(),
10
+ faults: [],
11
+ ...overrides,
12
+ };
13
+ }
@@ -0,0 +1,17 @@
1
+ import { brandSchemas } from '../rows/brand-schemas.ts';
2
+ import type { ItemRules } from '../rules/types.ts';
3
+ import { buildMockPipeline } from './build-mock-pipeline.ts';
4
+
5
+ // `core.issue` and `core.task` run the build phase alone; no phase needs acceptance criteria.
6
+ export function buildMockItemRules(overrides: Partial<ItemRules> = {}): ItemRules {
7
+ const pipeline = buildMockPipeline({ phases: ['build'] });
8
+
9
+ return {
10
+ kinds: new Map([
11
+ [brandSchemas.itemKindName.parse('core.issue'), { pipeline }],
12
+ [brandSchemas.itemKindName.parse('core.task'), { pipeline }],
13
+ ]),
14
+ phases: new Map(),
15
+ ...overrides,
16
+ };
17
+ }
@@ -1,6 +1,6 @@
1
1
  import { faker } from '@faker-js/faker';
2
- import { brandSchemas } from '../brand-schemas.ts';
3
- import type { LeaseRow } from '../types/core.ts';
2
+ import { brandSchemas } from '../rows/brand-schemas.ts';
3
+ import type { LeaseRow } from '../types.ts';
4
4
 
5
5
  export function buildMockLeaseRow(overrides: Partial<LeaseRow> = {}): LeaseRow {
6
6
  return {
@@ -0,0 +1,22 @@
1
+ import type { OutboxFact } from '../facts/types.ts';
2
+ import { brandSchemas } from '../rows/brand-schemas.ts';
3
+ import { buildMockFactId } from './build-mock-fact-id.ts';
4
+
5
+ // An unsent, unparked `boxxy.item.noted` row for the item `i-<seq>`.
6
+ export function buildMockOutboxFact(overrides: Partial<OutboxFact> = {}): OutboxFact {
7
+ const seq = overrides.seq ?? 1;
8
+
9
+ return {
10
+ seq,
11
+ factId: buildMockFactId(),
12
+ type: 'boxxy.item.noted',
13
+ entity: `boxxy:item:i-${seq}`,
14
+ actor: 'boxxy:owner:local',
15
+ at: brandSchemas.instant.parse(1_800_000_000_000 + seq),
16
+ payload: { seq, text: 'a note' },
17
+ sentAt: null,
18
+ parkedAt: null,
19
+ parkedReason: null,
20
+ ...overrides,
21
+ };
22
+ }
@@ -0,0 +1,29 @@
1
+ import { brandSchemas } from '../rows/brand-schemas.ts';
2
+ import { definePhase } from '../rules/define-phase.ts';
3
+ import type { PhaseKind } from '../rules/types.ts';
4
+
5
+ interface MockPhaseKindInput {
6
+ readonly name: string;
7
+ readonly outcomes: readonly string[];
8
+ }
9
+
10
+ // A phase kind with only a name and an outcome list: its close-out is any value, and it settles
11
+ // with its first outcome.
12
+ export function buildMockPhaseKind(input: MockPhaseKindInput): PhaseKind {
13
+ return definePhase({
14
+ name: brandSchemas.phaseName.parse(input.name),
15
+ version: 1,
16
+ worker: brandSchemas.workerKindName.parse('atc.session'),
17
+ outcomes: input.outcomes,
18
+ needsAcceptance: false,
19
+ input: () => ({}),
20
+ closeOut: { parse: (raw) => ({ kind: 'valid', closeOut: raw }), jsonSchema: {} },
21
+ settle: () => ({
22
+ state: 'succeeded',
23
+ failure: null,
24
+ outcome: input.outcomes[0] ?? '',
25
+ effects: [],
26
+ frictionDropped: 0,
27
+ }),
28
+ });
29
+ }
@@ -0,0 +1,52 @@
1
+ import { brandSchemas } from '../rows/brand-schemas.ts';
2
+ import type { Edge, Pipeline, PipelineStep } from '../rules/types.ts';
3
+
4
+ const standardOrder = ['triage', 'design', 'build', 'review'];
5
+
6
+ const standardEdges: Readonly<Record<string, Readonly<Record<string, Edge>>>> = {
7
+ triage: {
8
+ accepted: 'next',
9
+ duplicate: { hold: brandSchemas.rulesHoldReason.parse('duplicate') },
10
+ declined: { hold: brandSchemas.rulesHoldReason.parse('declined') },
11
+ },
12
+ design: {
13
+ designed: 'next',
14
+ blocked: { hold: brandSchemas.rulesHoldReason.parse('blocked') },
15
+ abandoned: 'again',
16
+ },
17
+ build: {
18
+ built: 'next',
19
+ blocked: { hold: brandSchemas.rulesHoldReason.parse('blocked') },
20
+ abandoned: 'again',
21
+ },
22
+ review: {
23
+ passed: { end: 'reviewed' },
24
+ 'changes-requested': { to: brandSchemas.phaseName.parse('build'), max: 3, resume: true },
25
+ },
26
+ };
27
+
28
+ interface MockPipelineInput {
29
+ // the phases in order; the four standard phases when left out
30
+ readonly phases?: readonly string[];
31
+
32
+ // fields to replace on the step of a phase; `on` is replaced whole
33
+ readonly steps?: Readonly<Record<string, Partial<PipelineStep>>>;
34
+ }
35
+
36
+ // The `standard` pipeline of the v1 rules file: triage, design, build and review, with two
37
+ // attempts in a row for each step.
38
+ export function buildMockPipeline(input: MockPipelineInput = {}): Pipeline {
39
+ const steps: PipelineStep[] = [];
40
+
41
+ for (const phase of input.phases ?? standardOrder) {
42
+ steps.push({
43
+ phase: brandSchemas.phaseName.parse(phase),
44
+ maxAttempts: 2,
45
+ timeoutMinutes: 30,
46
+ on: standardEdges[phase] ?? {},
47
+ ...input.steps?.[phase],
48
+ });
49
+ }
50
+
51
+ return { name: brandSchemas.pipelineName.parse('standard'), steps };
52
+ }
@@ -1,12 +1,12 @@
1
1
  import { faker } from '@faker-js/faker';
2
- import { brandSchemas } from '../brand-schemas.ts';
3
- import type { LinkRow } from '../types/core.ts';
2
+ import { brandSchemas } from '../rows/brand-schemas.ts';
3
+ import type { RelationRow } from '../types.ts';
4
4
 
5
- export function buildMockLinkRow(overrides: Partial<LinkRow> = {}): LinkRow {
5
+ export function buildMockRelationRow(overrides: Partial<RelationRow> = {}): RelationRow {
6
6
  return {
7
7
  source: brandSchemas.itemId.parse(faker.string.uuid({ version: 7 })),
8
8
  target: brandSchemas.itemId.parse(faker.string.uuid({ version: 7 })),
9
- type: brandSchemas.linkTypeName.parse('blocks'),
9
+ type: brandSchemas.relationTypeName.parse('blocks'),
10
10
  addedBy: 'boxxy:owner:local',
11
11
  addedAt: brandSchemas.instant.parse(faker.number.int({ max: 1_800_000_000_000 })),
12
12
  removedAt: null,
@@ -0,0 +1,18 @@
1
+ import type { Settled } from '../pipeline/types.ts';
2
+ import { brandSchemas } from '../rows/brand-schemas.ts';
3
+
4
+ interface MockSettledInput {
5
+ readonly phase: string;
6
+ readonly outcome: string;
7
+ readonly endedAt: number;
8
+ readonly failure?: string | null;
9
+ }
10
+
11
+ export function buildMockSettled(input: MockSettledInput): Settled {
12
+ return {
13
+ phase: brandSchemas.phaseName.parse(input.phase),
14
+ outcome: input.outcome,
15
+ endedAt: brandSchemas.instant.parse(input.endedAt),
16
+ failure: input.failure ?? null,
17
+ };
18
+ }
@@ -1,5 +1,13 @@
1
- import type { Snapshot } from '../types/planning.ts';
1
+ import type { Snapshot } from '../planning/types.ts';
2
2
 
3
3
  export function buildMockSnapshot(overrides: Partial<Snapshot> = {}): Snapshot {
4
- return { storeVersion: 1, items: [], links: [], levers: [], freeSlots: {}, ...overrides };
4
+ return {
5
+ storeVersion: 1,
6
+ items: [],
7
+ relations: [],
8
+ levers: [],
9
+ limits: [],
10
+ wakes: [],
11
+ ...overrides,
12
+ };
5
13
  }
@@ -1,11 +1,13 @@
1
1
  import { contentHash } from '@boxxy/content-hash';
2
2
  import { faker } from '@faker-js/faker';
3
- import { brandSchemas } from '../brand-schemas.ts';
4
- import type { AttemptRow } from '../types/core.ts';
3
+ import { brandSchemas } from '../rows/brand-schemas.ts';
4
+ import type { AttemptRow } from '../types.ts';
5
5
 
6
6
  const hashOptions = { length: 64, casing: 'lower', prefix: '' } as const;
7
7
 
8
- export function buildMockAttemptRow(overrides: Partial<AttemptRow> = {}): AttemptRow {
8
+ // A running agent-mode build attempt of round 1. `buildMockAttemptRow` still builds the slice 1
9
+ // row that the store reads and writes.
10
+ export function buildMockV1AttemptRow(overrides: Partial<AttemptRow> = {}): AttemptRow {
9
11
  const spawnRequest =
10
12
  overrides.spawnRequest === undefined
11
13
  ? { prompt: faker.lorem.sentence() }
@@ -19,19 +21,36 @@ export function buildMockAttemptRow(overrides: Partial<AttemptRow> = {}): Attemp
19
21
  runner: brandSchemas.runnerId.parse('runner-1'),
20
22
  recipeHash: brandSchemas.recipeHash.parse(faker.string.hexadecimal(hashOptions)),
21
23
  rulesVersion: brandSchemas.rulesVersion.parse(faker.string.hexadecimal(hashOptions)),
22
- pool: brandSchemas.poolName.parse('build'),
23
- slot: 1,
24
- slotGen: 1,
24
+ pool: brandSchemas.poolName.parse('main'),
25
25
  leaseGen: 1,
26
26
  spawnRequest,
27
27
  spawnRequestHash: contentHash(spawnRequest),
28
- state: 'succeeded',
28
+ state: 'running',
29
29
  failure: null,
30
30
  atcSession: brandSchemas.atcSessionId.parse(faker.string.uuid()),
31
- closeOut: null,
31
+ caller: null,
32
32
  claimedAt: brandSchemas.instant.parse(faker.number.int({ max: 1_800_000_000_000 })),
33
33
  startedAt: brandSchemas.instant.parse(faker.number.int({ max: 1_800_000_000_000 })),
34
- endedAt: brandSchemas.instant.parse(faker.number.int({ max: 1_800_000_000_000 })),
34
+ endedAt: null,
35
+ fields: {},
36
+ mode: 'agent',
37
+ round: 1,
38
+ input: {},
39
+ holdGen: 1,
40
+ closeOut: null,
41
+ closeOutHash: null,
42
+ closeOutFact: null,
43
+ closing: null,
44
+ closingAt: null,
45
+ suspendRequestedAt: null,
46
+ sleptAt: null,
47
+ wakeRequestedAt: null,
48
+ wokenAt: null,
49
+ outcome: null,
50
+ returnedOutcome: null,
51
+ strayedAt: null,
52
+ sessionStoppedAt: null,
53
+ sessionForgottenAt: null,
35
54
  ...overrides,
36
55
  };
37
56
  }
@@ -1,8 +1,9 @@
1
1
  import { faker } from '@faker-js/faker';
2
- import { brandSchemas } from '../brand-schemas.ts';
3
- import type { ItemRow } from '../types/core.ts';
2
+ import { brandSchemas } from '../rows/brand-schemas.ts';
3
+ import type { ItemRow } from '../types.ts';
4
4
 
5
- export function buildMockItemRow(overrides: Partial<ItemRow> = {}): ItemRow {
5
+ // An open issue with one acceptance criterion, no labels and no kept session.
6
+ export function buildMockV1ItemRow(overrides: Partial<ItemRow> = {}): ItemRow {
6
7
  return {
7
8
  id: brandSchemas.itemId.parse(faker.string.uuid({ version: 7 })),
8
9
  kind: brandSchemas.itemKindName.parse('core.issue'),
@@ -12,11 +13,14 @@ export function buildMockItemRow(overrides: Partial<ItemRow> = {}): ItemRow {
12
13
  acceptance: [
13
14
  { id: brandSchemas.criterionId.parse('a1'), text: faker.lorem.sentence(), evidence: [] },
14
15
  ],
16
+ fields: {},
17
+ labels: [],
15
18
  foundInAttempt: null,
16
19
  filedBy: 'boxxy:owner:local',
17
20
  filedAt: brandSchemas.instant.parse(faker.number.int({ max: 1_800_000_000_000 })),
18
21
  closedAt: null,
19
22
  closeReason: null,
23
+ keptSession: null,
20
24
  ...overrides,
21
25
  };
22
26
  }
@@ -0,0 +1,29 @@
1
+ import { faker } from '@faker-js/faker';
2
+ import { brandSchemas } from '../rows/brand-schemas.ts';
3
+ import type { ItemView } from '../types.ts';
4
+
5
+ // A ready item at the build phase of round 1. `buildMockItemView` still builds the slice 1 view
6
+ // that the store reads.
7
+ export function buildMockV1ItemView(overrides: Partial<ItemView> = {}): ItemView {
8
+ return {
9
+ id: brandSchemas.itemId.parse(faker.string.uuid({ version: 7 })),
10
+ kind: brandSchemas.itemKindName.parse('core.issue'),
11
+ repo: brandSchemas.repoName.parse(`${faker.word.noun()}/${faker.word.noun()}`),
12
+ title: faker.lorem.sentence(),
13
+ outcome: faker.lorem.sentence(),
14
+ acceptance: [
15
+ { id: brandSchemas.criterionId.parse('a1'), text: faker.lorem.sentence(), evidence: [] },
16
+ ],
17
+ fields: {},
18
+ labels: [],
19
+ foundInAttempt: null,
20
+ filedBy: 'boxxy:owner:local',
21
+ filedAt: brandSchemas.instant.parse(faker.number.int({ max: 1_800_000_000_000 })),
22
+ closedAt: null,
23
+ closeReason: null,
24
+ keptSession: null,
25
+ derived: { state: 'ready' },
26
+ position: { kind: 'phase', phase: brandSchemas.phaseName.parse('build'), round: 1 },
27
+ ...overrides,
28
+ };
29
+ }
@@ -1,8 +1,9 @@
1
1
  import { faker } from '@faker-js/faker';
2
- import { brandSchemas } from '../brand-schemas.ts';
3
- import type { LeverRow } from '../types/core.ts';
2
+ import { brandSchemas } from '../rows/brand-schemas.ts';
3
+ import type { LeverRow } from '../types.ts';
4
4
 
5
- export function buildMockLeverRow(overrides: Partial<LeverRow> = {}): LeverRow {
5
+ // A defer lever with no phase.
6
+ export function buildMockV1LeverRow(overrides: Partial<LeverRow> = {}): LeverRow {
6
7
  return {
7
8
  seq: faker.number.int({ min: 1, max: 10_000 }),
8
9
  kind: 'plan.defer',
@@ -10,6 +11,7 @@ export function buildMockLeverRow(overrides: Partial<LeverRow> = {}): LeverRow {
10
11
  until: null,
11
12
  expiresAt: null,
12
13
  reason: faker.lorem.sentence(),
14
+ phase: null,
13
15
  actor: 'boxxy:owner:local',
14
16
  at: brandSchemas.instant.parse(faker.number.int({ max: 1_800_000_000_000 })),
15
17
  ...overrides,
@@ -0,0 +1,26 @@
1
+ import { faker } from '@faker-js/faker';
2
+ import { brandSchemas } from '../rows/brand-schemas.ts';
3
+ import type { PullRequestRow } from '../types.ts';
4
+
5
+ // An open pull request, with its URL built from its repo and number. `buildMockPullRequestRow`
6
+ // still builds the slice 1 row.
7
+ export function buildMockV1PullRequestRow(overrides: Partial<PullRequestRow> = {}): PullRequestRow {
8
+ const repo =
9
+ overrides.repo ??
10
+ brandSchemas.repoName.parse(`${faker.string.alpha(8)}/${faker.string.alpha(8)}`);
11
+
12
+ const number = overrides.number ?? faker.number.int({ min: 1, max: 10_000 });
13
+
14
+ return {
15
+ repo,
16
+ number,
17
+ attempt: brandSchemas.attemptId.parse(faker.string.uuid({ version: 7 })),
18
+ githubId: faker.number.int({ min: 1, max: 2_000_000_000 }),
19
+ url: `https://github.com/${repo}/pull/${number}`,
20
+ state: 'open',
21
+ mergedAt: null,
22
+ mergeCommit: null,
23
+ checkedAt: brandSchemas.instant.parse(faker.number.int({ max: 1_800_000_000_000 })),
24
+ ...overrides,
25
+ };
26
+ }
@@ -0,0 +1,179 @@
1
+ import { contentHash } from '@boxxy/content-hash';
2
+ import { faker } from '@faker-js/faker';
3
+ import type { z } from 'zod';
4
+ import type { EntityRef, OutboxFact, WorkFacts } from '../facts/types.ts';
5
+ import { workFactSchemas } from '../facts/work-fact-schemas.ts';
6
+ import { brandSchemas } from '../rows/brand-schemas.ts';
7
+ import type { Instant } from '../types.ts';
8
+ import { toJSONValue } from '../utils/to-json-value.ts';
9
+ import { buildMockFactId } from './build-mock-fact-id.ts';
10
+
11
+ // The fact types that the pepys consumer reads.
12
+ export type MockedFactType =
13
+ | 'boxxy.item.filed'
14
+ | 'boxxy.item.edited'
15
+ | 'boxxy.item.closed'
16
+ | 'boxxy.attempt.claimed'
17
+ | 'boxxy.attempt.started'
18
+ | 'boxxy.attempt.reconciled'
19
+ | 'boxxy.attempt.resumed'
20
+ | 'boxxy.attempt.session_bound'
21
+ | 'boxxy.attempt.slept'
22
+ | 'boxxy.attempt.woken'
23
+ | 'boxxy.attempt.closed_out'
24
+ | 'boxxy.attempt.settled'
25
+ | 'boxxy.pull_request.linked';
26
+
27
+ // The payload's values are checked by the schema, so a test may give a plain string for a brand.
28
+ interface WorkFactOverrides<Type extends MockedFactType> {
29
+ readonly payload?: { readonly [Key in keyof WorkFacts[Type]]?: unknown };
30
+ readonly at?: number;
31
+ readonly entity?: EntityRef;
32
+ readonly seq?: number;
33
+ }
34
+
35
+ const hashOptions = { length: 64, casing: 'lower', prefix: '' } as const;
36
+
37
+ const defaultPayloads: { readonly [Type in MockedFactType]: () => WorkFacts[Type] } = {
38
+ 'boxxy.item.filed': () => ({
39
+ id: brandSchemas.itemId.parse(faker.string.uuid({ version: 7 })),
40
+ kind: brandSchemas.itemKindName.parse('core.issue'),
41
+ repo: brandSchemas.repoName.parse('shoal/charts'),
42
+ title: faker.lorem.sentence(),
43
+ outcome: faker.lorem.sentence(),
44
+ acceptance: [],
45
+ fields: {},
46
+ labels: [],
47
+ foundInAttempt: null,
48
+ filedBy: 'boxxy:owner:local',
49
+ filedAt: toInstant(1_800_000_000_000),
50
+ closedAt: null,
51
+ closeReason: null,
52
+ }),
53
+ 'boxxy.item.edited': () => ({ byAttempt: null }),
54
+ 'boxxy.item.closed': () => ({ reason: 'done', byAttempt: null, ended: [] }),
55
+ 'boxxy.attempt.claimed': () => ({
56
+ attempt: buildAttemptId(),
57
+ phase: brandSchemas.phaseName.parse('build'),
58
+ number: 1,
59
+ round: 1,
60
+ mode: 'agent',
61
+ pool: brandSchemas.poolName.parse('main'),
62
+ fields: {},
63
+ seats: [],
64
+ recipeHash: brandSchemas.recipeHash.parse(faker.string.hexadecimal(hashOptions)),
65
+ skillsRef: null,
66
+ rulesVersion: brandSchemas.rulesVersion.parse(faker.string.hexadecimal(hashOptions)),
67
+ }),
68
+ 'boxxy.attempt.started': () => ({
69
+ attempt: buildAttemptId(),
70
+ atcSession: buildAtcSession(),
71
+ resumeFallback: false,
72
+ caller: null,
73
+ }),
74
+ 'boxxy.attempt.reconciled': () => ({
75
+ attempt: buildAttemptId(),
76
+ atcSession: buildAtcSession(),
77
+ caller: null,
78
+ }),
79
+ 'boxxy.attempt.resumed': () => ({
80
+ attempt: buildAttemptId(),
81
+ atcSession: buildAtcSession(),
82
+ caller: null,
83
+ }),
84
+ 'boxxy.attempt.session_bound': () => ({
85
+ attempt: buildAttemptId(),
86
+ atcSession: buildAtcSession(),
87
+ agentSession: brandSchemas.agentSessionId.parse(`agent-${faker.string.alphanumeric(8)}`),
88
+ product: 'claude_code',
89
+ source: 'hook',
90
+ seenAt: toInstant(1_800_000_000_000),
91
+ }),
92
+ 'boxxy.attempt.slept': () => ({
93
+ attempt: buildAttemptId(),
94
+ idleSince: toInstant(1_800_000_000_000),
95
+ asked: true,
96
+ }),
97
+ 'boxxy.attempt.woken': () => ({
98
+ attempt: buildAttemptId(),
99
+ holdGen: 1,
100
+ seats: [],
101
+ overCap: false,
102
+ }),
103
+ 'boxxy.attempt.closed_out': () => {
104
+ const closeOut = {
105
+ result: { kind: 'accepted' as const },
106
+ relations: [],
107
+ friction: [],
108
+ frictionDropped: 0,
109
+ };
110
+
111
+ return {
112
+ attempt: buildAttemptId(),
113
+ phase: brandSchemas.phaseName.parse('triage'),
114
+ closeOut,
115
+ hash: contentHash(closeOut),
116
+ };
117
+ },
118
+ 'boxxy.attempt.settled': () => ({
119
+ attempt: buildAttemptId(),
120
+ phase: brandSchemas.phaseName.parse('build'),
121
+ round: 1,
122
+ state: 'succeeded',
123
+ failure: null,
124
+ outcome: 'built',
125
+ returnedOutcome: null,
126
+ closing: 'closed-out',
127
+ asleep: false,
128
+ sessionStopped: true,
129
+ endedAt: toInstant(1_800_000_000_000),
130
+ criteria: [],
131
+ matched: [],
132
+ frictionDropped: 0,
133
+ skippedEffects: [],
134
+ }),
135
+ 'boxxy.pull_request.linked': () => ({
136
+ attempt: buildAttemptId(),
137
+ repo: brandSchemas.repoName.parse('shoal/charts'),
138
+ number: 14,
139
+ url: 'https://github.com/shoal/charts/pull/14',
140
+ pullRequestId: '9001',
141
+ source: 'hook',
142
+ }),
143
+ };
144
+
145
+ // An unsent outbox row of the type, with every payload field the test does not give filled with a
146
+ // valid value. The payload passes the type's schema, or the factory throws.
147
+ export function buildMockWorkFact<Type extends MockedFactType>(
148
+ type: Type,
149
+ overrides: WorkFactOverrides<Type> = {},
150
+ ): OutboxFact {
151
+ const schema: z.ZodType = workFactSchemas[type];
152
+ const payload = schema.parse({ ...defaultPayloads[type](), ...overrides.payload });
153
+ const seq = overrides.seq ?? 1;
154
+
155
+ return {
156
+ seq,
157
+ factId: buildMockFactId(),
158
+ type,
159
+ entity: overrides.entity ?? `boxxy:item:${faker.string.uuid({ version: 7 })}`,
160
+ actor: 'boxxy:owner:local',
161
+ at: toInstant(overrides.at ?? 1_800_000_000_000),
162
+ payload: toJSONValue(payload),
163
+ sentAt: null,
164
+ parkedAt: null,
165
+ parkedReason: null,
166
+ };
167
+ }
168
+
169
+ function toInstant(at: number): Instant {
170
+ return brandSchemas.instant.parse(at);
171
+ }
172
+
173
+ function buildAttemptId() {
174
+ return brandSchemas.attemptId.parse(faker.string.uuid({ version: 7 }));
175
+ }
176
+
177
+ function buildAtcSession() {
178
+ return brandSchemas.atcSessionId.parse(`s-${faker.string.alphanumeric(8)}`);
179
+ }