@bluewombat/runtime 0.1.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 (151) hide show
  1. package/README.md +473 -0
  2. package/SPECS.md +303 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +172 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/config/defaults.d.ts +17 -0
  8. package/dist/config/defaults.d.ts.map +1 -0
  9. package/dist/config/defaults.js +17 -0
  10. package/dist/config/defaults.js.map +1 -0
  11. package/dist/config/find-config.d.ts +9 -0
  12. package/dist/config/find-config.d.ts.map +1 -0
  13. package/dist/config/find-config.js +25 -0
  14. package/dist/config/find-config.js.map +1 -0
  15. package/dist/config/home.d.ts +17 -0
  16. package/dist/config/home.d.ts.map +1 -0
  17. package/dist/config/home.js +23 -0
  18. package/dist/config/home.js.map +1 -0
  19. package/dist/config/load-config.d.ts +13 -0
  20. package/dist/config/load-config.d.ts.map +1 -0
  21. package/dist/config/load-config.js +537 -0
  22. package/dist/config/load-config.js.map +1 -0
  23. package/dist/config/parse-args.d.ts +25 -0
  24. package/dist/config/parse-args.d.ts.map +1 -0
  25. package/dist/config/parse-args.js +478 -0
  26. package/dist/config/parse-args.js.map +1 -0
  27. package/dist/config/types.d.ts +144 -0
  28. package/dist/config/types.d.ts.map +1 -0
  29. package/dist/config/types.js +2 -0
  30. package/dist/config/types.js.map +1 -0
  31. package/dist/index.d.ts +30 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +16 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/loop/authority.d.ts +51 -0
  36. package/dist/loop/authority.d.ts.map +1 -0
  37. package/dist/loop/authority.js +166 -0
  38. package/dist/loop/authority.js.map +1 -0
  39. package/dist/loop/context.d.ts +44 -0
  40. package/dist/loop/context.d.ts.map +1 -0
  41. package/dist/loop/context.js +2 -0
  42. package/dist/loop/context.js.map +1 -0
  43. package/dist/loop/cursor.d.ts +4 -0
  44. package/dist/loop/cursor.d.ts.map +1 -0
  45. package/dist/loop/cursor.js +20 -0
  46. package/dist/loop/cursor.js.map +1 -0
  47. package/dist/loop/deliveries.d.ts +22 -0
  48. package/dist/loop/deliveries.d.ts.map +1 -0
  49. package/dist/loop/deliveries.js +240 -0
  50. package/dist/loop/deliveries.js.map +1 -0
  51. package/dist/loop/describe.d.ts +40 -0
  52. package/dist/loop/describe.d.ts.map +1 -0
  53. package/dist/loop/describe.js +48 -0
  54. package/dist/loop/describe.js.map +1 -0
  55. package/dist/loop/drive.d.ts +15 -0
  56. package/dist/loop/drive.d.ts.map +1 -0
  57. package/dist/loop/drive.js +42 -0
  58. package/dist/loop/drive.js.map +1 -0
  59. package/dist/loop/journal.d.ts +22 -0
  60. package/dist/loop/journal.d.ts.map +1 -0
  61. package/dist/loop/journal.js +49 -0
  62. package/dist/loop/journal.js.map +1 -0
  63. package/dist/loop/lock.d.ts +11 -0
  64. package/dist/loop/lock.d.ts.map +1 -0
  65. package/dist/loop/lock.js +60 -0
  66. package/dist/loop/lock.js.map +1 -0
  67. package/dist/loop/message.d.ts +34 -0
  68. package/dist/loop/message.d.ts.map +1 -0
  69. package/dist/loop/message.js +48 -0
  70. package/dist/loop/message.js.map +1 -0
  71. package/dist/loop/open-host.d.ts +13 -0
  72. package/dist/loop/open-host.d.ts.map +1 -0
  73. package/dist/loop/open-host.js +180 -0
  74. package/dist/loop/open-host.js.map +1 -0
  75. package/dist/loop/probe.d.ts +9 -0
  76. package/dist/loop/probe.d.ts.map +1 -0
  77. package/dist/loop/probe.js +57 -0
  78. package/dist/loop/probe.js.map +1 -0
  79. package/dist/loop/report.d.ts +26 -0
  80. package/dist/loop/report.d.ts.map +1 -0
  81. package/dist/loop/report.js +328 -0
  82. package/dist/loop/report.js.map +1 -0
  83. package/dist/loop/tick.d.ts +24 -0
  84. package/dist/loop/tick.d.ts.map +1 -0
  85. package/dist/loop/tick.js +169 -0
  86. package/dist/loop/tick.js.map +1 -0
  87. package/dist/loop/trace.d.ts +16 -0
  88. package/dist/loop/trace.d.ts.map +1 -0
  89. package/dist/loop/trace.js +49 -0
  90. package/dist/loop/trace.js.map +1 -0
  91. package/dist/loop/transformers.d.ts +27 -0
  92. package/dist/loop/transformers.d.ts.map +1 -0
  93. package/dist/loop/transformers.js +229 -0
  94. package/dist/loop/transformers.js.map +1 -0
  95. package/dist/loop/work-line.d.ts +73 -0
  96. package/dist/loop/work-line.d.ts.map +1 -0
  97. package/dist/loop/work-line.js +109 -0
  98. package/dist/loop/work-line.js.map +1 -0
  99. package/dist/operator/board.d.ts +31 -0
  100. package/dist/operator/board.d.ts.map +1 -0
  101. package/dist/operator/board.js +67 -0
  102. package/dist/operator/board.js.map +1 -0
  103. package/dist/operator/cancel.d.ts +19 -0
  104. package/dist/operator/cancel.d.ts.map +1 -0
  105. package/dist/operator/cancel.js +70 -0
  106. package/dist/operator/cancel.js.map +1 -0
  107. package/dist/operator/doctor.d.ts +15 -0
  108. package/dist/operator/doctor.d.ts.map +1 -0
  109. package/dist/operator/doctor.js +416 -0
  110. package/dist/operator/doctor.js.map +1 -0
  111. package/dist/operator/git-remote.d.ts +39 -0
  112. package/dist/operator/git-remote.d.ts.map +1 -0
  113. package/dist/operator/git-remote.js +78 -0
  114. package/dist/operator/git-remote.js.map +1 -0
  115. package/dist/operator/init.d.ts +28 -0
  116. package/dist/operator/init.d.ts.map +1 -0
  117. package/dist/operator/init.js +522 -0
  118. package/dist/operator/init.js.map +1 -0
  119. package/dist/operator/live.d.ts +19 -0
  120. package/dist/operator/live.d.ts.map +1 -0
  121. package/dist/operator/live.js +149 -0
  122. package/dist/operator/live.js.map +1 -0
  123. package/dist/operator/prompt.d.ts +35 -0
  124. package/dist/operator/prompt.d.ts.map +1 -0
  125. package/dist/operator/prompt.js +125 -0
  126. package/dist/operator/prompt.js.map +1 -0
  127. package/dist/operator/setup.d.ts +18 -0
  128. package/dist/operator/setup.d.ts.map +1 -0
  129. package/dist/operator/setup.js +62 -0
  130. package/dist/operator/setup.js.map +1 -0
  131. package/dist/plugins/discover.d.ts +13 -0
  132. package/dist/plugins/discover.d.ts.map +1 -0
  133. package/dist/plugins/discover.js +81 -0
  134. package/dist/plugins/discover.js.map +1 -0
  135. package/dist/plugins/isolation.d.ts +78 -0
  136. package/dist/plugins/isolation.d.ts.map +1 -0
  137. package/dist/plugins/isolation.js +64 -0
  138. package/dist/plugins/isolation.js.map +1 -0
  139. package/dist/plugins/load.d.ts +35 -0
  140. package/dist/plugins/load.d.ts.map +1 -0
  141. package/dist/plugins/load.js +109 -0
  142. package/dist/plugins/load.js.map +1 -0
  143. package/dist/plugins/manager.d.ts +43 -0
  144. package/dist/plugins/manager.d.ts.map +1 -0
  145. package/dist/plugins/manager.js +47 -0
  146. package/dist/plugins/manager.js.map +1 -0
  147. package/dist/plugins/persist.d.ts +33 -0
  148. package/dist/plugins/persist.d.ts.map +1 -0
  149. package/dist/plugins/persist.js +26 -0
  150. package/dist/plugins/persist.js.map +1 -0
  151. package/package.json +66 -0
@@ -0,0 +1,30 @@
1
+ export { CONFIG_FILENAME, findConfig } from "./config/find-config.js";
2
+ export type { ParseInput, ParseResult } from "./config/parse-args.js";
3
+ export { parseArgs } from "./config/parse-args.js";
4
+ export type { HostInvocation, HostOptions } from "./config/types.js";
5
+ export type { CancelResult, Host, HostTickResult, OpenHostDeps } from "./loop/open-host.js";
6
+ export { openHost } from "./loop/open-host.js";
7
+ export type { TransformerSlots } from "./loop/transformers.js";
8
+ export { createTransformers } from "./loop/transformers.js";
9
+ export type { WorkLineState } from "./loop/work-line.js";
10
+ export { inspectWorkLine } from "./loop/work-line.js";
11
+ export type { CancelInput } from "./operator/cancel.js";
12
+ export { runCancel } from "./operator/cancel.js";
13
+ export type { DoctorInput } from "./operator/doctor.js";
14
+ export { runDoctor } from "./operator/doctor.js";
15
+ export { cloneWorkLine, remoteBranches } from "./operator/git-remote.js";
16
+ export type { InitInput, InitResult } from "./operator/init.js";
17
+ export { runInit } from "./operator/init.js";
18
+ export type { LiveInput } from "./operator/live.js";
19
+ export { runLive } from "./operator/live.js";
20
+ export type { Ask } from "./operator/prompt.js";
21
+ export { isInteractive, terminalAsk } from "./operator/prompt.js";
22
+ export type { SetupInput } from "./operator/setup.js";
23
+ export { runSetup } from "./operator/setup.js";
24
+ export type { DiscoveredManager } from "./plugins/discover.js";
25
+ export { discoverManagers } from "./plugins/discover.js";
26
+ export type { ResolveIsolationResult } from "./plugins/isolation.js";
27
+ export { resolveIsolationStrategy } from "./plugins/isolation.js";
28
+ export type { LoadedModule, ManagerRequest, ResolveResult } from "./plugins/manager.js";
29
+ export { loadManagerModule, openManager, resolveManagerModule } from "./plugins/manager.js";
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACtE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrE,YAAY,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC5F,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACzE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,YAAY,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAClE,YAAY,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAClE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ export { CONFIG_FILENAME, findConfig } from "./config/find-config.js";
2
+ export { parseArgs } from "./config/parse-args.js";
3
+ export { openHost } from "./loop/open-host.js";
4
+ export { createTransformers } from "./loop/transformers.js";
5
+ export { inspectWorkLine } from "./loop/work-line.js";
6
+ export { runCancel } from "./operator/cancel.js";
7
+ export { runDoctor } from "./operator/doctor.js";
8
+ export { cloneWorkLine, remoteBranches } from "./operator/git-remote.js";
9
+ export { runInit } from "./operator/init.js";
10
+ export { runLive } from "./operator/live.js";
11
+ export { isInteractive, terminalAsk } from "./operator/prompt.js";
12
+ export { runSetup } from "./operator/setup.js";
13
+ export { discoverManagers } from "./plugins/discover.js";
14
+ export { resolveIsolationStrategy } from "./plugins/isolation.js";
15
+ export { loadManagerModule, openManager, resolveManagerModule } from "./plugins/manager.js";
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAEtE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGnD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAEzE,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAElE,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAElE,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,51 @@
1
+ import type { AuthorityPort } from "@bluewombat/conductor";
2
+ import type { ManagerPort } from "@bluewombat/manager-kit";
3
+ import type { PassSpec } from "../config/types.js";
4
+ import type { Journal } from "./journal.js";
5
+ /**
6
+ * The Authority, as Host wires it.
7
+ *
8
+ * Two halves that do not belong to the same world: publishing the work is an
9
+ * operation on a directory Host owns, and offering it to the outside belongs to
10
+ * the FeatureManager. Host runs the first as a slot it never reads into, then
11
+ * hands the second the name that slot published under. Neither of them judges:
12
+ * that is a Gate.
13
+ *
14
+ * The Publisher is a slot for the same reason the Builder is one: `git push` is
15
+ * one way to put work in front of a judge, not the only one, and a Project that
16
+ * places its work some other way changes that command and nothing else.
17
+ */
18
+ export declare function openAuthority(input: {
19
+ manager: ManagerPort;
20
+ workLineStable: string;
21
+ workLineTarget: string;
22
+ publishArgv: string[];
23
+ timeoutMs: number;
24
+ /** The isolation strategy's name for a Child, from its id. */
25
+ refOf: (id: string) => string;
26
+ /** The work line's git environment (identity, credentials), for the Publisher. */
27
+ env?: Record<string, string> | undefined;
28
+ /** How the work describes itself, when the Project has a slot for it. */
29
+ describe?: PassSpec | undefined;
30
+ workspaceRoot?: string | undefined;
31
+ journal?: Journal | undefined;
32
+ }): AuthorityPort | undefined;
33
+ /**
34
+ * Bring the work line copy up to what the Authority holds, through the slot the
35
+ * Project named.
36
+ *
37
+ * The other half of the same seam as the Publisher: one puts the work where the
38
+ * judge reads, the other reads back what the judge accepted. `git fetch` is one
39
+ * way to do that, not the only one, so neither belongs in Host.
40
+ */
41
+ export declare function refreshWorkLine(input: {
42
+ workLineStable: string;
43
+ workLineTarget: string;
44
+ refreshArgv: string[];
45
+ timeoutMs: number;
46
+ env?: Record<string, string> | undefined;
47
+ }): {
48
+ ok: boolean;
49
+ detail?: string;
50
+ };
51
+ //# sourceMappingURL=authority.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authority.d.ts","sourceRoot":"","sources":["../../src/loop/authority.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,yBAAyB,CAAC;AAC9E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,OAAO,EAAE,WAAW,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9B,kFAAkF;IAClF,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACzC,yEAAyE;IACzE,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC/B,GAAG,aAAa,GAAG,SAAS,CAyD5B;AA2ED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;CAC1C,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAwBnC"}
@@ -0,0 +1,166 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { featureWorkspacePath, subjectOf } from "@bluewombat/conductor";
3
+ import { describe } from "./describe.js";
4
+ /**
5
+ * The Authority, as Host wires it.
6
+ *
7
+ * Two halves that do not belong to the same world: publishing the work is an
8
+ * operation on a directory Host owns, and offering it to the outside belongs to
9
+ * the FeatureManager. Host runs the first as a slot it never reads into, then
10
+ * hands the second the name that slot published under. Neither of them judges:
11
+ * that is a Gate.
12
+ *
13
+ * The Publisher is a slot for the same reason the Builder is one: `git push` is
14
+ * one way to put work in front of a judge, not the only one, and a Project that
15
+ * places its work some other way changes that command and nothing else.
16
+ */
17
+ export function openAuthority(input) {
18
+ const { manager, workLineStable, workLineTarget, publishArgv, timeoutMs, refOf } = input;
19
+ if (manager.submit === undefined || manager.fold === undefined) {
20
+ return undefined;
21
+ }
22
+ const submit = manager.submit.bind(manager);
23
+ const fold = manager.fold.bind(manager);
24
+ return {
25
+ async submit(offer) {
26
+ // The name the strategy gave this feature's Child. What the Publisher
27
+ // does with it — push it, copy it, upload it — is the Publisher's business.
28
+ const ref = refOf(offer.ref);
29
+ const published = publish({
30
+ argv: publishArgv,
31
+ cwd: workLineStable,
32
+ id: offer.key,
33
+ ref,
34
+ target: workLineTarget,
35
+ timeoutMs,
36
+ env: input.env,
37
+ });
38
+ if (!published.ok) {
39
+ return { outcome: "refused", reason: published.reason };
40
+ }
41
+ // The Project's own words for what it submits, when it has a slot for
42
+ // them; the Authority puts them where its kind of Submission is read.
43
+ // Asked in the feature workspace, where the whole change is.
44
+ let description;
45
+ if (input.describe !== undefined && input.workspaceRoot !== undefined) {
46
+ const asked = describe(input.describe, {
47
+ id: offer.key,
48
+ title: offer.title ?? offer.key,
49
+ intention: offer.intention ?? "",
50
+ target: workLineTarget,
51
+ cwd: featureWorkspacePath(input.workspaceRoot, offer.key),
52
+ });
53
+ if (asked.ok) {
54
+ description = asked.description;
55
+ }
56
+ else {
57
+ input.journal?.append({ event: "describe-skipped", id: offer.key, detail: asked.detail });
58
+ }
59
+ }
60
+ return await submit({
61
+ ...offer,
62
+ ref: published.ref,
63
+ target: workLineTarget,
64
+ // What the Submission lists as its work: one line per Subtask, the way
65
+ // the plan was said on the tracker, not the whole intentions.
66
+ ...(offer.steps === undefined ? {} : { steps: offer.steps.map(subjectOf) }),
67
+ ...(description === undefined ? {} : { description }),
68
+ });
69
+ },
70
+ async fold(asked) {
71
+ return await fold(asked);
72
+ },
73
+ };
74
+ }
75
+ /**
76
+ * Run the Publisher slot in the work line and read the one line it answers.
77
+ *
78
+ * Same contract as every other slot: argv in, one JSON object on stdout. A slot
79
+ * that cannot run at all is a refusal like any other here — the Submission does
80
+ * not happen, the reason reaches the tracker, and the next pass tries again.
81
+ */
82
+ function publish(input) {
83
+ const [command, ...rest] = input.argv;
84
+ if (command === undefined) {
85
+ return { ok: false, reason: "No Publisher is configured, so nothing can be submitted." };
86
+ }
87
+ const run = spawnSync(command, [...rest, "--id", input.id, "--ref", input.ref, "--target", input.target], {
88
+ cwd: input.cwd,
89
+ encoding: "utf8",
90
+ timeout: input.timeoutMs,
91
+ env: { ...process.env, ...input.env },
92
+ });
93
+ if (run.error !== undefined) {
94
+ return { ok: false, reason: `Publisher could not run: ${run.error.message}` };
95
+ }
96
+ const answer = lastJsonLine(run.stdout ?? "");
97
+ if (answer === undefined) {
98
+ const detail = `${run.stderr ?? ""}`.trim().slice(-1000);
99
+ return {
100
+ ok: false,
101
+ reason: `Publisher stdout is not a JSON object.${detail.length > 0 ? ` ${detail}` : ""}`,
102
+ };
103
+ }
104
+ if (typeof answer.ref === "string" && answer.ref.length > 0) {
105
+ return { ok: true, ref: answer.ref };
106
+ }
107
+ if (answer.outcome === "refused") {
108
+ const reason = typeof answer.reason === "string" ? answer.reason : "no reason given";
109
+ return { ok: false, reason };
110
+ }
111
+ return { ok: false, reason: "Publisher answered neither a reference nor a refusal." };
112
+ }
113
+ function lastJsonLine(stdout) {
114
+ const lines = stdout.trim().split("\n");
115
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
116
+ const line = lines[i]?.trim();
117
+ if (line === undefined || line.length === 0) {
118
+ continue;
119
+ }
120
+ try {
121
+ const parsed = JSON.parse(line);
122
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
123
+ return parsed;
124
+ }
125
+ }
126
+ catch {
127
+ // Not the contract line. A slot may write anything else to stderr, but
128
+ // stdout noise is its own bug — keep looking for a line that parses.
129
+ }
130
+ }
131
+ return undefined;
132
+ }
133
+ /**
134
+ * Bring the work line copy up to what the Authority holds, through the slot the
135
+ * Project named.
136
+ *
137
+ * The other half of the same seam as the Publisher: one puts the work where the
138
+ * judge reads, the other reads back what the judge accepted. `git fetch` is one
139
+ * way to do that, not the only one, so neither belongs in Host.
140
+ */
141
+ export function refreshWorkLine(input) {
142
+ const [command, ...rest] = input.refreshArgv;
143
+ if (command === undefined) {
144
+ return { ok: false, detail: "No Refresher is configured." };
145
+ }
146
+ const run = spawnSync(command, [...rest, "--target", input.workLineTarget], {
147
+ cwd: input.workLineStable,
148
+ encoding: "utf8",
149
+ timeout: input.timeoutMs,
150
+ env: { ...process.env, ...input.env },
151
+ });
152
+ if (run.error !== undefined) {
153
+ return { ok: false, detail: `Refresher could not run: ${run.error.message}` };
154
+ }
155
+ const answer = lastJsonLine(run.stdout ?? "");
156
+ if (answer === undefined) {
157
+ const detail = `${run.stderr ?? ""}`.trim().slice(-500);
158
+ return { ok: false, detail: `Refresher stdout is not a JSON object. ${detail}`.trim() };
159
+ }
160
+ if (answer.ok === true) {
161
+ return { ok: true };
162
+ }
163
+ const reason = typeof answer.reason === "string" ? answer.reason : "no reason given";
164
+ return { ok: false, detail: reason };
165
+ }
166
+ //# sourceMappingURL=authority.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authority.js","sourceRoot":"","sources":["../../src/loop/authority.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import type { AuthorityPort, Conductor, ConductorMoment } from "@bluewombat/conductor";
2
+ import type { EventName, ManagerPort } from "@bluewombat/manager-kit";
3
+ import type { WorkLedger } from "@bluewombat/work-ledger";
4
+ import type { HostOptions } from "../config/types.js";
5
+ import type { Journal } from "./journal.js";
6
+ import type { Trace } from "./trace.js";
7
+ /**
8
+ * What one Host tick needs after composition. Shared so tick, deliveries and
9
+ * report do not import the factory.
10
+ */
11
+ export type HostRunInput = {
12
+ options: HostOptions;
13
+ /** The work line as resolved at boot: the directory, and the name work is offered to. */
14
+ workLine: {
15
+ stable: string;
16
+ target?: string;
17
+ env?: Record<string, string> | undefined;
18
+ };
19
+ ledger: WorkLedger;
20
+ conductor: Conductor;
21
+ manager: ManagerPort;
22
+ journal: Journal;
23
+ trace: Trace;
24
+ /** Present when the Project declared an Authority and the manager can submit. */
25
+ authority?: AuthorityPort;
26
+ /**
27
+ * Where Conductor's moments land during a pass. A tick points it at its own
28
+ * report function before driving, so what is said mid-pass counts as that
29
+ * tick's Events; between ticks nobody listens.
30
+ */
31
+ watcher: {
32
+ current?: ((moment: ConductorMoment) => Promise<void>) | undefined;
33
+ };
34
+ /**
35
+ * Event ids this process already reported. A fact said once is not said
36
+ * again by this process; the manager's own memory covers a restart.
37
+ */
38
+ said: Set<string>;
39
+ };
40
+ /** One Delivery (or sweep) within a tick: the run input plus Events collected so far. */
41
+ export type HostDeliveryInput = HostRunInput & {
42
+ reported: EventName[];
43
+ };
44
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/loop/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,WAAW,CAAC;IACrB,yFAAyF;IACzF,QAAQ,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAA;KAAE,CAAC;IACxF,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,SAAS,CAAC;IACrB,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,iFAAiF;IACjF,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B;;;;OAIG;IACH,OAAO,EAAE;QAAE,OAAO,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAA;KAAE,CAAC;IAChF;;;OAGG;IACH,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnB,CAAC;AAEF,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG;IAC7C,QAAQ,EAAE,SAAS,EAAE,CAAC;CACvB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/loop/context.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ export declare function cursorPath(ledgerRoot: string): string;
2
+ export declare function loadCursor(ledgerRoot: string): Promise<string | undefined>;
3
+ export declare function saveCursor(ledgerRoot: string, cursor: string): Promise<void>;
4
+ //# sourceMappingURL=cursor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/loop/cursor.ts"],"names":[],"mappings":"AAGA,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAOhF;AAED,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIlF"}
@@ -0,0 +1,20 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ export function cursorPath(ledgerRoot) {
4
+ return join(ledgerRoot, "cursor");
5
+ }
6
+ export async function loadCursor(ledgerRoot) {
7
+ try {
8
+ const text = (await readFile(cursorPath(ledgerRoot), "utf8")).trim();
9
+ return text.length > 0 ? text : undefined;
10
+ }
11
+ catch {
12
+ return undefined;
13
+ }
14
+ }
15
+ export async function saveCursor(ledgerRoot, cursor) {
16
+ const path = cursorPath(ledgerRoot);
17
+ await mkdir(dirname(path), { recursive: true });
18
+ await writeFile(path, `${cursor}\n`, "utf8");
19
+ }
20
+ //# sourceMappingURL=cursor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/loop/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,UAAU,UAAU,CAAC,UAAkB;IAC3C,OAAO,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAkB;IACjD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACrE,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAkB,EAAE,MAAc;IACjE,MAAM,IAAI,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACpC,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,SAAS,CAAC,IAAI,EAAE,GAAG,MAAM,IAAI,EAAE,MAAM,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { ProjectRunResult } from "@bluewombat/conductor";
2
+ import type { HostDeliveryInput, HostRunInput } from "./context.js";
3
+ export declare function handleDelivery(input: HostDeliveryInput & {
4
+ payload: Record<string, unknown>;
5
+ }): Promise<{
6
+ advance: boolean;
7
+ run?: ProjectRunResult;
8
+ project?: string;
9
+ }>;
10
+ export declare function handleCancel(feature: {
11
+ key: string;
12
+ project: string;
13
+ }, input: HostDeliveryInput): Promise<void>;
14
+ export type CancelResult = {
15
+ ok: true;
16
+ } | {
17
+ ok: false;
18
+ code: "not-found" | "point-of-no-return" | "already-cancelled" | "done";
19
+ };
20
+ /** Abandon one Feature by key — the same path a `cancel` delivery takes. */
21
+ export declare function cancelFeature(input: HostRunInput, key: string): Promise<CancelResult>;
22
+ //# sourceMappingURL=deliveries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deliveries.d.ts","sourceRoot":"","sources":["../../src/loop/deliveries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAG9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAqBpE,wBAAsB,cAAc,CAClC,KAAK,EAAE,iBAAiB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC9D,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,gBAAgB,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA8DzE;AAuFD,wBAAsB,YAAY,CAChC,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EACzC,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,IAAI,CAAC,CAuCf;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IACE,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EAAE,WAAW,GAAG,oBAAoB,GAAG,mBAAmB,GAAG,MAAM,CAAC;CACzE,CAAC;AAEN,4EAA4E;AAC5E,wBAAsB,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAkB3F"}
@@ -0,0 +1,240 @@
1
+ import { driveProject } from "./drive.js";
2
+ import { pushReport, resumePointOf } from "./report.js";
3
+ /**
4
+ * An upsert leaves Features that ready / cancel / too-late already own.
5
+ * SPECS §6: leave in-flight / terminal (including submitted); do not restart.
6
+ * The ledger still records a *new* fingerprint as pending_fingerprint; Host
7
+ * does not admit here, so an echo of Submitted cannot look like one.
8
+ */
9
+ const IN_FLIGHT_OR_TERMINAL = new Set([
10
+ "planning",
11
+ "running",
12
+ "escalated",
13
+ "integrating",
14
+ "submitted",
15
+ "merging",
16
+ "done",
17
+ "cancelled",
18
+ ]);
19
+ export async function handleDelivery(input) {
20
+ const adapted = await input.manager.adapt(input.payload);
21
+ switch (adapted.outcome) {
22
+ case "unavailable":
23
+ case "interrupted":
24
+ return { advance: false };
25
+ case "invalid": {
26
+ // Telling the tracker writes to it, and that write delivers the same
27
+ // intention again. What a human wrote has not changed: said once.
28
+ const before = await input.ledger.get(adapted.key);
29
+ if (before.ok &&
30
+ before.aggregate.state === "invalid" &&
31
+ before.aggregate.intention.fingerprint === adapted.fingerprint) {
32
+ input.journal.append({ event: "skipped", key: adapted.key, state: "invalid" });
33
+ return { advance: true };
34
+ }
35
+ await input.ledger.recordInvalid({
36
+ key: adapted.key,
37
+ project: adapted.project,
38
+ code: adapted.invalid.code,
39
+ reason: adapted.invalid.reason,
40
+ fingerprint: adapted.fingerprint,
41
+ });
42
+ input.journal.append({
43
+ event: "invalid",
44
+ key: adapted.key,
45
+ reason: adapted.invalid.reason,
46
+ });
47
+ await pushReport(input, {
48
+ event: "invalid",
49
+ key: adapted.key,
50
+ project: adapted.project,
51
+ // One per intention as written: an edit is a new fingerprint and is said again.
52
+ eventId: `${adapted.key}:invalid:${adapted.fingerprint}`,
53
+ fields: { reason: adapted.invalid.reason },
54
+ });
55
+ return { advance: true };
56
+ }
57
+ case "converted":
58
+ break;
59
+ }
60
+ const feature = adapted.feature;
61
+ // Poll snapshots have no labeled edge: a ready signal on an escalated upsert
62
+ // is the same instruction as an explicit ready intent.
63
+ if (feature.intent === "upsert" && input.manager.signalsReady?.(input.payload) === true) {
64
+ const before = await input.ledger.get(feature.key);
65
+ if (before.ok && before.aggregate.state === "escalated") {
66
+ return await handleReady(feature, input);
67
+ }
68
+ }
69
+ switch (feature.intent) {
70
+ case "ready":
71
+ return await handleReady(feature, input);
72
+ case "cancel":
73
+ await handleCancel(feature, input);
74
+ return { advance: true };
75
+ case "upsert":
76
+ return await handleUpsert(feature, input);
77
+ }
78
+ }
79
+ async function handleUpsert(feature, input) {
80
+ const admission = toAdmission(feature);
81
+ if (admission === undefined) {
82
+ return { advance: true };
83
+ }
84
+ const before = await input.ledger.get(admission.key);
85
+ if (before.ok && IN_FLIGHT_OR_TERMINAL.has(before.aggregate.state)) {
86
+ input.journal.append({
87
+ event: "skipped",
88
+ key: admission.key,
89
+ state: before.aggregate.state,
90
+ });
91
+ return { advance: true };
92
+ }
93
+ const admitted = await input.ledger.admit(admission);
94
+ if (!admitted.ok) {
95
+ if (admitted.code === "too-late" || admitted.code === "point-of-no-return") {
96
+ await pushReport(input, {
97
+ event: "reject_late",
98
+ key: admission.key,
99
+ project: admission.project,
100
+ // One per intention: the same edit delivered again is the echo of
101
+ // this report, not another edit.
102
+ eventId: `${admission.key}:reject_late:${admission.fingerprint}`,
103
+ fields: { reason: `Admission refused: ${admitted.code}` },
104
+ });
105
+ }
106
+ return { advance: true };
107
+ }
108
+ if (!before.ok || before.aggregate.state === "invalid") {
109
+ input.journal.append({ event: "admitted", key: admission.key, project: admission.project });
110
+ await pushReport(input, {
111
+ event: "accepted",
112
+ key: admission.key,
113
+ project: admission.project,
114
+ // One per intention as written, like `invalid`: a corrected issue is accepted again.
115
+ eventId: `${admission.key}:accepted:${admission.fingerprint}`,
116
+ fields: { priority: admission.priority },
117
+ });
118
+ }
119
+ const run = await driveProject(input, admission.project, admission.key);
120
+ if (run === undefined) {
121
+ // Frozen Project: do not mark it driven, so a later delivery in this pass
122
+ // (a cancel, a ready) can free it and the sweep can start the queue.
123
+ return { advance: true };
124
+ }
125
+ return { advance: true, run, project: admission.project };
126
+ }
127
+ async function handleReady(feature, input) {
128
+ const before = await input.ledger.get(feature.key);
129
+ if (!before.ok || before.aggregate.state !== "escalated") {
130
+ return { advance: true };
131
+ }
132
+ const resumePoint = resumePointOf(before.aggregate);
133
+ const ready = await input.ledger.resumeReady(feature.key);
134
+ if (!ready.ok) {
135
+ return { advance: true };
136
+ }
137
+ // The signal is an instruction, not a state: left where it is, it resumes the
138
+ // feature again on every pass and an escalation never freezes anything.
139
+ await input.manager.clearReady?.(feature.key);
140
+ // The ledger counts the rounds; this resume is the latest one.
141
+ const resumed = await input.ledger.get(feature.key);
142
+ const round = resumed.ok ? (resumed.aggregate.resumes ?? 0) : 0;
143
+ await pushReport(input, {
144
+ event: "resumed",
145
+ key: feature.key,
146
+ project: feature.project,
147
+ eventId: `${feature.key}:resumed:${round}`,
148
+ fields: { resume_point: resumePoint },
149
+ });
150
+ const run = await driveProject(input, feature.project, feature.key);
151
+ if (run === undefined) {
152
+ return { advance: true };
153
+ }
154
+ return { advance: true, run, project: feature.project };
155
+ }
156
+ export async function handleCancel(feature, input) {
157
+ const before = await input.ledger.get(feature.key);
158
+ if (!before.ok) {
159
+ return;
160
+ }
161
+ const state = before.aggregate.state;
162
+ if (state === "cancelled") {
163
+ // The ledger takes a second cancel as done already; the tracker was told
164
+ // the first time, and that is what delivered this one.
165
+ return;
166
+ }
167
+ if (state === "done") {
168
+ // A finished feature's issue closing is the tracker agreeing, not a
169
+ // cancel arriving late — the Submission itself asked for that close.
170
+ input.journal.append({ event: "skipped", key: feature.key, state });
171
+ return;
172
+ }
173
+ const cancelled = await input.conductor.cancel(feature.key);
174
+ if (!cancelled.ok) {
175
+ if (cancelled.code === "point-of-no-return") {
176
+ await pushReport(input, {
177
+ event: "reject_late",
178
+ key: feature.key,
179
+ project: feature.project,
180
+ // A closed issue stays closed: every later delivery is the same cancel.
181
+ eventId: `${feature.key}:reject_late:cancel`,
182
+ fields: { reason: "Cancel arrived after the point of no return." },
183
+ });
184
+ }
185
+ return;
186
+ }
187
+ await pushReport(input, {
188
+ event: "cancelled",
189
+ key: feature.key,
190
+ project: feature.project,
191
+ // A Feature is cancelled once: the ledger refuses a second cancel and never re-admits the key.
192
+ eventId: `${feature.key}:cancelled`,
193
+ fields: { state },
194
+ });
195
+ }
196
+ /** Abandon one Feature by key — the same path a `cancel` delivery takes. */
197
+ export async function cancelFeature(input, key) {
198
+ const got = await input.ledger.get(key);
199
+ if (!got.ok) {
200
+ return { ok: false, code: "not-found" };
201
+ }
202
+ const state = got.aggregate.state;
203
+ if (state === "cancelled") {
204
+ return { ok: false, code: "already-cancelled" };
205
+ }
206
+ if (state === "done") {
207
+ return { ok: false, code: "done" };
208
+ }
209
+ await handleCancel({ key, project: got.aggregate.intention.project }, { ...input, reported: [] });
210
+ const after = await input.ledger.get(key);
211
+ if (after.ok && after.aggregate.state === "cancelled") {
212
+ return { ok: true };
213
+ }
214
+ return { ok: false, code: "point-of-no-return" };
215
+ }
216
+ function toAdmission(feature) {
217
+ if (feature.intent !== "upsert") {
218
+ return undefined;
219
+ }
220
+ if (feature.intention === undefined) {
221
+ return undefined;
222
+ }
223
+ const admission = {
224
+ key: feature.key,
225
+ project: feature.project,
226
+ fingerprint: feature.fingerprint,
227
+ priority: feature.priority,
228
+ intention: feature.intention,
229
+ manager: feature.manager,
230
+ external_id: feature.external_id,
231
+ };
232
+ if (feature.title !== undefined) {
233
+ admission.title = feature.title;
234
+ }
235
+ if (feature.source !== undefined) {
236
+ admission.source = feature.source;
237
+ }
238
+ return admission;
239
+ }
240
+ //# sourceMappingURL=deliveries.js.map