@shivaedev/work-fleet 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +2 -2
  3. package/dist/admit.d.ts +54 -0
  4. package/dist/admit.d.ts.map +1 -1
  5. package/dist/engine/abandonStalled.d.ts +9807 -0
  6. package/dist/engine/abandonStalled.d.ts.map +1 -0
  7. package/dist/engine/abandonStalled.js +55 -0
  8. package/dist/engine/abandonStalled.js.map +1 -0
  9. package/dist/engine/applyResolution.d.ts +108 -0
  10. package/dist/engine/applyResolution.d.ts.map +1 -1
  11. package/dist/engine/applyResolution.js +3 -0
  12. package/dist/engine/applyResolution.js.map +1 -1
  13. package/dist/engine/approveBrief.d.ts +164 -2
  14. package/dist/engine/approveBrief.d.ts.map +1 -1
  15. package/dist/engine/approveBrief.js +7 -5
  16. package/dist/engine/approveBrief.js.map +1 -1
  17. package/dist/engine/authorizeResolution.d.ts +942 -24
  18. package/dist/engine/authorizeResolution.d.ts.map +1 -1
  19. package/dist/engine/boardBrief.d.ts +162 -0
  20. package/dist/engine/boardBrief.d.ts.map +1 -1
  21. package/dist/engine/capacityRetry.d.ts +2883 -75
  22. package/dist/engine/capacityRetry.d.ts.map +1 -1
  23. package/dist/engine/closeDecisions.d.ts +108 -0
  24. package/dist/engine/closeDecisions.d.ts.map +1 -1
  25. package/dist/engine/completeBoardWork.d.ts +108 -0
  26. package/dist/engine/completeBoardWork.d.ts.map +1 -1
  27. package/dist/engine/completeReport.d.ts +108 -0
  28. package/dist/engine/completeReport.d.ts.map +1 -1
  29. package/dist/engine/continueCommand.d.ts +54 -0
  30. package/dist/engine/continueCommand.d.ts.map +1 -1
  31. package/dist/engine/continueWorker.d.ts +108 -0
  32. package/dist/engine/continueWorker.d.ts.map +1 -1
  33. package/dist/engine/deliverPendingAnswers.d.ts +1104 -24
  34. package/dist/engine/deliverPendingAnswers.d.ts.map +1 -1
  35. package/dist/engine/dispatchCommand.d.ts +54 -0
  36. package/dist/engine/dispatchCommand.d.ts.map +1 -1
  37. package/dist/engine/ensureDecisionOwner.d.ts +108 -0
  38. package/dist/engine/ensureDecisionOwner.d.ts.map +1 -1
  39. package/dist/engine/ensurePublication.d.ts +108 -0
  40. package/dist/engine/ensurePublication.d.ts.map +1 -1
  41. package/dist/engine/inspectRenewal.d.ts +108 -0
  42. package/dist/engine/inspectRenewal.d.ts.map +1 -1
  43. package/dist/engine/launchInput.d.ts +54 -0
  44. package/dist/engine/launchInput.d.ts.map +1 -1
  45. package/dist/engine/launchReceipt.d.ts +54 -0
  46. package/dist/engine/launchReceipt.d.ts.map +1 -1
  47. package/dist/engine/makeAdopt.d.ts +108 -0
  48. package/dist/engine/makeAdopt.d.ts.map +1 -1
  49. package/dist/engine/makeAdvance.d.ts +54 -0
  50. package/dist/engine/makeAdvance.d.ts.map +1 -1
  51. package/dist/engine/makeDeliver.d.ts +108 -0
  52. package/dist/engine/makeDeliver.d.ts.map +1 -1
  53. package/dist/engine/makeHuman.d.ts +108 -0
  54. package/dist/engine/makeHuman.d.ts.map +1 -1
  55. package/dist/engine/makeLaunch.d.ts +108 -0
  56. package/dist/engine/makeLaunch.d.ts.map +1 -1
  57. package/dist/engine/makeObserve.d.ts +108 -0
  58. package/dist/engine/makeObserve.d.ts.map +1 -1
  59. package/dist/engine/makeReview.d.ts +108 -0
  60. package/dist/engine/makeReview.d.ts.map +1 -1
  61. package/dist/engine/makeState.d.ts +888 -24
  62. package/dist/engine/makeState.d.ts.map +1 -1
  63. package/dist/engine/makeValidate.d.ts +108 -0
  64. package/dist/engine/makeValidate.d.ts.map +1 -1
  65. package/dist/engine/mergeReadiness.d.ts +1212 -24
  66. package/dist/engine/mergeReadiness.d.ts.map +1 -1
  67. package/dist/engine/observeDeliveryFailure.d.ts +996 -24
  68. package/dist/engine/observeDeliveryFailure.d.ts.map +1 -1
  69. package/dist/engine/observeRenewal.d.ts +189 -0
  70. package/dist/engine/observeRenewal.d.ts.map +1 -1
  71. package/dist/engine/observedAttempt.d.ts.map +1 -1
  72. package/dist/engine/observedAttempt.js +3 -0
  73. package/dist/engine/observedAttempt.js.map +1 -1
  74. package/dist/engine/obtainReview.d.ts +54 -0
  75. package/dist/engine/obtainReview.d.ts.map +1 -1
  76. package/dist/engine/operationRecord.d.ts +726 -24
  77. package/dist/engine/operationRecord.d.ts.map +1 -1
  78. package/dist/engine/persistTerminalReceipts.d.ts +108 -0
  79. package/dist/engine/persistTerminalReceipts.d.ts.map +1 -1
  80. package/dist/engine/prepareCommand.d.ts +54 -0
  81. package/dist/engine/prepareCommand.d.ts.map +1 -1
  82. package/dist/engine/prepareCommand.js +19 -0
  83. package/dist/engine/prepareCommand.js.map +1 -1
  84. package/dist/engine/queueAnswerCommand.d.ts +54 -0
  85. package/dist/engine/queueAnswerCommand.d.ts.map +1 -1
  86. package/dist/engine/readOrContinueReview.d.ts +54 -0
  87. package/dist/engine/readOrContinueReview.d.ts.map +1 -1
  88. package/dist/engine/readRetry.d.ts +1995 -51
  89. package/dist/engine/readRetry.d.ts.map +1 -1
  90. package/dist/engine/reassignCommand.d.ts +54 -0
  91. package/dist/engine/reassignCommand.d.ts.map +1 -1
  92. package/dist/engine/reconcileAttempt.d.ts +81 -0
  93. package/dist/engine/reconcileAttempt.d.ts.map +1 -1
  94. package/dist/engine/reconcileCommand.d.ts +54 -0
  95. package/dist/engine/reconcileCommand.d.ts.map +1 -1
  96. package/dist/engine/recoverStall.d.ts +1023 -24
  97. package/dist/engine/recoverStall.d.ts.map +1 -1
  98. package/dist/engine/recoverStall.js +5 -1
  99. package/dist/engine/recoverStall.js.map +1 -1
  100. package/dist/engine/recoverUnobservable.d.ts +1023 -24
  101. package/dist/engine/recoverUnobservable.d.ts.map +1 -1
  102. package/dist/engine/refreshBrief.d.ts +1997 -53
  103. package/dist/engine/refreshBrief.d.ts.map +1 -1
  104. package/dist/engine/refreshReviewResult.d.ts +108 -0
  105. package/dist/engine/refreshReviewResult.d.ts.map +1 -1
  106. package/dist/engine/releaseClosedPreparation.d.ts +996 -24
  107. package/dist/engine/releaseClosedPreparation.d.ts.map +1 -1
  108. package/dist/engine/releaseExecution.d.ts +996 -24
  109. package/dist/engine/releaseExecution.d.ts.map +1 -1
  110. package/dist/engine/releaseFinishedWork.d.ts +54 -0
  111. package/dist/engine/releaseFinishedWork.d.ts.map +1 -1
  112. package/dist/engine/repository.d.ts +672 -24
  113. package/dist/engine/repository.d.ts.map +1 -1
  114. package/dist/engine/repository.js +1 -0
  115. package/dist/engine/repository.js.map +1 -1
  116. package/dist/engine/requestConflictRepair.d.ts +996 -24
  117. package/dist/engine/requestConflictRepair.d.ts.map +1 -1
  118. package/dist/engine/requestRework.d.ts +108 -0
  119. package/dist/engine/requestRework.d.ts.map +1 -1
  120. package/dist/engine/resolveCommand.d.ts +54 -0
  121. package/dist/engine/resolveCommand.d.ts.map +1 -1
  122. package/dist/engine/resolveExecution.d.ts +996 -24
  123. package/dist/engine/resolveExecution.d.ts.map +1 -1
  124. package/dist/engine/resolveExecution.js +3 -1
  125. package/dist/engine/resolveExecution.js.map +1 -1
  126. package/dist/engine/resumeAttempt.d.ts +1023 -24
  127. package/dist/engine/resumeAttempt.d.ts.map +1 -1
  128. package/dist/engine/retireUnacknowledged.d.ts +108 -0
  129. package/dist/engine/retireUnacknowledged.d.ts.map +1 -1
  130. package/dist/engine/retryTerminal.d.ts +942 -24
  131. package/dist/engine/retryTerminal.d.ts.map +1 -1
  132. package/dist/engine/scheduler.d.ts +108 -0
  133. package/dist/engine/scheduler.d.ts.map +1 -1
  134. package/dist/engine/sessionCleanup.d.ts +108 -0
  135. package/dist/engine/sessionCleanup.d.ts.map +1 -1
  136. package/dist/engine/sessionCleanup.js +3 -1
  137. package/dist/engine/sessionCleanup.js.map +1 -1
  138. package/dist/engine/sessionStatus.d.ts.map +1 -1
  139. package/dist/engine/sessionStatus.js +3 -0
  140. package/dist/engine/sessionStatus.js.map +1 -1
  141. package/dist/engine/settleMergedDelivery.d.ts +996 -24
  142. package/dist/engine/settleMergedDelivery.d.ts.map +1 -1
  143. package/dist/engine/steerBrief.d.ts +10340 -0
  144. package/dist/engine/steerBrief.d.ts.map +1 -0
  145. package/dist/engine/steerBrief.js +42 -0
  146. package/dist/engine/steerBrief.js.map +1 -0
  147. package/dist/engine/submissionFailure.d.ts +888 -24
  148. package/dist/engine/submissionFailure.d.ts.map +1 -1
  149. package/dist/engine/submitLaunch.d.ts +942 -24
  150. package/dist/engine/submitLaunch.d.ts.map +1 -1
  151. package/dist/engine/views.js +1 -0
  152. package/dist/engine/views.js.map +1 -1
  153. package/dist/engine/workerReport.d.ts +162 -0
  154. package/dist/engine/workerReport.d.ts.map +1 -1
  155. package/dist/fleet.d.ts +540 -0
  156. package/dist/fleet.d.ts.map +1 -1
  157. package/dist/host/admission.d.ts +108 -0
  158. package/dist/host/admission.d.ts.map +1 -1
  159. package/dist/host/admission.js +2 -1
  160. package/dist/host/admission.js.map +1 -1
  161. package/dist/host/answerWork.d.ts +54 -0
  162. package/dist/host/answerWork.d.ts.map +1 -1
  163. package/dist/host/commands.d.ts +108 -0
  164. package/dist/host/commands.d.ts.map +1 -1
  165. package/dist/host/daemon.d.ts +1465 -865
  166. package/dist/host/daemon.d.ts.map +1 -1
  167. package/dist/host/daemonClient.d.ts +691 -91
  168. package/dist/host/daemonClient.d.ts.map +1 -1
  169. package/dist/host/daemonContract.d.ts +984 -0
  170. package/dist/host/daemonContract.d.ts.map +1 -1
  171. package/dist/host/readWaitStates.d.ts.map +1 -1
  172. package/dist/host/readWaitStates.js +13 -4
  173. package/dist/host/readWaitStates.js.map +1 -1
  174. package/dist/host/reassignWork.d.ts +54 -0
  175. package/dist/host/reassignWork.d.ts.map +1 -1
  176. package/dist/host/recordPayloads.d.ts.map +1 -1
  177. package/dist/host/recordPayloads.js +8 -3
  178. package/dist/host/recordPayloads.js.map +1 -1
  179. package/dist/host/releaseWork.d.ts +108 -0
  180. package/dist/host/releaseWork.d.ts.map +1 -1
  181. package/dist/host/status.d.ts +30 -0
  182. package/dist/host/status.d.ts.map +1 -1
  183. package/dist/model.d.ts +144 -0
  184. package/dist/model.d.ts.map +1 -1
  185. package/dist/model.js +14 -1
  186. package/dist/model.js.map +1 -1
  187. package/dist/operations.d.ts +1224 -0
  188. package/dist/operations.d.ts.map +1 -1
  189. package/dist/packageInfo.d.ts +1 -1
  190. package/dist/packageInfo.js +1 -1
  191. package/dist/prompt/briefEdit.d.ts +2 -0
  192. package/dist/prompt/briefEdit.d.ts.map +1 -0
  193. package/dist/prompt/briefEdit.js +41 -0
  194. package/dist/prompt/briefEdit.js.map +1 -0
  195. package/dist/prompt/worker.d.ts.map +1 -1
  196. package/dist/prompt/worker.js +11 -2
  197. package/dist/prompt/worker.js.map +1 -1
  198. package/dist/qualifyDependencies.d.ts +54 -0
  199. package/dist/qualifyDependencies.d.ts.map +1 -1
  200. package/dist/scheduling/areas.d.ts +162 -0
  201. package/dist/scheduling/areas.d.ts.map +1 -1
  202. package/dist/storage/attemptV12.d.ts +108 -0
  203. package/dist/storage/attemptV12.d.ts.map +1 -0
  204. package/dist/storage/attemptV12.js +94 -0
  205. package/dist/storage/attemptV12.js.map +1 -0
  206. package/dist/storage/createCollections.d.ts +5 -0
  207. package/dist/storage/createCollections.d.ts.map +1 -0
  208. package/dist/storage/createCollections.js +21 -0
  209. package/dist/storage/createCollections.js.map +1 -0
  210. package/dist/storage/cycleV12.d.ts +287 -0
  211. package/dist/storage/cycleV12.d.ts.map +1 -0
  212. package/dist/storage/cycleV12.js +124 -0
  213. package/dist/storage/cycleV12.js.map +1 -0
  214. package/dist/storage/fleetMigrations.d.ts +0 -39
  215. package/dist/storage/fleetMigrations.d.ts.map +1 -1
  216. package/dist/storage/fleetMigrations.js +3 -9
  217. package/dist/storage/fleetMigrations.js.map +1 -1
  218. package/dist/storage/legacyColumns.d.ts +40 -0
  219. package/dist/storage/legacyColumns.d.ts.map +1 -0
  220. package/dist/storage/legacyColumns.js +12 -0
  221. package/dist/storage/legacyColumns.js.map +1 -0
  222. package/dist/storage/makeForeignReservations.d.ts +15 -0
  223. package/dist/storage/makeForeignReservations.d.ts.map +1 -0
  224. package/dist/storage/makeForeignReservations.js +34 -0
  225. package/dist/storage/makeForeignReservations.js.map +1 -0
  226. package/dist/storage/makePostgresFleetStore.d.ts +36 -16
  227. package/dist/storage/makePostgresFleetStore.d.ts.map +1 -1
  228. package/dist/storage/makePostgresFleetStore.js +24 -32
  229. package/dist/storage/makePostgresFleetStore.js.map +1 -1
  230. package/dist/storage/model.d.ts +53 -59
  231. package/dist/storage/model.d.ts.map +1 -1
  232. package/dist/storage/model.js +18 -60
  233. package/dist/storage/model.js.map +1 -1
  234. package/dist/storage/operationEntries.d.ts +6 -5
  235. package/dist/storage/operationEntries.d.ts.map +1 -1
  236. package/dist/storage/operationEntries.js +12 -15
  237. package/dist/storage/operationEntries.js.map +1 -1
  238. package/dist/storage/preparationV12.d.ts +44 -0
  239. package/dist/storage/preparationV12.d.ts.map +1 -0
  240. package/dist/storage/preparationV12.js +30 -0
  241. package/dist/storage/preparationV12.js.map +1 -0
  242. package/dist/storage/readRecords.d.ts +4 -0
  243. package/dist/storage/readRecords.d.ts.map +1 -0
  244. package/dist/storage/readRecords.js +10 -0
  245. package/dist/storage/readRecords.js.map +1 -0
  246. package/dist/storage/recordChildren.d.ts +11 -0
  247. package/dist/storage/recordChildren.d.ts.map +1 -0
  248. package/dist/storage/recordChildren.js +105 -0
  249. package/dist/storage/recordChildren.js.map +1 -0
  250. package/dist/storage/recordShape.d.ts +23 -0
  251. package/dist/storage/recordShape.d.ts.map +1 -0
  252. package/dist/storage/recordShape.js +112 -0
  253. package/dist/storage/recordShape.js.map +1 -0
  254. package/dist/storage/recordValue.d.ts +9 -6
  255. package/dist/storage/recordValue.d.ts.map +1 -1
  256. package/dist/storage/recordValue.js +42 -32
  257. package/dist/storage/recordValue.js.map +1 -1
  258. package/dist/storage/shapeV12.d.ts +794 -0
  259. package/dist/storage/shapeV12.d.ts.map +1 -0
  260. package/dist/storage/shapeV12.js +10 -0
  261. package/dist/storage/shapeV12.js.map +1 -0
  262. package/dist/storage/textRecordValue.d.ts +3 -0
  263. package/dist/storage/textRecordValue.d.ts.map +1 -0
  264. package/dist/storage/textRecordValue.js +13 -0
  265. package/dist/storage/textRecordValue.js.map +1 -0
  266. package/dist/storage/typedDecisionsV12.d.ts +103 -0
  267. package/dist/storage/typedDecisionsV12.d.ts.map +1 -0
  268. package/dist/storage/typedDecisionsV12.js +97 -0
  269. package/dist/storage/typedDecisionsV12.js.map +1 -0
  270. package/dist/storage/typedTables.d.ts +4 -0
  271. package/dist/storage/typedTables.d.ts.map +1 -0
  272. package/dist/storage/typedTables.js +39 -0
  273. package/dist/storage/typedTables.js.map +1 -0
  274. package/package.json +8 -8
  275. package/src/engine/abandonStalled.ts +63 -0
  276. package/src/engine/applyResolution.ts +3 -0
  277. package/src/engine/approveBrief.ts +34 -32
  278. package/src/engine/observedAttempt.ts +3 -0
  279. package/src/engine/prepareCommand.ts +21 -0
  280. package/src/engine/recoverStall.ts +6 -2
  281. package/src/engine/repository.ts +1 -0
  282. package/src/engine/resolveExecution.ts +3 -1
  283. package/src/engine/sessionCleanup.ts +3 -1
  284. package/src/engine/sessionStatus.ts +5 -0
  285. package/src/engine/steerBrief.ts +54 -0
  286. package/src/engine/views.ts +1 -0
  287. package/src/host/admission.ts +2 -1
  288. package/src/host/readWaitStates.ts +12 -11
  289. package/src/host/recordPayloads.ts +11 -3
  290. package/src/model.ts +20 -1
  291. package/src/packageInfo.ts +1 -1
  292. package/src/prompt/briefEdit.ts +44 -0
  293. package/src/prompt/worker.ts +11 -2
  294. package/src/storage/attemptV12.ts +95 -0
  295. package/src/storage/createCollections.ts +21 -0
  296. package/src/storage/cycleV12.ts +125 -0
  297. package/src/storage/fleetMigrations.ts +3 -10
  298. package/src/storage/legacyColumns.ts +12 -0
  299. package/src/storage/makeForeignReservations.ts +41 -0
  300. package/src/storage/makePostgresFleetStore.ts +30 -43
  301. package/src/storage/model.ts +19 -69
  302. package/src/storage/operationEntries.ts +12 -22
  303. package/src/storage/preparationV12.ts +31 -0
  304. package/src/storage/readRecords.ts +12 -0
  305. package/src/storage/recordChildren.ts +131 -0
  306. package/src/storage/recordShape.ts +137 -0
  307. package/src/storage/recordValue.ts +47 -34
  308. package/src/storage/shapeV12.ts +11 -0
  309. package/src/storage/textRecordValue.ts +16 -0
  310. package/src/storage/typedDecisionsV12.ts +100 -0
  311. package/src/storage/typedTables.ts +39 -0
@@ -0,0 +1,54 @@
1
+ import { Effect, Result } from "effect";
2
+ import { workRevision } from "#board/workRevision.ts";
3
+ import type { EngineState } from "#engine/makeState.ts";
4
+ import type { FleetRecord } from "#model.ts";
5
+ import { briefEditPrompt } from "#prompt/briefEdit.ts";
6
+ import type { Versioned } from "#storage/model.ts";
7
+
8
+ export function briefSteerIntent(previous: FleetRecord, record: FleetRecord, requestedAt: number): FleetRecord {
9
+ const revision = workRevision(record.boardRevision, record.boardContext);
10
+ const attempt = record.attempts.at(-1);
11
+ if (
12
+ revision === workRevision(previous.boardRevision, previous.boardContext)
13
+ || record.stage !== "executing"
14
+ || attempt?.receipt === undefined
15
+ || attempt.role === "reviewer"
16
+ || !["accepted", "running"].includes(attempt.status)
17
+ || attempt.briefSteers?.some((entry) => entry.revision === revision)
18
+ ) {
19
+ return record;
20
+ }
21
+ return {
22
+ ...record,
23
+ attempts: record.attempts.map((entry) =>
24
+ entry.operationId === attempt.operationId ? { ...entry, briefSteers: [...(entry.briefSteers ?? []), { requestedAt, revision }] } : entry,
25
+ ),
26
+ };
27
+ }
28
+
29
+ export const steerBrief = Effect.fn("Fleet.steerBrief")(function* (state: EngineState, stored: Versioned<FleetRecord>, previous: FleetRecord) {
30
+ const record = stored.value;
31
+ const revision = workRevision(record.boardRevision, record.boardContext);
32
+ const attempt = record.attempts.at(-1);
33
+ const intent = attempt?.briefSteers?.find((entry) => entry.revision === revision);
34
+ if (
35
+ attempt?.receipt === undefined
36
+ || intent === undefined
37
+ || previous.attempts.some((entry) => entry.operationId === attempt.operationId && entry.briefSteers?.some((saved) => saved.revision === revision))
38
+ ) {
39
+ return stored;
40
+ }
41
+ const result = yield* state.sessions
42
+ .steer(attempt.receipt, briefEditPrompt(previous.boardContext, record.boardContext ?? record.prompt))
43
+ .pipe(Effect.result);
44
+ const evidence = Result.isFailure(result) ? { error: result.failure.message } : { steeredAt: state.policy.now() };
45
+ const current = yield* state.load(record.workId);
46
+ return yield* state.save(current, {
47
+ ...current.value,
48
+ attempts: current.value.attempts.map((entry) =>
49
+ entry.operationId === attempt.operationId
50
+ ? { ...entry, briefSteers: entry.briefSteers?.map((saved) => (saved.revision === revision ? { ...saved, ...evidence } : saved)) }
51
+ : entry,
52
+ ),
53
+ });
54
+ });
@@ -33,6 +33,7 @@ function sessionView(attempt: Attempt) {
33
33
  : [
34
34
  {
35
35
  ...attempt.receipt,
36
+ briefSteers: attempt.briefSteers,
36
37
  interruptedAt: attempt.stallRecovery?.interruptedAt,
37
38
  interruptRequestedAt: attempt.stallRecovery?.interruptRequestedAt,
38
39
  lastProgressAt: attempt.progress?.at,
@@ -57,7 +57,8 @@ function unlaunched(record: FleetRecord) {
57
57
  }
58
58
 
59
59
  function stalePreparation(record: FleetRecord, work: BoardWork, main: MainSnapshot) {
60
- return unlaunched(record) && (briefChanged(record, work) || record.preparation.baseRevision !== main.revision);
60
+ const changed = briefChanged(record, work);
61
+ return record.stage === "prepared" && (unlaunched(record) || !changed) && (changed || record.preparation.baseRevision !== main.revision);
61
62
  }
62
63
 
63
64
  export type Approval = "board" | "explicit";
@@ -1,16 +1,23 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Schema from "effect/Schema";
3
+ import { Model } from "effect/schema";
3
4
  import { makeRepository } from "@shivaedev/effect-sql/repository.ts";
4
5
  import { State } from "#host/waitOutcome.ts";
5
- import { recordColumns, recordJson, recordModel } from "#storage/model.ts";
6
+ import { columnSchema, schemaShape, shapeColumns } from "#storage/recordShape.ts";
7
+ import { recordValues } from "#storage/recordValue.ts";
6
8
 
7
9
  export interface ReadOptions {
8
10
  readonly namespace: string;
9
11
  }
10
12
 
11
13
  export const readWaitStates = Effect.fn("FleetReader.readWaitStates")(function* (options: ReadOptions, ids?: readonly string[]) {
14
+ const shape = schemaShape(State);
15
+ class SnapshotRow extends Model.Class<SnapshotRow>("FleetSnapshot")({
16
+ ...Object.fromEntries(Object.entries(shapeColumns(shape)).map(([name, kind]) => [name, columnSchema(kind)])),
17
+ "work_id": Schema.String,
18
+ }) {}
12
19
  const read = Effect.gen(function* () {
13
- const repository = yield* makeRepository(recordModel(), {
20
+ const repository = yield* makeRepository(SnapshotRow, {
14
21
  idColumn: "work_id",
15
22
  spanPrefix: "FleetReader",
16
23
  tableName: `${options.namespace}_records`,
@@ -18,7 +25,6 @@ export const readWaitStates = Effect.fn("FleetReader.readWaitStates")(function*
18
25
  const rows = yield* repository
19
26
  .findMany({
20
27
  orderBy: { direction: "asc", field: "work_id" },
21
- select: ["work_id", "stage", "blocker", "attentionOwner", "checks", "decision", "outcome"],
22
28
  })
23
29
  .pipe(
24
30
  Effect.catchTag("SqlError", (error) => {
@@ -26,14 +32,9 @@ export const readWaitStates = Effect.fn("FleetReader.readWaitStates")(function*
26
32
  return typeof cause === "object" && cause !== null && "code" in cause && cause.code === "42P01" ? Effect.succeed([]) : Effect.fail(error);
27
33
  }),
28
34
  );
29
- return yield* Effect.forEach(
30
- rows.filter((row) => ids === undefined || ids.includes(row.work_id)),
31
- (row) =>
32
- recordJson(row, recordColumns()).pipe(
33
- Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(State))),
34
- Effect.map((state) => ({ ...state, outcome: state.outcome ?? {} })),
35
- ),
36
- );
35
+ const selected = rows.filter((row) => ids === undefined || ids.includes(row.work_id));
36
+ const states = yield* Schema.decodeUnknownEffect(Schema.Array(State))(yield* recordValues(selected, shape, options.namespace));
37
+ return states.map((state) => ({ ...state, outcome: state.outcome ?? {} }));
37
38
  });
38
39
  return yield* read;
39
40
  });
@@ -1,7 +1,10 @@
1
1
  import { Effect } from "effect";
2
2
  import { SqlClient } from "effect/sql";
3
3
  import { makeRepository } from "@shivaedev/effect-sql/repository.ts";
4
- import { recordColumns, recordJson, recordModel } from "#storage/model.ts";
4
+ import { recordModel } from "#storage/model.ts";
5
+ import { readRecords } from "#storage/readRecords.ts";
6
+ import { recordShape } from "#storage/recordShape.ts";
7
+ import { recordValues } from "#storage/recordValue.ts";
5
8
 
6
9
  export const recordPayloads = Effect.fn("FleetHost.recordPayloads")(function* (namespace: string, workId?: string) {
7
10
  const sql = yield* SqlClient.SqlClient;
@@ -11,6 +14,11 @@ export const recordPayloads = Effect.fn("FleetHost.recordPayloads")(function* (n
11
14
  return [];
12
15
  }
13
16
  const repository = yield* makeRepository(recordModel(), { idColumn: "work_id", spanPrefix: "FleetReader", tableName: table });
14
- const rows = yield* repository.findMany({ orderBy: { direction: "asc", field: "work_id" }, where: { "work_id": workId } });
15
- return yield* Effect.forEach(rows, (row) => recordJson(row, recordColumns()));
17
+ return yield* readRecords(
18
+ namespace,
19
+ Effect.gen(function* () {
20
+ const rows = yield* repository.findMany({ orderBy: { direction: "asc", field: "work_id" }, where: { "work_id": workId } });
21
+ return (yield* recordValues(rows, recordShape(), namespace)).map((value) => JSON.stringify(value));
22
+ }),
23
+ );
16
24
  });
package/src/model.ts CHANGED
@@ -4,8 +4,26 @@ import { AttentionOwner } from "#attention.ts";
4
4
  import { BoardReport } from "#board/schema.ts";
5
5
  import { Checks, Decision, DecisionResponse, Delivery, Review, SupersededDecision, WorkResult } from "#policy.ts";
6
6
  import { Preparation } from "#preparation/schema.ts";
7
- import { LaunchSettings, SessionReceipt } from "#session/schema.ts";
7
+ import { LaunchSettings, SessionObservation, SessionReceipt } from "#session/schema.ts";
8
8
  export const Attempt = Schema.Struct({
9
+ abandoned: Schema.optional(
10
+ Schema.Struct({
11
+ at: Schema.Number,
12
+ interruptError: Schema.optional(Schema.String),
13
+ observation: Schema.optional(SessionObservation),
14
+ observationError: Schema.optional(Schema.String),
15
+ }),
16
+ ),
17
+ briefSteers: Schema.optional(
18
+ Schema.Array(
19
+ Schema.Struct({
20
+ error: Schema.optional(Schema.String),
21
+ requestedAt: Schema.Number,
22
+ revision: Schema.String,
23
+ steeredAt: Schema.optional(Schema.Number),
24
+ }),
25
+ ),
26
+ ),
9
27
  checkFailures: Schema.optional(Schema.Array(Schema.String)),
10
28
  missingSince: Schema.optional(Schema.Number),
11
29
  observationRetries: Schema.optional(Schema.Number),
@@ -99,6 +117,7 @@ export const FleetView = Schema.Struct({
99
117
  sessions: Schema.Array(
100
118
  Schema.Struct({
101
119
  ...SessionReceipt.fields,
120
+ briefSteers: Attempt.fields.briefSteers,
102
121
  interruptedAt: Schema.optional(Schema.Number),
103
122
  interruptRequestedAt: Schema.optional(Schema.Number),
104
123
  lastProgressAt: Schema.optional(Schema.Number),
@@ -1,3 +1,3 @@
1
1
  export const NAME = "work-fleet";
2
2
  export const TITLE = "Work Fleet";
3
- export const VERSION = "0.1.1";
3
+ export const VERSION = "0.2.0";
@@ -0,0 +1,44 @@
1
+ import { board as boardApi } from "@shivaedev/work-board/integration/board.ts";
2
+ import { fleetDeliverable } from "#board/fleetField.ts";
3
+
4
+ export function briefEditPrompt(previous: string | undefined, current: string): string {
5
+ const before = boardApi.metadataParse(previous ?? "");
6
+ const after = boardApi.metadataParse(current);
7
+ const criteria = new Map((before.fields.criteria ?? []).map(({ id, text }) => [id, text]));
8
+ const next = new Map((after.fields.criteria ?? []).map(({ id, text }) => [id, text]));
9
+ const changes = [...new Set([...criteria.keys(), ...next.keys()])].flatMap((id) =>
10
+ criteria.get(id) === next.get(id) ? [] : [`Criterion ${id}: ${criteria.get(id) ?? "(added)"} -> ${next.get(id) ?? "(removed)"}`],
11
+ );
12
+ if (before.body !== after.body) {
13
+ changes.push(...bodyChanges(before.body, after.body));
14
+ }
15
+ for (const field of ["kind", "relationships"] as const) {
16
+ if (JSON.stringify(before.fields[field]) !== JSON.stringify(after.fields[field])) {
17
+ changes.push(`The ${field} changed.`);
18
+ }
19
+ }
20
+ if (fleetDeliverable(before) !== fleetDeliverable(after)) {
21
+ changes.push("The deliverable changed.");
22
+ }
23
+ return [
24
+ "Fleet approved an edited brief. Apply this correction to your running work.",
25
+ "What changed:",
26
+ ...changes,
27
+ "Full new brief:",
28
+ current,
29
+ ].join("\n\n");
30
+ }
31
+
32
+ function bodyChanges(before: string, after: string): readonly string[] {
33
+ const removed = before.split("\n");
34
+ const added = after.split("\n");
35
+ while (removed.length > 0 && added.length > 0 && removed[0] === added[0]) {
36
+ removed.shift();
37
+ added.shift();
38
+ }
39
+ while (removed.length > 0 && added.length > 0 && removed.at(-1) === added.at(-1)) {
40
+ removed.pop();
41
+ added.pop();
42
+ }
43
+ return [`Previous title/body excerpt:\n${removed.join("\n") || "(empty)"}`, `New title/body excerpt:\n${added.join("\n") || "(empty)"}`];
44
+ }
@@ -36,7 +36,8 @@ export function cloudStartup({ sharedEnvironment }: CloudStartup, access?: "read
36
36
  return [
37
37
  "Cloud startup:",
38
38
  "- Your workspace may still be provisioning when this message arrives. If the cloud-environment runtime skill offers wait_for_environment, call it and wait calmly until the environment is ready; this usually takes about 90 seconds. Do not explore the snapshot or retry commands while waiting. Your instructions below stay valid.",
39
- `- Source ${sharedEnvironment} at the start of EVERY independent shell command when it exists. The pinned Node, pnpm and Bun, the proxy, CA trust, caches and database settings come from it.`,
39
+ "- Default login shells activate the pinned Node, pnpm, Bun, gh and psql and every shared environment export through the profile hook, including the proxy, CA trust, caches and database settings.",
40
+ `- If \`node -v\` does not report the repository's \`.node-version\`, source ${sharedEnvironment} once in that command and say so in the final message.`,
40
41
  ...(access === "read-only"
41
42
  ? []
42
43
  : [
@@ -47,13 +48,21 @@ export function cloudStartup({ sharedEnvironment }: CloudStartup, access?: "read
47
48
 
48
49
  export function workerPrompt(input: WorkerPrompt) {
49
50
  if (input.deliverable === "report") {
50
- return [`Work Board task: ${input.workId}`, "", cloudStartup(input), "", reportPrompt(input.task.trim())].join("\n");
51
+ return [
52
+ `Work Board task: ${input.workId}`,
53
+ "",
54
+ cloudStartup(input),
55
+ "- Before executing repository commands, run `./script/update` with escalation (require_escalated), since it installs dependencies.",
56
+ "",
57
+ reportPrompt(input.task.trim()),
58
+ ].join("\n");
51
59
  }
52
60
  return [
53
61
  `Work Board task: ${input.workId}`,
54
62
  "",
55
63
  cloudStartup(input),
56
64
  `- Fetch origin/main and create ${input.branch} from that exact current revision, not from the environment snapshot. Use the supplied checkout and leave unrelated state alone.`,
65
+ "- Immediately after fetching origin/main and creating the branch, run `./script/update` with escalation (require_escalated), since it installs dependencies, before any other command or task work.",
57
66
  "",
58
67
  "Implement this approved Board task:",
59
68
  "---",
@@ -0,0 +1,95 @@
1
+ import type { Shape } from "#storage/recordShape.ts";
2
+
3
+ export const attemptV12 = {
4
+ fields: {
5
+ abandoned: {
6
+ fields: {
7
+ at: "number",
8
+ interruptError: "string",
9
+ observation: {
10
+ fields: {
11
+ capacity: "string",
12
+ error: "string",
13
+ execution: "string",
14
+ output: "string",
15
+ progress: { array: { fields: { id: "string", status: "string" } } },
16
+ provisioning: "string",
17
+ receipt: { fields: { sessionId: "string", turnId: "string" } },
18
+ turnMissing: "boolean",
19
+ },
20
+ },
21
+ observationError: "string",
22
+ },
23
+ },
24
+ briefSteers: {
25
+ array: {
26
+ fields: {
27
+ error: "string",
28
+ requestedAt: "number",
29
+ revision: "string",
30
+ steeredAt: "number",
31
+ },
32
+ },
33
+ },
34
+ checkFailures: { array: "string" },
35
+ missingSince: "number",
36
+ observationRetries: "number",
37
+ operationId: "string",
38
+ output: "string",
39
+ progress: {
40
+ fields: {
41
+ at: "number",
42
+ signature: "string",
43
+ },
44
+ },
45
+ prompt: "string",
46
+ providerFailures: "number",
47
+ readRetry: {
48
+ fields: {
49
+ error: "string",
50
+ failures: "number",
51
+ retryAt: "number",
52
+ },
53
+ },
54
+ receipt: {
55
+ fields: {
56
+ sessionId: "string",
57
+ turnId: "string",
58
+ },
59
+ },
60
+ reviewHead: "string",
61
+ reviewRevision: "string",
62
+ role: "string",
63
+ sessionCleanupComplete: "boolean",
64
+ sessionId: "string",
65
+ sessionUnusable: "boolean",
66
+ settings: {
67
+ fields: {
68
+ effort: "string",
69
+ model: "string",
70
+ serviceTier: "string",
71
+ },
72
+ },
73
+ stallRecovery: {
74
+ fields: {
75
+ count: "number",
76
+ interruptedAt: "number",
77
+ interruptRequestedAt: "number",
78
+ lastActionAt: "number",
79
+ steeredAt: "number",
80
+ },
81
+ },
82
+ status: "string",
83
+ submissionTimedOut: "boolean",
84
+ submissionUncertain: "boolean",
85
+ submittedAt: "number",
86
+ terminalReceipts: {
87
+ array: {
88
+ fields: {
89
+ sessionId: "string",
90
+ turnId: "string",
91
+ },
92
+ },
93
+ },
94
+ },
95
+ } as const satisfies Shape;
@@ -0,0 +1,21 @@
1
+ import { Effect } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+ import { childTable, positions } from "#storage/recordChildren.ts";
4
+ import { collections, type Shape, shapeColumns, sqlTypes } from "#storage/recordShape.ts";
5
+
6
+ export const createCollections = Effect.fn("FleetStore.createCollections")(function* (namespace: string, shape: Shape) {
7
+ const sql = yield* SqlClient.SqlClient;
8
+ const table = `${namespace}_records`;
9
+ for (const collection of collections(shape)) {
10
+ const keys = positions(collection);
11
+ const fields = Object.entries(shapeColumns(collection.shape)).map(([name, kind]) => sql`${sql(name)} ${sql.literal(sqlTypes[kind])}`);
12
+ yield* sql`create table ${sql(childTable(namespace, collection.path))} (
13
+ work_id text not null references ${sql(table)} (work_id) on delete cascade,
14
+ ${sql.csv(keys.map((key) => sql`${sql(key)} integer not null check (${sql(key)} >= 0)`))},
15
+ ${sql.csv(fields)}, primary key (work_id, ${sql.csv(keys.map((key) => sql`${sql(key)}`))})
16
+ )`;
17
+ if (collection.path.at(-1) === "attempts") {
18
+ yield* sql`create index on ${sql(childTable(namespace, collection.path))} (${sql("operationId")})`;
19
+ }
20
+ }
21
+ });
@@ -0,0 +1,125 @@
1
+ import { preparationV12 } from "#storage/preparationV12.ts";
2
+ import type { Shape } from "#storage/recordShape.ts";
3
+ import { decisionV12, responsesV12, supersededDecisionsV12 } from "#storage/typedDecisionsV12.ts";
4
+
5
+ export const cycleV12 = {
6
+ fields: {
7
+ admissionReady: "boolean",
8
+ areas: { array: "string" },
9
+ attentionOwner: "string",
10
+ blocker: "string",
11
+ boardContext: "string",
12
+ boardRevision: "string",
13
+ boardSourcePath: "string",
14
+ checks: {
15
+ fields: {
16
+ evidence: { array: "string" },
17
+ failedChecks: { array: "string" },
18
+ head: "string",
19
+ passed: "boolean",
20
+ pending: "boolean",
21
+ repairPrompt: "string",
22
+ },
23
+ },
24
+ completedAt: "number",
25
+ conflictRepairs: { array: "string" },
26
+ cwd: "string",
27
+ decision: decisionV12,
28
+ decisionAcknowledged: "boolean",
29
+ decisionPublished: "boolean",
30
+ deliverable: "string",
31
+ integrationRetry: {
32
+ fields: {
33
+ error: "string",
34
+ failures: "number",
35
+ retryAt: "number",
36
+ },
37
+ },
38
+ launch: {
39
+ fields: {
40
+ effort: "string",
41
+ model: "string",
42
+ serviceTier: "string",
43
+ },
44
+ },
45
+ launchRetry: {
46
+ fields: {
47
+ prompt: "string",
48
+ rejectedAt: "number",
49
+ rejections: "number",
50
+ retryAt: "number",
51
+ },
52
+ },
53
+ mergeAreasReleased: "boolean",
54
+ mergeReadyAt: "number",
55
+ mergeReadyHead: "string",
56
+ outcome: {
57
+ fields: {
58
+ evidence: { array: "string" },
59
+ head: "string",
60
+ revision: "string",
61
+ url: "string",
62
+ },
63
+ },
64
+ pendingAnswers: { array: "string" },
65
+ preparation: preparationV12,
66
+ prompt: "string",
67
+ releaseReason: "string",
68
+ report: {
69
+ fields: {
70
+ itemId: "string",
71
+ receipt: {
72
+ fields: {
73
+ sessionId: "string",
74
+ turnId: "string",
75
+ },
76
+ },
77
+ revision: "string",
78
+ sourcePath: "string",
79
+ workRevision: "string",
80
+ },
81
+ },
82
+ responses: responsesV12,
83
+ result: {
84
+ fields: {
85
+ evidence: { array: "string" },
86
+ head: "string",
87
+ kind: "string",
88
+ paths: { array: "string" },
89
+ pr: "string",
90
+ prNumber: "number",
91
+ },
92
+ },
93
+ resultObservations: {
94
+ array: {
95
+ fields: {
96
+ evidence: { array: "string" },
97
+ head: "string",
98
+ kind: "string",
99
+ paths: { array: "string" },
100
+ pr: "string",
101
+ prNumber: "number",
102
+ },
103
+ },
104
+ },
105
+ review: {
106
+ fields: {
107
+ evidence: { array: "string" },
108
+ findings: { array: "string" },
109
+ head: "string",
110
+ receipt: {
111
+ fields: {
112
+ sessionId: "string",
113
+ turnId: "string",
114
+ },
115
+ },
116
+ repairPrompt: "string",
117
+ verdict: "string",
118
+ },
119
+ },
120
+ stage: "string",
121
+ stageEnteredAt: "number",
122
+ supersededDecisions: supersededDecisionsV12,
123
+ updatedAt: "number",
124
+ },
125
+ } as const satisfies Shape;
@@ -1,21 +1,13 @@
1
1
  import { Effect } from "effect";
2
2
  import { Migrator, SqlClient } from "effect/sql";
3
3
  import { backfillDecisionCodes } from "#storage/backfillDecisionCodes.ts";
4
- import { modelTableColumns, modelTables } from "#storage/modelTables.ts";
4
+ import { modelTables } from "#storage/modelTables.ts";
5
5
  import { pruneEmptyCycles } from "#storage/pruneEmptyCycles.ts";
6
6
  import { recordTimeColumns } from "#storage/recordTimes.ts";
7
+ import { typedTables } from "#storage/typedTables.ts";
7
8
 
8
9
  const releaseReasonColumn = { kind: "string", name: "releaseReason" } as const;
9
10
 
10
- // PostgreSQL text cannot distinguish scalar strings from serialized JSON.
11
- export const fleetMigrationColumns = {
12
- ...modelTableColumns,
13
- [releaseReasonColumn.name]: releaseReasonColumn.kind,
14
- ...recordTimeColumns,
15
- integrationRetry: "json",
16
- pendingAnswers: "json",
17
- } as const;
18
-
19
11
  export const fleetMigrations = Effect.fn("FleetStore.fleetMigrations")(function* (namespace: string, legacyOnly = false) {
20
12
  const sql = yield* SqlClient.SqlClient;
21
13
  return yield* Migrator.fromRecord({
@@ -55,6 +47,7 @@ export const fleetMigrations = Effect.fn("FleetStore.fleetMigrations")(function*
55
47
  "9_integration_retry": sql`alter table ${sql(`${namespace}_records`)} add column ${sql("integrationRetry")} text`,
56
48
  "10_prune_empty_cycles": pruneEmptyCycles(namespace),
57
49
  "11_pending_answers": sql`alter table ${sql(`${namespace}_records`)} add column ${sql("pendingAnswers")} text`,
50
+ "12_typed_tables": typedTables(namespace),
58
51
  }),
59
52
  });
60
53
  });
@@ -0,0 +1,12 @@
1
+ import { modelTableColumns } from "#storage/modelTables.ts";
2
+
3
+ // PostgreSQL text cannot distinguish scalar strings from serialized JSON.
4
+ export const legacyColumns = {
5
+ ...modelTableColumns,
6
+ completedAt: "number",
7
+ integrationRetry: "json",
8
+ pendingAnswers: "json",
9
+ releaseReason: "string",
10
+ stageEnteredAt: "number",
11
+ updatedAt: "number",
12
+ } as const;
@@ -0,0 +1,41 @@
1
+ import { Effect } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+ import { makeRepository } from "@shivaedev/effect-sql/repository.ts";
4
+ import { fleetRecords } from "#fleetRecords.ts";
5
+ import { makeFleetTransaction } from "#storage/makeFleetTransaction.ts";
6
+ import { ForeignReservationRow } from "#storage/model.ts";
7
+
8
+ export const makeForeignReservations = Effect.fn("FleetStore.makeForeignReservations")(function* (namespace: string) {
9
+ const sql = yield* SqlClient.SqlClient;
10
+ const commit = yield* makeFleetTransaction;
11
+ const reservations = yield* makeRepository(ForeignReservationRow, {
12
+ idColumn: "owner",
13
+ spanPrefix: "FleetForeign",
14
+ tableName: `${namespace}_foreign_reservations`,
15
+ });
16
+ const lock = sql`select pg_advisory_xact_lock(hashtextextended(${`work_fleet_admission:${namespace}`}, 0))`;
17
+ return {
18
+ foreignReservations: () => reservations.findMany({ orderBy: { direction: "asc", field: "owner" } }),
19
+ releaseForeign: (owner: string) =>
20
+ commit(
21
+ [fleetRecords.list],
22
+ Effect.gen(function* () {
23
+ yield* lock;
24
+ yield* reservations.delete(owner);
25
+ }),
26
+ ),
27
+ reserveForeign: (owner: string, occupancy: { readonly executing: boolean; readonly backlog: boolean } = { backlog: true, executing: true }) =>
28
+ commit(
29
+ [fleetRecords.list],
30
+ Effect.gen(function* () {
31
+ yield* lock;
32
+ const existing = yield* reservations.findMany({ where: { owner } });
33
+ if (existing.length === 0) {
34
+ yield* reservations.insertVoid({ owner, ...occupancy });
35
+ } else {
36
+ yield* reservations.updateVoid({ owner, ...occupancy });
37
+ }
38
+ }),
39
+ ),
40
+ };
41
+ });