@tiphys/kernel 0.0.0 → 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 (207) hide show
  1. package/AGENTS.md +663 -0
  2. package/LICENSE +202 -0
  3. package/assurance-modes.yaml +299 -0
  4. package/checklists/clean-room.yaml +325 -0
  5. package/checklists/env-failure-diagnosis.yaml +68 -0
  6. package/checklists/flake-playbook.yaml +68 -0
  7. package/checklists/hazard-review.yaml +144 -0
  8. package/checklists/plan-review.yaml +103 -0
  9. package/dist/bin/tiphys.d.ts +2 -0
  10. package/dist/bin/tiphys.js +92 -0
  11. package/dist/src/adapters/load.d.ts +202 -0
  12. package/dist/src/adapters/load.js +440 -0
  13. package/dist/src/brief.d.ts +32 -0
  14. package/dist/src/brief.js +66 -0
  15. package/dist/src/checklists.d.ts +169 -0
  16. package/dist/src/checklists.js +310 -0
  17. package/dist/src/checks.d.ts +1539 -0
  18. package/dist/src/checks.js +5025 -0
  19. package/dist/src/cli.d.ts +9 -0
  20. package/dist/src/cli.js +68 -0
  21. package/dist/src/commands/brief.d.ts +92 -0
  22. package/dist/src/commands/brief.js +343 -0
  23. package/dist/src/commands/checklist.d.ts +42 -0
  24. package/dist/src/commands/checklist.js +168 -0
  25. package/dist/src/commands/cutover.d.ts +35 -0
  26. package/dist/src/commands/cutover.js +448 -0
  27. package/dist/src/commands/doctor.d.ts +264 -0
  28. package/dist/src/commands/doctor.js +1546 -0
  29. package/dist/src/commands/gates.d.ts +9 -0
  30. package/dist/src/commands/gates.js +360 -0
  31. package/dist/src/commands/init.d.ts +30 -0
  32. package/dist/src/commands/init.js +177 -0
  33. package/dist/src/commands/lock.d.ts +34 -0
  34. package/dist/src/commands/lock.js +340 -0
  35. package/dist/src/commands/mode.d.ts +40 -0
  36. package/dist/src/commands/mode.js +134 -0
  37. package/dist/src/commands/next.d.ts +130 -0
  38. package/dist/src/commands/next.js +597 -0
  39. package/dist/src/commands/plan.d.ts +20 -0
  40. package/dist/src/commands/plan.js +105 -0
  41. package/dist/src/commands/pool.d.ts +1 -0
  42. package/dist/src/commands/pool.js +139 -0
  43. package/dist/src/commands/resume.d.ts +1 -0
  44. package/dist/src/commands/resume.js +88 -0
  45. package/dist/src/commands/spawn.d.ts +1 -0
  46. package/dist/src/commands/spawn.js +195 -0
  47. package/dist/src/commands/status.d.ts +21 -0
  48. package/dist/src/commands/status.js +122 -0
  49. package/dist/src/commands/sync.d.ts +47 -0
  50. package/dist/src/commands/sync.js +341 -0
  51. package/dist/src/commands/teardown.d.ts +1 -0
  52. package/dist/src/commands/teardown.js +87 -0
  53. package/dist/src/commands/tuition.d.ts +21 -0
  54. package/dist/src/commands/tuition.js +218 -0
  55. package/dist/src/commands/validate.d.ts +78 -0
  56. package/dist/src/commands/validate.js +430 -0
  57. package/dist/src/commands/watch.d.ts +1 -0
  58. package/dist/src/commands/watch.js +172 -0
  59. package/dist/src/cutover.d.ts +584 -0
  60. package/dist/src/cutover.js +1444 -0
  61. package/dist/src/exclusion.d.ts +389 -0
  62. package/dist/src/exclusion.js +843 -0
  63. package/dist/src/exec/env.d.ts +278 -0
  64. package/dist/src/exec/env.js +334 -0
  65. package/dist/src/fleet.d.ts +223 -0
  66. package/dist/src/fleet.js +298 -0
  67. package/dist/src/gates/adapters/http-json.d.ts +5 -0
  68. package/dist/src/gates/adapters/http-json.js +283 -0
  69. package/dist/src/gates/adapters/migrations-command.d.ts +1 -0
  70. package/dist/src/gates/adapters/migrations-command.js +373 -0
  71. package/dist/src/gates/citations.d.ts +408 -0
  72. package/dist/src/gates/citations.js +1169 -0
  73. package/dist/src/gates/coverage.d.ts +375 -0
  74. package/dist/src/gates/coverage.js +836 -0
  75. package/dist/src/gates/credentials.d.ts +233 -0
  76. package/dist/src/gates/credentials.js +752 -0
  77. package/dist/src/gates/deploy.d.ts +1 -0
  78. package/dist/src/gates/deploy.js +33 -0
  79. package/dist/src/gates/gate-classes.d.ts +56 -0
  80. package/dist/src/gates/gate-classes.js +633 -0
  81. package/dist/src/gates/manifest.d.ts +99 -0
  82. package/dist/src/gates/manifest.js +208 -0
  83. package/dist/src/gates/merge-preconditions.d.ts +319 -0
  84. package/dist/src/gates/merge-preconditions.js +932 -0
  85. package/dist/src/gates/migrations.d.ts +1 -0
  86. package/dist/src/gates/migrations.js +36 -0
  87. package/dist/src/gates/pin.d.ts +114 -0
  88. package/dist/src/gates/pin.js +154 -0
  89. package/dist/src/gates/red-witness.d.ts +22 -0
  90. package/dist/src/gates/red-witness.js +482 -0
  91. package/dist/src/gates/release.d.ts +283 -0
  92. package/dist/src/gates/release.js +820 -0
  93. package/dist/src/gates/result.d.ts +116 -0
  94. package/dist/src/gates/result.js +91 -0
  95. package/dist/src/gates/run.d.ts +614 -0
  96. package/dist/src/gates/run.js +1614 -0
  97. package/dist/src/gates/schemas/citation-config.schema.json +59 -0
  98. package/dist/src/gates/schemas/coverage-config.schema.json +77 -0
  99. package/dist/src/gates/schemas/gate-manifest.schema.json +125 -0
  100. package/dist/src/gates/schemas/gate-result.schema.json +160 -0
  101. package/dist/src/gates/schemas/phase-declaration.schema.json +87 -0
  102. package/dist/src/gates/schemas/release-record.schema.json +119 -0
  103. package/dist/src/gates/schemas/verifier-config.schema.json +101 -0
  104. package/dist/src/gates/schemas/witness-spec.schema.json +110 -0
  105. package/dist/src/gates/scope.d.ts +131 -0
  106. package/dist/src/gates/scope.js +1018 -0
  107. package/dist/src/gates/suite.d.ts +217 -0
  108. package/dist/src/gates/suite.js +968 -0
  109. package/dist/src/gates/validate.d.ts +121 -0
  110. package/dist/src/gates/validate.js +414 -0
  111. package/dist/src/hooks.d.ts +84 -0
  112. package/dist/src/hooks.js +125 -0
  113. package/dist/src/index.d.ts +31 -0
  114. package/dist/src/index.js +30 -0
  115. package/dist/src/liveness.d.ts +321 -0
  116. package/dist/src/liveness.js +396 -0
  117. package/dist/src/lock.d.ts +256 -0
  118. package/dist/src/lock.js +792 -0
  119. package/dist/src/model-resolution.d.ts +159 -0
  120. package/dist/src/model-resolution.js +307 -0
  121. package/dist/src/modes.d.ts +149 -0
  122. package/dist/src/modes.js +258 -0
  123. package/dist/src/path-identity.d.ts +34 -0
  124. package/dist/src/path-identity.js +48 -0
  125. package/dist/src/plan.d.ts +73 -0
  126. package/dist/src/plan.js +153 -0
  127. package/dist/src/pool.d.ts +326 -0
  128. package/dist/src/pool.js +988 -0
  129. package/dist/src/roles.d.ts +461 -0
  130. package/dist/src/roles.js +776 -0
  131. package/dist/src/spawn.d.ts +482 -0
  132. package/dist/src/spawn.js +1003 -0
  133. package/dist/src/status.d.ts +116 -0
  134. package/dist/src/status.js +148 -0
  135. package/dist/src/task.d.ts +504 -0
  136. package/dist/src/task.js +307 -0
  137. package/dist/src/teardown.d.ts +39 -0
  138. package/dist/src/teardown.js +422 -0
  139. package/dist/src/tuition.d.ts +159 -0
  140. package/dist/src/tuition.js +311 -0
  141. package/dist/src/validate.d.ts +263 -0
  142. package/dist/src/validate.js +742 -0
  143. package/dist/src/version.d.ts +3 -0
  144. package/dist/src/version.js +38 -0
  145. package/dist/src/watcher.d.ts +275 -0
  146. package/dist/src/watcher.js +849 -0
  147. package/dist/src/witness/run.d.ts +299 -0
  148. package/dist/src/witness/run.js +1373 -0
  149. package/dist/src/witness/spec.d.ts +270 -0
  150. package/dist/src/witness/spec.js +475 -0
  151. package/dist/tsconfig.src.tsbuildinfo +1 -0
  152. package/gate-registry.yaml +526 -0
  153. package/gates.manifest.json +257 -0
  154. package/package.json +72 -3
  155. package/role-model-config.yaml +88 -0
  156. package/roles/README.md +128 -0
  157. package/roles/_shared-dispatch-contract.md +87 -0
  158. package/roles/adversarial-plan-reviewer.md +80 -0
  159. package/roles/clean-room-reviewer.md +140 -0
  160. package/roles/implementer.md +463 -0
  161. package/roles/investigator.md +138 -0
  162. package/roles/plan-writer.md +95 -0
  163. package/schemas/README.md +82 -0
  164. package/schemas/assurance-modes.schema.json +264 -0
  165. package/schemas/charter.schema.json +185 -0
  166. package/schemas/checklist.schema.json +114 -0
  167. package/schemas/cutover-state.schema.json +64 -0
  168. package/schemas/decision-record.schema.json +88 -0
  169. package/schemas/executor-record.schema.json +36 -0
  170. package/schemas/final-report.schema.json +90 -0
  171. package/schemas/finding.schema.json +106 -0
  172. package/schemas/gate-registry.schema.json +260 -0
  173. package/schemas/mechanism-index.schema.json +94 -0
  174. package/schemas/model-resolution.schema.json +362 -0
  175. package/schemas/plan.schema.json +300 -0
  176. package/schemas/report.schema.json +579 -0
  177. package/schemas/role-brief.schema.json +105 -0
  178. package/schemas/role-model-config.schema.json +90 -0
  179. package/schemas/status-line.schema.json +40 -0
  180. package/schemas/tuition.schema.json +191 -0
  181. package/schemas/verdict.schema.json +295 -0
  182. package/schemas/work-history.schema.json +183 -0
  183. package/schemas/write-bypass.schema.json +69 -0
  184. package/templates/charter.example.yaml +54 -0
  185. package/templates/decision-record.example.yaml +27 -0
  186. package/templates/final-report.example.yaml +80 -0
  187. package/templates/plan.example.yaml +87 -0
  188. package/templates/report.example.yaml +236 -0
  189. package/templates/warnings.md +74 -0
  190. package/templates/work-history.example.yaml +185 -0
  191. package/tuition/README.md +76 -0
  192. package/tuition/T-001.yaml +48 -0
  193. package/tuition/T-002.yaml +51 -0
  194. package/tuition/T-003.yaml +100 -0
  195. package/tuition/T-004.yaml +52 -0
  196. package/tuition/T-005.yaml +72 -0
  197. package/tuition/T-006.yaml +81 -0
  198. package/tuition/T-007.yaml +56 -0
  199. package/tuition/T-008.yaml +111 -0
  200. package/tuition/T-009.yaml +50 -0
  201. package/tuition/T-015.yaml +36 -0
  202. package/tuition/T-016.yaml +36 -0
  203. package/tuition/T-017.yaml +46 -0
  204. package/tuition/T-018.yaml +84 -0
  205. package/tuition/T-021.yaml +40 -0
  206. package/tuition/T-022.yaml +36 -0
  207. package/tuition/mechanism-index.yaml +256 -0
@@ -0,0 +1,116 @@
1
+ /**
2
+ * THE STATUS LINE (kernel plan M3, M3-P1 steps 5 and 6; R-084).
3
+ *
4
+ * The orchestrator narrates to the owner at MILESTONES ONLY: a phase merged,
5
+ * an incident, a decision needed. Routine noise gets a one-line ack and
6
+ * nothing else. That sparseness is expressed STRUCTURALLY in
7
+ * `schemas/status-line.schema.json` rather than as an instruction: the state
8
+ * vocabulary has exactly five members and none of them means "still working",
9
+ * so a heartbeat is not a thing this contract can say.
10
+ *
11
+ * CONSTRAINT C-1, NAMED BECAUSE IT DECIDES THE SHAPE OF THIS MODULE.
12
+ * "Never read current state from the tail of an append-only log." The stream
13
+ * at `state/status/stream.jsonl` is the HISTORY and is append-only. The
14
+ * current state is `status/current.json`, a whole document rewritten
15
+ * atomically on every emit. `readCurrent` opens `current.json` and nothing
16
+ * else; it does not know the stream's path. A truncated, corrupted or
17
+ * half-written stream therefore cannot change what `tiphys status show`
18
+ * reports, and that is the property criterion 7 witnesses in both directions.
19
+ *
20
+ * THE SPLIT (M4-D-13, decided in the M4-P18 plan section). The two documents
21
+ * used to sit side by side under the gitignored `state/` prefix, so the one
22
+ * sentence that says where the pipeline stands did not survive a fleet being
23
+ * reclaimed: AGENTS.md's `fleet-state-commit-discipline` clause names "the
24
+ * state file that says where the pipeline stands" as DURABLE, and an ignored
25
+ * path can be neither committed nor pushed. So the pointer moved OUT of
26
+ * `state/` to a tracked path and the stream stayed, because the stream is
27
+ * history that a restart rebuilds.
28
+ *
29
+ * C-1 IS THE REASON FOR THE SPLIT AND NOT A CONSTRAINT IT HAS TO DODGE.
30
+ * `readCurrent` opens the whole document and has no code path that reaches
31
+ * the stream, so moving the document changes WHERE it lives and changes
32
+ * NOTHING about how current state is read. The pair of witnesses in
33
+ * test/status.test.ts is re-pointed at the new layout and still reddens
34
+ * under the same mutation: `readCurrent` aimed at STREAM_FILE.
35
+ *
36
+ * ATOMIC REWRITE means write a temp file beside the target and rename. A
37
+ * rename within one directory is atomic on POSIX, so a reader either sees the
38
+ * whole previous document or the whole new one and never a partial write.
39
+ * The temp file is created beside the DURABLE document, so the two paths in
40
+ * the rename stay inside one directory after the split.
41
+ */
42
+ /**
43
+ * Where the status files live inside a fleet home, and the two directories
44
+ * are on OPPOSITE sides of the fleet `.gitignore` by design (M4-D-13).
45
+ * `STATUS_DIR` is under the ignored `state/` prefix and holds the rebuilt
46
+ * history; `DURABLE_STATUS_DIR` is tracked and holds the one document a
47
+ * restart must not lose.
48
+ */
49
+ export declare const STATUS_DIR: string;
50
+ export declare const DURABLE_STATUS_DIR = "status";
51
+ export declare const STREAM_FILE: string;
52
+ export declare const CURRENT_FILE: string;
53
+ /**
54
+ * The five supervisor-actionable states, blueprint section 5. Duplicated
55
+ * from `schemas/status-line.schema.json` so the CLI can reject a bad state
56
+ * before it composes a record; `test/status.test.ts` asserts the two agree,
57
+ * because a duplicated vocabulary that nothing compares is a vocabulary that
58
+ * drifts.
59
+ */
60
+ export declare const STATUS_STATES: readonly string[];
61
+ export interface StatusRecord {
62
+ kind: "status-line";
63
+ at: string;
64
+ run: string;
65
+ project: string;
66
+ state: string;
67
+ detail: string;
68
+ refs: string[];
69
+ }
70
+ export interface EmitInput {
71
+ run: string;
72
+ project: string;
73
+ state: string;
74
+ detail?: string;
75
+ refs?: string[];
76
+ /** Injected so a test can assert an exact record rather than a shape. */
77
+ at?: string;
78
+ }
79
+ /** RFC 3339 UTC to whole seconds, the form the schema's pattern accepts. */
80
+ export declare function nowIso(): string;
81
+ export declare function makeStatusRecord(input: EmitInput): StatusRecord;
82
+ export type EmitOutcome = {
83
+ ok: true;
84
+ record: StatusRecord;
85
+ } | {
86
+ ok: false;
87
+ reason: string;
88
+ };
89
+ /**
90
+ * Append ONE line to the stream, then rewrite `current.json` atomically.
91
+ *
92
+ * The order is deliberate and is the same ordering rule M2's gate runner
93
+ * learned: the durable history is written first, so a crash between the two
94
+ * leaves a history that is ahead of the pointer rather than a pointer that
95
+ * names an event no record supports.
96
+ */
97
+ export declare function emitStatus(fleetRoot: string, record: StatusRecord): EmitOutcome;
98
+ export type CurrentRead = {
99
+ ok: true;
100
+ record: StatusRecord;
101
+ } | {
102
+ ok: false;
103
+ reason: string;
104
+ };
105
+ /**
106
+ * Read the current status. C-1: this function knows only `current.json`.
107
+ *
108
+ * It does not accept a stream path, does not fall back to the stream, and
109
+ * has no code path that opens it. That is what makes criterion 7 a
110
+ * demonstrable property rather than a convention: an implementation that
111
+ * reads the stream is a DIFFERENT function, and the criterion requires it to
112
+ * be written, shown red against corrupt stream bytes, and reverted.
113
+ */
114
+ export declare function readCurrent(fleetRoot: string): CurrentRead;
115
+ /** One line, the form `tiphys status show` prints. */
116
+ export declare function renderStatus(record: StatusRecord): string;
@@ -0,0 +1,148 @@
1
+ /**
2
+ * THE STATUS LINE (kernel plan M3, M3-P1 steps 5 and 6; R-084).
3
+ *
4
+ * The orchestrator narrates to the owner at MILESTONES ONLY: a phase merged,
5
+ * an incident, a decision needed. Routine noise gets a one-line ack and
6
+ * nothing else. That sparseness is expressed STRUCTURALLY in
7
+ * `schemas/status-line.schema.json` rather than as an instruction: the state
8
+ * vocabulary has exactly five members and none of them means "still working",
9
+ * so a heartbeat is not a thing this contract can say.
10
+ *
11
+ * CONSTRAINT C-1, NAMED BECAUSE IT DECIDES THE SHAPE OF THIS MODULE.
12
+ * "Never read current state from the tail of an append-only log." The stream
13
+ * at `state/status/stream.jsonl` is the HISTORY and is append-only. The
14
+ * current state is `status/current.json`, a whole document rewritten
15
+ * atomically on every emit. `readCurrent` opens `current.json` and nothing
16
+ * else; it does not know the stream's path. A truncated, corrupted or
17
+ * half-written stream therefore cannot change what `tiphys status show`
18
+ * reports, and that is the property criterion 7 witnesses in both directions.
19
+ *
20
+ * THE SPLIT (M4-D-13, decided in the M4-P18 plan section). The two documents
21
+ * used to sit side by side under the gitignored `state/` prefix, so the one
22
+ * sentence that says where the pipeline stands did not survive a fleet being
23
+ * reclaimed: AGENTS.md's `fleet-state-commit-discipline` clause names "the
24
+ * state file that says where the pipeline stands" as DURABLE, and an ignored
25
+ * path can be neither committed nor pushed. So the pointer moved OUT of
26
+ * `state/` to a tracked path and the stream stayed, because the stream is
27
+ * history that a restart rebuilds.
28
+ *
29
+ * C-1 IS THE REASON FOR THE SPLIT AND NOT A CONSTRAINT IT HAS TO DODGE.
30
+ * `readCurrent` opens the whole document and has no code path that reaches
31
+ * the stream, so moving the document changes WHERE it lives and changes
32
+ * NOTHING about how current state is read. The pair of witnesses in
33
+ * test/status.test.ts is re-pointed at the new layout and still reddens
34
+ * under the same mutation: `readCurrent` aimed at STREAM_FILE.
35
+ *
36
+ * ATOMIC REWRITE means write a temp file beside the target and rename. A
37
+ * rename within one directory is atomic on POSIX, so a reader either sees the
38
+ * whole previous document or the whole new one and never a partial write.
39
+ * The temp file is created beside the DURABLE document, so the two paths in
40
+ * the rename stay inside one directory after the split.
41
+ */
42
+ import { mkdirSync, renameSync, writeFileSync, appendFileSync } from "node:fs";
43
+ import { join } from "node:path";
44
+ import { refuseOpenForWrite, readRegularFileIfPresent } from "./task.js";
45
+ /**
46
+ * Where the status files live inside a fleet home, and the two directories
47
+ * are on OPPOSITE sides of the fleet `.gitignore` by design (M4-D-13).
48
+ * `STATUS_DIR` is under the ignored `state/` prefix and holds the rebuilt
49
+ * history; `DURABLE_STATUS_DIR` is tracked and holds the one document a
50
+ * restart must not lose.
51
+ */
52
+ export const STATUS_DIR = join("state", "status");
53
+ export const DURABLE_STATUS_DIR = "status";
54
+ export const STREAM_FILE = join(STATUS_DIR, "stream.jsonl");
55
+ export const CURRENT_FILE = join(DURABLE_STATUS_DIR, "current.json");
56
+ /**
57
+ * The five supervisor-actionable states, blueprint section 5. Duplicated
58
+ * from `schemas/status-line.schema.json` so the CLI can reject a bad state
59
+ * before it composes a record; `test/status.test.ts` asserts the two agree,
60
+ * because a duplicated vocabulary that nothing compares is a vocabulary that
61
+ * drifts.
62
+ */
63
+ export const STATUS_STATES = [
64
+ "blocked",
65
+ "done",
66
+ "failed",
67
+ "needs-decision",
68
+ "phase-change",
69
+ ];
70
+ /** RFC 3339 UTC to whole seconds, the form the schema's pattern accepts. */
71
+ export function nowIso() {
72
+ return `${new Date().toISOString().slice(0, 19)}Z`;
73
+ }
74
+ export function makeStatusRecord(input) {
75
+ return {
76
+ kind: "status-line",
77
+ at: input.at ?? nowIso(),
78
+ run: input.run,
79
+ project: input.project,
80
+ state: input.state,
81
+ detail: input.detail ?? "",
82
+ refs: input.refs ?? [],
83
+ };
84
+ }
85
+ /**
86
+ * Append ONE line to the stream, then rewrite `current.json` atomically.
87
+ *
88
+ * The order is deliberate and is the same ordering rule M2's gate runner
89
+ * learned: the durable history is written first, so a crash between the two
90
+ * leaves a history that is ahead of the pointer rather than a pointer that
91
+ * names an event no record supports.
92
+ */
93
+ export function emitStatus(fleetRoot, record) {
94
+ /* TWO DIRECTORIES SINCE THE SPLIT, and both are created before either is
95
+ written: the ephemeral one is rebuilt by `tiphys resume` after a reclaim
96
+ and the durable one comes across in a clone, so on any given fleet home
97
+ either can be the one that is absent. */
98
+ mkdirSync(join(fleetRoot, STATUS_DIR), { recursive: true });
99
+ mkdirSync(join(fleetRoot, DURABLE_STATUS_DIR), { recursive: true });
100
+ const streamPath = join(fleetRoot, STREAM_FILE);
101
+ const currentPath = join(fleetRoot, CURRENT_FILE);
102
+ const temporaryPath = `${currentPath}.tmp`;
103
+ for (const path of [streamPath, currentPath, temporaryPath]) {
104
+ const refusal = refuseOpenForWrite(path);
105
+ if (refusal !== undefined) {
106
+ return { ok: false, reason: refusal };
107
+ }
108
+ }
109
+ appendFileSync(streamPath, `${JSON.stringify(record)}\n`, "utf8");
110
+ writeFileSync(temporaryPath, `${JSON.stringify(record, undefined, 2)}\n`, "utf8");
111
+ renameSync(temporaryPath, currentPath);
112
+ return { ok: true, record };
113
+ }
114
+ /**
115
+ * Read the current status. C-1: this function knows only `current.json`.
116
+ *
117
+ * It does not accept a stream path, does not fall back to the stream, and
118
+ * has no code path that opens it. That is what makes criterion 7 a
119
+ * demonstrable property rather than a convention: an implementation that
120
+ * reads the stream is a DIFFERENT function, and the criterion requires it to
121
+ * be written, shown red against corrupt stream bytes, and reverted.
122
+ */
123
+ export function readCurrent(fleetRoot) {
124
+ const path = join(fleetRoot, CURRENT_FILE);
125
+ const read = readRegularFileIfPresent(path);
126
+ if (read.kind === "absent") {
127
+ return { ok: false, reason: `no status has been emitted in ${fleetRoot}` };
128
+ }
129
+ if (read.kind === "refused") {
130
+ return { ok: false, reason: read.reason };
131
+ }
132
+ let parsed;
133
+ try {
134
+ parsed = JSON.parse(read.body);
135
+ }
136
+ catch (error) {
137
+ return {
138
+ ok: false,
139
+ reason: `${path} is not readable as a status record: ${error.message}`,
140
+ };
141
+ }
142
+ return { ok: true, record: parsed };
143
+ }
144
+ /** One line, the form `tiphys status show` prints. */
145
+ export function renderStatus(record) {
146
+ const refs = record.refs.length === 0 ? "" : ` refs=${record.refs.join(",")}`;
147
+ return `${record.at} ${record.state} run=${record.run} project=${record.project}${record.detail === "" ? "" : ` ${record.detail}`}${refs}`;
148
+ }