@boxxy/model 0.1.1 → 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 (222) 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/facts/outbox-fact-schema.d.ts +3 -0
  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 -16
  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/rows/row-schemas.d.ts +5 -0
  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 +2 -0
  73. package/dist/test-utils/build-mock-fact-id.d.ts +2 -0
  74. package/dist/test-utils/build-mock-frame-item.d.ts +2 -0
  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 +2 -0
  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} +94 -36
  95. package/dist/utils/format-key-path.d.ts +1 -0
  96. package/dist/utils/to-json-value.d.ts +2 -0
  97. package/package.json +14 -3
  98. package/src/{build-close-out-schema.ts → closeouts/build-close-out-schema.ts} +5 -10
  99. package/src/closeouts/close-out-answer-schema.ts +42 -0
  100. package/src/closeouts/close-out-schema.ts +13 -0
  101. package/src/closeouts/design-close-out-schema.ts +20 -0
  102. package/src/closeouts/friction-schema.ts +11 -0
  103. package/src/closeouts/open-item-schema.ts +8 -0
  104. package/src/closeouts/review-close-out-schema.ts +38 -0
  105. package/src/closeouts/triage-close-out-schema.ts +29 -0
  106. package/src/closeouts/types.ts +230 -0
  107. package/src/dispatch/build-applied-change.ts +57 -0
  108. package/src/dispatch/collect-seat-sync.ts +86 -0
  109. package/src/dispatch/pick-seat-take.ts +39 -0
  110. package/src/dispatch/types.ts +14 -0
  111. package/src/facts/outbox-fact-schema.ts +22 -0
  112. package/src/facts/parse-new-fact.ts +16 -0
  113. package/src/facts/types.ts +235 -0
  114. package/src/facts/work-fact-schemas.ts +201 -0
  115. package/src/index.ts +147 -41
  116. package/src/items/build-item-views.ts +146 -0
  117. package/src/items/collect-retries.ts +12 -0
  118. package/src/items/collect-settled.ts +20 -0
  119. package/src/items/derive-item-state.ts +95 -0
  120. package/src/{live-levers.ts → items/live-levers.ts} +1 -1
  121. package/src/items/types.ts +26 -0
  122. package/src/pipeline/advance.ts +78 -0
  123. package/src/pipeline/constants.ts +12 -0
  124. package/src/pipeline/count-trailing.ts +16 -0
  125. package/src/pipeline/find-position.ts +74 -0
  126. package/src/pipeline/find-step.ts +6 -0
  127. package/src/pipeline/merge-by-time.ts +33 -0
  128. package/src/pipeline/position-schema.ts +27 -0
  129. package/src/pipeline/types.ts +47 -0
  130. package/src/{define-admission.ts → planning/define-admission.ts} +2 -2
  131. package/src/{define-gate.ts → planning/define-gate.ts} +2 -2
  132. package/src/{define-signal.ts → planning/define-signal.ts} +2 -2
  133. package/src/planning/is-filter-match.ts +10 -0
  134. package/src/planning/limit-fill-schema.ts +12 -0
  135. package/src/{signal-key.ts → planning/signal-key.ts} +1 -1
  136. package/src/planning/types.ts +216 -0
  137. package/src/ports/types.ts +412 -0
  138. package/src/reports/constants.ts +6 -0
  139. package/src/reports/cost-to-land-schema.ts +47 -0
  140. package/src/reports/outbox-status.ts +22 -0
  141. package/src/reports/stage-time-schema.ts +21 -0
  142. package/src/reports/types.ts +7 -0
  143. package/src/rows/actor-ref-schema.ts +17 -0
  144. package/src/rows/attempt-row-schema.ts +69 -0
  145. package/src/rows/attempt-session-row-schema.ts +12 -0
  146. package/src/{brand-schemas.ts → rows/brand-schemas.ts} +5 -2
  147. package/src/rows/caller-identity-schema.ts +7 -0
  148. package/src/rows/criterion-schema.ts +9 -0
  149. package/src/rows/decision-row-schema.ts +34 -0
  150. package/src/rows/evidence-row-schema.ts +13 -0
  151. package/src/rows/item-row-schema.ts +32 -0
  152. package/src/rows/item-view-schema.ts +31 -0
  153. package/src/rows/lease-row-schema.ts +12 -0
  154. package/src/rows/lever-row-schema.ts +17 -0
  155. package/src/rows/limit-schema.ts +12 -0
  156. package/src/rows/limit-seat-row-schema.ts +12 -0
  157. package/src/rows/note-row-schema.ts +12 -0
  158. package/src/rows/notice-row-schema.ts +17 -0
  159. package/src/rows/pull-request-row-schema.ts +15 -0
  160. package/src/rows/relation-row-schema.ts +13 -0
  161. package/src/rows/row-schemas.ts +30 -0
  162. package/src/rows/runner-row-schema.ts +9 -0
  163. package/src/rows/spawn-request-schema.ts +15 -0
  164. package/src/rules/define-phase.ts +12 -0
  165. package/src/rules/load-rejection-schema.ts +51 -0
  166. package/src/rules/types.ts +291 -0
  167. package/src/test-utils/build-mock-cost-to-land.ts +31 -0
  168. package/src/test-utils/build-mock-evidence-row.ts +17 -0
  169. package/src/test-utils/build-mock-fact-id.ts +7 -0
  170. package/src/test-utils/build-mock-frame-item.ts +14 -0
  171. package/src/test-utils/build-mock-frame.ts +13 -0
  172. package/src/test-utils/build-mock-item-rules.ts +17 -0
  173. package/src/test-utils/build-mock-lease-row.ts +2 -2
  174. package/src/test-utils/build-mock-outbox-fact.ts +22 -0
  175. package/src/test-utils/build-mock-phase-kind.ts +29 -0
  176. package/src/test-utils/build-mock-pipeline.ts +52 -0
  177. package/src/test-utils/{build-mock-link-row.ts → build-mock-relation-row.ts} +4 -4
  178. package/src/test-utils/build-mock-settled.ts +18 -0
  179. package/src/test-utils/build-mock-snapshot.ts +13 -0
  180. package/src/test-utils/{build-mock-attempt-row.ts → build-mock-v1-attempt-row.ts} +28 -9
  181. package/src/test-utils/{build-mock-item-row.ts → build-mock-v1-item-row.ts} +7 -3
  182. package/src/test-utils/build-mock-v1-item-view.ts +29 -0
  183. package/src/test-utils/{build-mock-lever-row.ts → build-mock-v1-lever-row.ts} +5 -3
  184. package/src/test-utils/build-mock-v1-pull-request-row.ts +26 -0
  185. package/src/test-utils/build-mock-work-fact.ts +179 -0
  186. package/src/test-utils/create-fake-notifier.ts +54 -0
  187. package/src/test-utils/create-fake-pepys-reader.ts +73 -0
  188. package/src/test-utils/fact-sink-contract.ts +103 -0
  189. package/src/test-utils/pepys-reader-contract.ts +117 -0
  190. package/src/{types/core.ts → types.ts} +184 -48
  191. package/src/utils/format-key-path.ts +19 -0
  192. package/src/utils/to-json-value.ts +25 -0
  193. package/dist/build-close-out-schema.d.ts +0 -3
  194. package/dist/define-admission.d.ts +0 -3
  195. package/dist/define-gate.d.ts +0 -3
  196. package/dist/define-signal.d.ts +0 -3
  197. package/dist/derive-item-state.d.ts +0 -2
  198. package/dist/fact-schemas.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-lever-row.d.ts +0 -2
  204. package/dist/test-utils/build-mock-link-row.d.ts +0 -2
  205. package/dist/test-utils/build-mock-pull-request-row.d.ts +0 -2
  206. package/dist/types/dispatch.d.ts +0 -82
  207. package/dist/types/facts.d.ts +0 -154
  208. package/dist/types/planning.d.ts +0 -94
  209. package/dist/types/ports.d.ts +0 -218
  210. package/dist/types/registries.d.ts +0 -150
  211. package/src/derive-item-state.ts +0 -76
  212. package/src/fact-schemas.ts +0 -138
  213. package/src/friction-schema.ts +0 -10
  214. package/src/test-utils/build-mock-item-rows.ts +0 -14
  215. package/src/test-utils/build-mock-pull-request-row.ts +0 -16
  216. package/src/types/dispatch.ts +0 -95
  217. package/src/types/facts.ts +0 -166
  218. package/src/types/planning.ts +0 -136
  219. package/src/types/ports.ts +0 -262
  220. package/src/types/registries.ts +0 -185
  221. /package/dist/test-utils/{seed-faker.d.ts → pin-faker-seed.d.ts} +0 -0
  222. /package/src/test-utils/{seed-faker.ts → pin-faker-seed.ts} +0 -0
@@ -0,0 +1,34 @@
1
+ import { z } from 'zod';
2
+ import type { DecisionRow } from '../types.ts';
3
+ import { brandSchemas } from './brand-schemas.ts';
4
+
5
+ const countSchema = z.number().int().nonnegative();
6
+
7
+ const faultSchema = z.object({
8
+ plugin: z.string().min(1),
9
+ kind: z.enum(['signal', 'gate', 'waiver', 'admission', 'propagation', 'filter-field']),
10
+ item: brandSchemas.itemId.nullable(),
11
+ error: z.string(),
12
+ });
13
+
14
+ const limitCountSchema = z.object({
15
+ limit: brandSchemas.limitName,
16
+ held: countSchema,
17
+ max: countSchema,
18
+ });
19
+
20
+ // A plain object, so that the wire schema of `boxxy.decision.recorded` can extend it.
21
+ export const decisionRowSchema = z.object({
22
+ id: brandSchemas.decisionId,
23
+ attempt: brandSchemas.attemptId,
24
+ item: brandSchemas.itemId,
25
+ kind: z.enum(['start', 'wake']),
26
+ pool: brandSchemas.poolName,
27
+ rulesVersion: brandSchemas.rulesVersion,
28
+ snapshotHash: brandSchemas.snapshotHash,
29
+ storeVersion: countSchema,
30
+ pepysOffsets: z.record(brandSchemas.streamName, countSchema),
31
+ faults: z.array(faultSchema),
32
+ limits: z.array(limitCountSchema),
33
+ at: brandSchemas.instant,
34
+ }) satisfies z.ZodType<DecisionRow>;
@@ -0,0 +1,13 @@
1
+ import { z } from 'zod';
2
+ import type { EvidenceRow } from '../types.ts';
3
+ import { brandSchemas } from './brand-schemas.ts';
4
+
5
+ export const evidenceRowSchema: z.ZodType<EvidenceRow> = z.object({
6
+ factId: brandSchemas.factId,
7
+ attempt: brandSchemas.attemptId,
8
+ kind: z.enum(['pull_request', 'commit', 'test', 'ci-run', 'live-check']),
9
+ ref: z.string().min(1),
10
+ observed: z.string().nullable(),
11
+ source: z.enum(['hook', 'trailer', 'agent', 'watch']),
12
+ at: brandSchemas.instant,
13
+ });
@@ -0,0 +1,32 @@
1
+ import { z } from 'zod';
2
+ import type { ItemRow } from '../types.ts';
3
+ import { actorRefSchema } from './actor-ref-schema.ts';
4
+ import { brandSchemas } from './brand-schemas.ts';
5
+ import { callerIdentitySchema } from './caller-identity-schema.ts';
6
+ import { criterionSchema } from './criterion-schema.ts';
7
+
8
+ // A plain object, so that the fact schema of `boxxy.item.filed` can omit `keptSession`.
9
+ export const itemRowSchema = z.object({
10
+ id: brandSchemas.itemId,
11
+ kind: brandSchemas.itemKindName,
12
+ repo: brandSchemas.repoName,
13
+ title: z.string().min(1),
14
+ outcome: z.string().min(1),
15
+ acceptance: z.array(criterionSchema),
16
+ fields: z.object({ designMode: z.enum(['agent', 'attended']).exactOptional() }),
17
+ labels: z.array(z.string().min(1)),
18
+ foundInAttempt: brandSchemas.attemptId.nullable(),
19
+ filedBy: actorRefSchema,
20
+ filedAt: brandSchemas.instant,
21
+ closedAt: brandSchemas.instant.nullable(),
22
+ closeReason: z.string().nullable(),
23
+ keptSession: z
24
+ .object({
25
+ atcSession: brandSchemas.atcSessionId,
26
+ target: z.string().min(1),
27
+ attempt: brandSchemas.attemptId,
28
+ keptAt: brandSchemas.instant,
29
+ caller: callerIdentitySchema.nullable(),
30
+ })
31
+ .nullable(),
32
+ }) satisfies z.ZodType<ItemRow>;
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ import { positionSchema } from '../pipeline/position-schema.ts';
3
+ import type { ItemView } from '../types.ts';
4
+ import { brandSchemas } from './brand-schemas.ts';
5
+ import { itemRowSchema } from './item-row-schema.ts';
6
+
7
+ const holdReasonOrWaitingSchema = z.union([
8
+ z.literal('pull request open'),
9
+ z.literal('no pull request linked'),
10
+ z.literal('pull request closed unmerged'),
11
+ z.literal('attempts used up'),
12
+ z.literal('rounds used up'),
13
+ z.literal('outcome not in rules'),
14
+ brandSchemas.rulesHoldReason,
15
+ ]);
16
+
17
+ const derivedSchema = z.discriminatedUnion('state', [
18
+ z.object({ state: z.literal('idea') }),
19
+ z.object({ state: z.literal('ready') }),
20
+ z.object({ state: z.literal('running') }),
21
+ z.object({ state: z.literal('blocked') }),
22
+ z.object({ state: z.literal('done') }),
23
+ z.object({ state: z.literal('archived') }),
24
+ z.object({ state: z.literal('waiting'), reason: holdReasonOrWaitingSchema }),
25
+ ]);
26
+
27
+ // typed as the core type, so a handler returns the stored view with its read-only lists
28
+ export const itemViewSchema: z.ZodType<ItemView> = itemRowSchema.extend({
29
+ derived: derivedSchema,
30
+ position: positionSchema,
31
+ });
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod';
2
+ import type { LeaseRow } from '../types.ts';
3
+ import { brandSchemas } from './brand-schemas.ts';
4
+
5
+ export const leaseRowSchema: z.ZodType<LeaseRow> = z.object({
6
+ item: brandSchemas.itemId,
7
+ phase: brandSchemas.phaseName,
8
+ attempt: brandSchemas.attemptId,
9
+ gen: z.number().int().nonnegative(),
10
+ takenAt: brandSchemas.instant,
11
+ releasedAt: brandSchemas.instant.nullable(),
12
+ });
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ import type { LeverRow } from '../types.ts';
3
+ import { actorRefSchema } from './actor-ref-schema.ts';
4
+ import { brandSchemas } from './brand-schemas.ts';
5
+
6
+ // A plain object, so that the wire schema of `boxxy.lever.recorded` can extend it.
7
+ export const leverRowSchema = z.object({
8
+ seq: z.number().int().nonnegative(),
9
+ kind: z.templateLiteral([z.string().min(1), '.', z.string().min(1)]),
10
+ item: brandSchemas.itemId,
11
+ until: brandSchemas.instant.nullable(),
12
+ expiresAt: brandSchemas.instant.nullable(),
13
+ reason: z.string().nullable(),
14
+ phase: brandSchemas.phaseName.nullable(),
15
+ actor: actorRefSchema,
16
+ at: brandSchemas.instant,
17
+ }) satisfies z.ZodType<LeverRow>;
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod';
2
+ import type { Limit } from '../planning/types.ts';
3
+ import { brandSchemas } from './brand-schemas.ts';
4
+
5
+ const filterSchema = z.record(z.string(), z.array(z.string()));
6
+
7
+ // A limit as the store keeps it from the last sync of the loaded limits.
8
+ export const limitSchema: z.ZodType<Limit> = z.object({
9
+ name: brandSchemas.limitName,
10
+ max: z.number().int().nonnegative(),
11
+ counts: filterSchema,
12
+ });
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod';
2
+ import type { LimitSeatRow } from '../types.ts';
3
+ import { brandSchemas } from './brand-schemas.ts';
4
+
5
+ export const limitSeatRowSchema: z.ZodType<LimitSeatRow> = z.object({
6
+ limit: brandSchemas.limitName,
7
+ attempt: brandSchemas.attemptId,
8
+ holdGen: z.number().int().positive(),
9
+ overCap: z.boolean(),
10
+ takenAt: brandSchemas.instant,
11
+ releasedAt: brandSchemas.instant.nullable(),
12
+ });
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod';
2
+ import type { NoteRow } from '../types.ts';
3
+ import { actorRefSchema } from './actor-ref-schema.ts';
4
+ import { brandSchemas } from './brand-schemas.ts';
5
+
6
+ export const noteRowSchema: z.ZodType<NoteRow> = z.object({
7
+ item: brandSchemas.itemId,
8
+ seq: z.number().int().positive(),
9
+ text: z.string().min(1),
10
+ by: actorRefSchema,
11
+ at: brandSchemas.instant,
12
+ });
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ import type { NoticeRow } from '../types.ts';
3
+ import { brandSchemas } from './brand-schemas.ts';
4
+
5
+ const countSchema = z.number().int().nonnegative();
6
+
7
+ export const noticeRowSchema: z.ZodType<NoticeRow> = z.object({
8
+ id: z.number().int().positive(),
9
+ item: brandSchemas.itemId,
10
+ reason: z.union([
11
+ z.enum(['attempts used up', 'rounds used up', 'outcome not in rules']),
12
+ brandSchemas.rulesHoldReason,
13
+ ]),
14
+ at: brandSchemas.instant,
15
+ tries: countSchema,
16
+ sentAt: brandSchemas.instant.nullable(),
17
+ });
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+ import type { PullRequestRow } from '../types.ts';
3
+ import { brandSchemas } from './brand-schemas.ts';
4
+
5
+ export const pullRequestRowSchema: z.ZodType<PullRequestRow> = z.object({
6
+ repo: brandSchemas.repoName,
7
+ number: z.number().int().positive(),
8
+ attempt: brandSchemas.attemptId,
9
+ githubId: z.number().int().positive(),
10
+ url: z.url(),
11
+ state: z.enum(['open', 'closed', 'merged']),
12
+ mergedAt: brandSchemas.instant.nullable(),
13
+ mergeCommit: brandSchemas.commitSha.nullable(),
14
+ checkedAt: brandSchemas.instant,
15
+ });
@@ -0,0 +1,13 @@
1
+ import { z } from 'zod';
2
+ import type { RelationRow } from '../types.ts';
3
+ import { actorRefSchema } from './actor-ref-schema.ts';
4
+ import { brandSchemas } from './brand-schemas.ts';
5
+
6
+ export const relationRowSchema: z.ZodType<RelationRow> = z.object({
7
+ source: brandSchemas.itemId,
8
+ target: brandSchemas.itemId,
9
+ type: brandSchemas.relationTypeName,
10
+ addedBy: actorRefSchema,
11
+ addedAt: brandSchemas.instant,
12
+ removedAt: brandSchemas.instant.nullable(),
13
+ });
@@ -0,0 +1,30 @@
1
+ import type { z } from 'zod';
2
+ import type { TableName, Tables } from '../ports/types.ts';
3
+ import { attemptRowSchema } from './attempt-row-schema.ts';
4
+ import { attemptSessionRowSchema } from './attempt-session-row-schema.ts';
5
+ import { decisionRowSchema } from './decision-row-schema.ts';
6
+ import { evidenceRowSchema } from './evidence-row-schema.ts';
7
+ import { itemRowSchema } from './item-row-schema.ts';
8
+ import { leaseRowSchema } from './lease-row-schema.ts';
9
+ import { leverRowSchema } from './lever-row-schema.ts';
10
+ import { noteRowSchema } from './note-row-schema.ts';
11
+ import { noticeRowSchema } from './notice-row-schema.ts';
12
+ import { pullRequestRowSchema } from './pull-request-row-schema.ts';
13
+ import { relationRowSchema } from './relation-row-schema.ts';
14
+ import { runnerRowSchema } from './runner-row-schema.ts';
15
+
16
+ // The schema of each table that a row write may name, for a store that reads rows back.
17
+ export const rowSchemas: { readonly [T in TableName]: z.ZodType<Tables[T]> } = {
18
+ items: itemRowSchema,
19
+ relations: relationRowSchema,
20
+ levers: leverRowSchema,
21
+ attempts: attemptRowSchema,
22
+ attempt_sessions: attemptSessionRowSchema,
23
+ leases: leaseRowSchema,
24
+ decisions: decisionRowSchema,
25
+ evidence: evidenceRowSchema,
26
+ pull_requests: pullRequestRowSchema,
27
+ runners: runnerRowSchema,
28
+ notes: noteRowSchema,
29
+ notices: noticeRowSchema,
30
+ };
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod';
2
+ import type { RunnerRow } from '../types.ts';
3
+ import { brandSchemas } from './brand-schemas.ts';
4
+
5
+ export const runnerRowSchema: z.ZodType<RunnerRow> = z.object({
6
+ id: brandSchemas.runnerId,
7
+ heartbeatAt: brandSchemas.instant,
8
+ paused: z.boolean(),
9
+ });
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+ import type { SpawnRequest } from '../ports/types.ts';
3
+ import { brandSchemas } from './brand-schemas.ts';
4
+
5
+ // The spawn request as the attempt row stores it, read back for a respawn.
6
+ export const spawnRequestSchema: z.ZodType<SpawnRequest> = z.object({
7
+ target: z.string().min(1),
8
+ cwd: z.string().min(1),
9
+ prompt: z.string(),
10
+ agent: z.string().min(1),
11
+ model: z.string().min(1),
12
+ effort: z.enum(['low', 'medium', 'high', 'xhigh', 'max']),
13
+ name: z.templateLiteral(['boxxy/', z.string()]),
14
+ idempotencyKey: brandSchemas.attemptId,
15
+ });
@@ -0,0 +1,12 @@
1
+ import type { JsonValue } from '@boxxy/content-hash';
2
+ import type { PhaseKind } from './types.ts';
3
+
4
+ // Checks a phase kind against its own body, input and outcome list, then gives it the erased
5
+ // type that the registry holds.
6
+ export function definePhase<
7
+ Body,
8
+ Input extends JsonValue,
9
+ const Outcomes extends readonly string[],
10
+ >(phase: PhaseKind<Body, Input, Outcomes>): PhaseKind {
11
+ return phase;
12
+ }
@@ -0,0 +1,51 @@
1
+ import { z } from 'zod';
2
+ import { brandSchemas } from '../rows/brand-schemas.ts';
3
+ import type { LoadRejection, LoadRejectionReason } from './types.ts';
4
+
5
+ const phase = brandSchemas.phaseName;
6
+ const outcomeOfPhase = { phase, outcome: z.string() };
7
+
8
+ const loadRejectionReasonSchema: z.ZodType<LoadRejectionReason> = z.discriminatedUnion('kind', [
9
+ z.object({ kind: z.literal('invalid'), message: z.string() }),
10
+ z.object({ kind: z.literal('unknown-concept'), ref: z.string() }),
11
+ z.object({ kind: z.literal('unknown-signal'), signal: z.string() }),
12
+ z.object({
13
+ kind: z.literal('bad-plugin-params'),
14
+ pluginKind: z.enum(['signal', 'gate', 'admission']),
15
+ name: z.string(),
16
+ message: z.string(),
17
+ }),
18
+ z.object({ kind: z.literal('unknown-relation-type'), relation: z.string() }),
19
+ z.object({ kind: z.literal('signal-without-combine'), signal: z.string() }),
20
+ z.object({ kind: z.literal('missing-recipe'), itemKind: z.string(), phase: z.string() }),
21
+ z.object({ kind: z.literal('missing-prompt'), path: z.string() }),
22
+ z.object({ kind: z.literal('uncommitted-changes') }),
23
+ z.object({ kind: z.literal('unknown-filter-field'), field: z.string() }),
24
+ z.object({ kind: z.literal('pool-without-limit'), pool: z.string() }),
25
+ z.object({ kind: z.literal('unknown-phase'), phase: z.string() }),
26
+ z.object({ kind: z.literal('unmapped-outcome'), ...outcomeOfPhase }),
27
+ z.object({ kind: z.literal('unknown-outcome'), ...outcomeOfPhase }),
28
+ z.object({ kind: z.literal('open-loop'), ...outcomeOfPhase }),
29
+ z.object({ kind: z.literal('edge-to-unknown-phase'), phase, to: z.string() }),
30
+ z.object({
31
+ kind: z.literal('step-without-recipe'),
32
+ itemKind: brandSchemas.itemKindName,
33
+ phase,
34
+ }),
35
+ z.object({ kind: z.literal('step-without-timeout'), phase }),
36
+ z.object({ kind: z.literal('quiet-below-idle') }),
37
+ z.object({ kind: z.literal('unattended-below-quiet') }),
38
+ z.object({
39
+ kind: z.literal('engine-outcome-listed'),
40
+ phase,
41
+ outcome: z.enum(['lost', 'unsettleable']),
42
+ }),
43
+ z.object({ kind: z.literal('skills-without-pin'), recipe: z.string() }),
44
+ z.object({ kind: z.literal('resume-on-forward-edge'), ...outcomeOfPhase }),
45
+ ]);
46
+
47
+ export const loadRejectionSchema: z.ZodType<LoadRejection> = z.object({
48
+ file: z.string(),
49
+ path: z.string(),
50
+ reason: loadRejectionReasonSchema,
51
+ });
@@ -0,0 +1,291 @@
1
+ import type { JsonValue } from '@boxxy/content-hash';
2
+ import type { z } from 'zod';
3
+ import type { CloseOutParser, Effect } from '../closeouts/types.ts';
4
+ import type { RulesHoldReason } from '../pipeline/types.ts';
5
+ import type {
6
+ Admission,
7
+ FrameItem,
8
+ Gate,
9
+ Limit,
10
+ Pool,
11
+ PropagationLine,
12
+ Signal,
13
+ SignalKey,
14
+ SignalName,
15
+ View,
16
+ Waiver,
17
+ } from '../planning/types.ts';
18
+ import type {
19
+ AttemptRow,
20
+ CommitSha,
21
+ ConceptPin,
22
+ EngineOutcome,
23
+ Instant,
24
+ ItemKindName,
25
+ ItemView,
26
+ LeverKind,
27
+ Minutes,
28
+ PhaseName,
29
+ PipelineName,
30
+ PullRequestRow,
31
+ RecipeMaterial,
32
+ RecipeName,
33
+ RelationTypeName,
34
+ RulesVersion,
35
+ SessionMode,
36
+ WorkerKindName,
37
+ } from '../types.ts';
38
+
39
+ export type Schema<T> = z.ZodType<T>;
40
+
41
+ export interface ParamKinds<P> {
42
+ signal: Signal<P>;
43
+ gate: Gate<P>;
44
+ admission: Admission<P>;
45
+ }
46
+
47
+ export type ParamKindName = keyof ParamKinds<unknown>;
48
+
49
+ export interface Registered<K extends ParamKindName> {
50
+ readonly kind: K;
51
+ readonly name: string;
52
+ open: <R>(use: <P>(plugin: ParamKinds<P>[K]) => R) => R;
53
+ }
54
+
55
+ export interface Resolved<K extends ParamKindName> {
56
+ readonly kind: K;
57
+ readonly name: string;
58
+
59
+ // `signalKey(name, params)` for a signal, and the name for a gate
60
+ readonly key: string;
61
+ open: <R>(use: <P>(plugin: ParamKinds<P>[K], params: P) => R) => R;
62
+ }
63
+
64
+ export type ConceptName = string;
65
+
66
+ export interface Concept {
67
+ name: ConceptName;
68
+ version: number;
69
+ signals?: Registered<'signal'>[];
70
+ gates?: Registered<'gate'>[];
71
+ waivers?: Waiver[];
72
+ levers?: LeverKind[];
73
+ filterFields?: FilterField[];
74
+ }
75
+
76
+ export interface FilterField {
77
+ name: string;
78
+
79
+ // null never matches a filter on this field
80
+ value: (item: FrameItem) => string | readonly string[] | null;
81
+ }
82
+
83
+ export interface PlainKinds {
84
+ waiver: Waiver;
85
+ 'filter-field': FilterField;
86
+ lever: { readonly kind: LeverKind };
87
+ view: View;
88
+ }
89
+
90
+ export type PluginKindName = ParamKindName | keyof PlainKinds;
91
+
92
+ export type Registry = { readonly [K in ParamKindName]: ReadonlyMap<string, Registered<K>> } & {
93
+ readonly [K in keyof PlainKinds]: ReadonlyMap<string, PlainKinds[K]>;
94
+ } & { readonly pins: readonly ConceptPin[] };
95
+
96
+ // The code of one phase. Its `outcomes` are the one list of the phase's outcome names: the
97
+ // loader checks the rules against it, and `settle` answers with one of them.
98
+ export interface PhaseKind<
99
+ Body = unknown,
100
+ Input extends JsonValue = JsonValue,
101
+ Outcomes extends readonly string[] = readonly string[],
102
+ > {
103
+ readonly name: PhaseName;
104
+ readonly version: number;
105
+ readonly worker: WorkerKindName;
106
+ readonly outcomes: Outcomes;
107
+ readonly needsAcceptance: boolean;
108
+
109
+ // the session mode of an attempt of this phase; agent when the kind sets none
110
+ readonly mode?: (item: ItemView) => SessionMode;
111
+ readonly input: (ctx: PhaseInputContext) => Input;
112
+ readonly closeOut: CloseOutParser<Body>;
113
+
114
+ // Called only with the attempt's valid close-out. A method's parameter is bivariant, so a
115
+ // phase kind of any body still fits `PhaseKind`; a plain function property would refuse it.
116
+ readonly settle: BivariantSettle<Body, Outcomes[number]>['settle'];
117
+ }
118
+
119
+ interface BivariantSettle<Body, Outcome extends string> {
120
+ // oxlint-disable-next-line typescript/method-signature-style -- the method form is what makes the parameter bivariant
121
+ settle(input: SettleInput<Body>): Settlement<Outcome>;
122
+ }
123
+
124
+ export interface PhaseInputContext {
125
+ readonly item: ItemView;
126
+
127
+ // the item's attempts, oldest first
128
+ readonly history: readonly AttemptRow[];
129
+ readonly pullRequests: readonly PullRequestRow[];
130
+ }
131
+
132
+ export interface SettleInput<Body> {
133
+ readonly attempt: AttemptRow;
134
+ readonly item: ItemView;
135
+ readonly closeOut: Body;
136
+ readonly now: Instant;
137
+ }
138
+
139
+ export interface Settlement<Outcome extends string = string> {
140
+ readonly state: 'succeeded' | 'failed';
141
+ readonly failure: string | null;
142
+ readonly outcome: Outcome;
143
+ readonly effects: readonly Effect[];
144
+ readonly frictionDropped: number;
145
+ }
146
+
147
+ export interface KindRules {
148
+ readonly pipeline: Pipeline;
149
+
150
+ // by phase, and by `<phase>/attended` for an attended session of the phase
151
+ readonly recipes: Readonly<Record<string, RecipeName>>;
152
+ }
153
+
154
+ export interface Pipeline {
155
+ readonly name: PipelineName;
156
+ readonly steps: readonly PipelineStep[];
157
+ }
158
+
159
+ export interface PipelineStep {
160
+ readonly phase: PhaseName;
161
+
162
+ // attempts in a row that ended `lost` or took an `again` edge
163
+ readonly maxAttempts: number;
164
+
165
+ // the clock of one stretch of running of an agent-mode attempt
166
+ readonly timeoutMinutes: Minutes;
167
+
168
+ // an entry for every outcome of the phase kind
169
+ readonly on: Readonly<Record<string, Edge>>;
170
+ }
171
+
172
+ export type Edge =
173
+ | 'next'
174
+ | 'again'
175
+ | { readonly to: PhaseName; readonly max?: number; readonly resume?: boolean }
176
+ | { readonly end: string }
177
+ | { readonly hold: RulesHoldReason };
178
+
179
+ export interface SkillsPin {
180
+ // the repository, as owner/name
181
+ readonly source: string;
182
+ readonly ref: CommitSha;
183
+ }
184
+
185
+ // A relation type is registry data: the rules file lists the names, and a new type needs no code.
186
+ export interface RelationType {
187
+ readonly name: RelationTypeName;
188
+ }
189
+
190
+ export interface SleepRules {
191
+ readonly afterIdleMinutes: Minutes;
192
+ readonly afterQuietMinutes: Minutes;
193
+ readonly afterUnattendedMinutes: Minutes;
194
+ }
195
+
196
+ export type ConceptEntry = `${string}@${number}`;
197
+
198
+ export type PluginRef = string | Readonly<Record<string, JsonValue>>;
199
+
200
+ export type SignalRef = SignalName | Readonly<Record<string, JsonValue>>;
201
+
202
+ // What an item's position and state read of the rules loaded now; a `LoadedRules` is one.
203
+ export interface ItemRules {
204
+ readonly kinds: ReadonlyMap<ItemKindName, { readonly pipeline: Pipeline }>;
205
+ readonly phases: ReadonlyMap<PhaseName, { readonly needsAcceptance: boolean }>;
206
+ }
207
+
208
+ export interface LoadedRules {
209
+ readonly version: RulesVersion;
210
+ readonly commit: string;
211
+ readonly registry: Registry;
212
+ readonly signals: ReadonlyMap<SignalKey, Resolved<'signal'>>;
213
+ readonly gates: readonly Resolved<'gate'>[];
214
+ readonly kinds: ReadonlyMap<ItemKindName, KindRules>;
215
+ readonly phases: ReadonlyMap<PhaseName, PhaseKind>;
216
+ readonly limits: readonly Limit[];
217
+
218
+ // in declared order, each pool's `order` starting with `lead`
219
+ readonly pools: readonly Pool[];
220
+ readonly propagation: readonly PropagationLine[];
221
+ readonly recipes: ReadonlyMap<RecipeName, RecipeMaterial>;
222
+ readonly sleep: SleepRules;
223
+
224
+ // null when the rules file has no `skills` block
225
+ readonly skills: SkillsPin | null;
226
+ readonly relationTypes: ReadonlyMap<RelationTypeName, RelationType>;
227
+ }
228
+
229
+ export interface LoadRejection {
230
+ readonly file: string;
231
+ readonly path: string;
232
+ readonly reason: LoadRejectionReason;
233
+ }
234
+
235
+ export type LoadRejectionReason =
236
+ | { readonly kind: 'invalid'; readonly message: string }
237
+ | { readonly kind: 'unknown-concept'; readonly ref: string }
238
+ | { readonly kind: 'unknown-signal'; readonly signal: string }
239
+ | {
240
+ readonly kind: 'bad-plugin-params';
241
+ readonly pluginKind: ParamKindName;
242
+ readonly name: string;
243
+ readonly message: string;
244
+ }
245
+ | { readonly kind: 'unknown-relation-type'; readonly relation: string }
246
+ | { readonly kind: 'signal-without-combine'; readonly signal: string }
247
+ | { readonly kind: 'missing-recipe'; readonly itemKind: string; readonly phase: string }
248
+ | { readonly kind: 'missing-prompt'; readonly path: string }
249
+ | { readonly kind: 'uncommitted-changes' }
250
+
251
+ // a limit's `counts` or a pool's `takes` names a field that no concept registers
252
+ | { readonly kind: 'unknown-filter-field'; readonly field: string }
253
+
254
+ // a pool can take an item that no limit counts, so its picks would have no bound
255
+ | { readonly kind: 'pool-without-limit'; readonly pool: string }
256
+ | { readonly kind: 'unknown-phase'; readonly phase: string }
257
+ | { readonly kind: 'unmapped-outcome'; readonly phase: PhaseName; readonly outcome: string }
258
+ | { readonly kind: 'unknown-outcome'; readonly phase: PhaseName; readonly outcome: string }
259
+ | { readonly kind: 'open-loop'; readonly phase: PhaseName; readonly outcome: string }
260
+ | { readonly kind: 'edge-to-unknown-phase'; readonly phase: PhaseName; readonly to: string }
261
+ | {
262
+ readonly kind: 'step-without-recipe';
263
+ readonly itemKind: ItemKindName;
264
+ readonly phase: PhaseName;
265
+ }
266
+ | { readonly kind: 'step-without-timeout'; readonly phase: PhaseName }
267
+ | { readonly kind: 'quiet-below-idle' }
268
+ | { readonly kind: 'unattended-below-quiet' }
269
+ | {
270
+ readonly kind: 'engine-outcome-listed';
271
+ readonly phase: PhaseName;
272
+ readonly outcome: EngineOutcome;
273
+ }
274
+ | { readonly kind: 'skills-without-pin'; readonly recipe: string }
275
+ | {
276
+ readonly kind: 'resume-on-forward-edge';
277
+ readonly phase: PhaseName;
278
+ readonly outcome: string;
279
+ }
280
+
281
+ // a `hold` edge names one of the engine's hold reasons
282
+ | {
283
+ readonly kind: 'engine-hold-reason';
284
+ readonly phase: PhaseName;
285
+ readonly outcome: string;
286
+ readonly reason: string;
287
+ };
288
+
289
+ export type LoadResult =
290
+ | { readonly kind: 'loaded'; readonly rules: LoadedRules; readonly warnings: readonly string[] }
291
+ | { readonly kind: 'rejected'; readonly rejections: readonly LoadRejection[] };