@tiphys/kernel 0.0.0 → 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 (181) hide show
  1. package/AGENTS.md +611 -0
  2. package/LICENSE +202 -0
  3. package/assurance-modes.yaml +278 -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 +14 -0
  11. package/dist/src/brief.d.ts +32 -0
  12. package/dist/src/brief.js +59 -0
  13. package/dist/src/checklists.d.ts +169 -0
  14. package/dist/src/checklists.js +310 -0
  15. package/dist/src/checks.d.ts +828 -0
  16. package/dist/src/checks.js +3314 -0
  17. package/dist/src/cli.d.ts +9 -0
  18. package/dist/src/cli.js +57 -0
  19. package/dist/src/commands/brief.d.ts +92 -0
  20. package/dist/src/commands/brief.js +320 -0
  21. package/dist/src/commands/checklist.d.ts +42 -0
  22. package/dist/src/commands/checklist.js +168 -0
  23. package/dist/src/commands/doctor.d.ts +35 -0
  24. package/dist/src/commands/doctor.js +605 -0
  25. package/dist/src/commands/gates.d.ts +9 -0
  26. package/dist/src/commands/gates.js +360 -0
  27. package/dist/src/commands/init.d.ts +30 -0
  28. package/dist/src/commands/init.js +128 -0
  29. package/dist/src/commands/lock.d.ts +1 -0
  30. package/dist/src/commands/lock.js +229 -0
  31. package/dist/src/commands/mode.d.ts +40 -0
  32. package/dist/src/commands/mode.js +134 -0
  33. package/dist/src/commands/plan.d.ts +20 -0
  34. package/dist/src/commands/plan.js +105 -0
  35. package/dist/src/commands/pool.d.ts +1 -0
  36. package/dist/src/commands/pool.js +128 -0
  37. package/dist/src/commands/spawn.d.ts +1 -0
  38. package/dist/src/commands/spawn.js +146 -0
  39. package/dist/src/commands/status.d.ts +19 -0
  40. package/dist/src/commands/status.js +120 -0
  41. package/dist/src/commands/teardown.d.ts +1 -0
  42. package/dist/src/commands/teardown.js +79 -0
  43. package/dist/src/commands/tuition.d.ts +21 -0
  44. package/dist/src/commands/tuition.js +218 -0
  45. package/dist/src/commands/validate.d.ts +78 -0
  46. package/dist/src/commands/validate.js +360 -0
  47. package/dist/src/commands/watch.d.ts +1 -0
  48. package/dist/src/commands/watch.js +172 -0
  49. package/dist/src/exec/env.d.ts +128 -0
  50. package/dist/src/exec/env.js +190 -0
  51. package/dist/src/fleet.d.ts +51 -0
  52. package/dist/src/fleet.js +80 -0
  53. package/dist/src/gates/adapters/http-json.d.ts +5 -0
  54. package/dist/src/gates/adapters/http-json.js +283 -0
  55. package/dist/src/gates/adapters/migrations-command.d.ts +1 -0
  56. package/dist/src/gates/adapters/migrations-command.js +373 -0
  57. package/dist/src/gates/citations.d.ts +408 -0
  58. package/dist/src/gates/citations.js +1163 -0
  59. package/dist/src/gates/coverage.d.ts +284 -0
  60. package/dist/src/gates/coverage.js +701 -0
  61. package/dist/src/gates/credentials.d.ts +74 -0
  62. package/dist/src/gates/credentials.js +533 -0
  63. package/dist/src/gates/deploy.d.ts +1 -0
  64. package/dist/src/gates/deploy.js +33 -0
  65. package/dist/src/gates/manifest.d.ts +99 -0
  66. package/dist/src/gates/manifest.js +208 -0
  67. package/dist/src/gates/migrations.d.ts +1 -0
  68. package/dist/src/gates/migrations.js +36 -0
  69. package/dist/src/gates/pin.d.ts +114 -0
  70. package/dist/src/gates/pin.js +154 -0
  71. package/dist/src/gates/red-witness.d.ts +22 -0
  72. package/dist/src/gates/red-witness.js +390 -0
  73. package/dist/src/gates/release.d.ts +283 -0
  74. package/dist/src/gates/release.js +820 -0
  75. package/dist/src/gates/result.d.ts +116 -0
  76. package/dist/src/gates/result.js +91 -0
  77. package/dist/src/gates/run.d.ts +566 -0
  78. package/dist/src/gates/run.js +1536 -0
  79. package/dist/src/gates/schemas/citation-config.schema.json +59 -0
  80. package/dist/src/gates/schemas/coverage-config.schema.json +77 -0
  81. package/dist/src/gates/schemas/gate-manifest.schema.json +125 -0
  82. package/dist/src/gates/schemas/gate-result.schema.json +160 -0
  83. package/dist/src/gates/schemas/phase-declaration.schema.json +42 -0
  84. package/dist/src/gates/schemas/release-record.schema.json +119 -0
  85. package/dist/src/gates/schemas/verifier-config.schema.json +101 -0
  86. package/dist/src/gates/schemas/witness-spec.schema.json +110 -0
  87. package/dist/src/gates/scope.d.ts +131 -0
  88. package/dist/src/gates/scope.js +1018 -0
  89. package/dist/src/gates/suite.d.ts +217 -0
  90. package/dist/src/gates/suite.js +927 -0
  91. package/dist/src/gates/validate.d.ts +121 -0
  92. package/dist/src/gates/validate.js +414 -0
  93. package/dist/src/hooks.d.ts +32 -0
  94. package/dist/src/hooks.js +62 -0
  95. package/dist/src/liveness.d.ts +321 -0
  96. package/dist/src/liveness.js +396 -0
  97. package/dist/src/lock.d.ts +178 -0
  98. package/dist/src/lock.js +500 -0
  99. package/dist/src/modes.d.ts +149 -0
  100. package/dist/src/modes.js +258 -0
  101. package/dist/src/path-identity.d.ts +2 -0
  102. package/dist/src/path-identity.js +10 -0
  103. package/dist/src/plan.d.ts +73 -0
  104. package/dist/src/plan.js +153 -0
  105. package/dist/src/pool.d.ts +130 -0
  106. package/dist/src/pool.js +721 -0
  107. package/dist/src/roles.d.ts +430 -0
  108. package/dist/src/roles.js +734 -0
  109. package/dist/src/spawn.d.ts +177 -0
  110. package/dist/src/spawn.js +332 -0
  111. package/dist/src/status.d.ts +91 -0
  112. package/dist/src/status.js +119 -0
  113. package/dist/src/task.d.ts +264 -0
  114. package/dist/src/task.js +305 -0
  115. package/dist/src/teardown.d.ts +32 -0
  116. package/dist/src/teardown.js +314 -0
  117. package/dist/src/tuition.d.ts +159 -0
  118. package/dist/src/tuition.js +311 -0
  119. package/dist/src/validate.d.ts +230 -0
  120. package/dist/src/validate.js +732 -0
  121. package/dist/src/version.d.ts +3 -0
  122. package/dist/src/version.js +38 -0
  123. package/dist/src/watcher.d.ts +275 -0
  124. package/dist/src/watcher.js +859 -0
  125. package/dist/src/witness/run.d.ts +274 -0
  126. package/dist/src/witness/run.js +1327 -0
  127. package/dist/src/witness/spec.d.ts +102 -0
  128. package/dist/src/witness/spec.js +253 -0
  129. package/dist/tsconfig.src.tsbuildinfo +1 -0
  130. package/gate-registry.yaml +390 -0
  131. package/gates.manifest.json +195 -0
  132. package/package.json +57 -3
  133. package/role-model-config.yaml +88 -0
  134. package/roles/README.md +128 -0
  135. package/roles/_shared-dispatch-contract.md +87 -0
  136. package/roles/adversarial-plan-reviewer.md +80 -0
  137. package/roles/clean-room-reviewer.md +140 -0
  138. package/roles/implementer.md +460 -0
  139. package/roles/investigator.md +138 -0
  140. package/roles/plan-writer.md +95 -0
  141. package/schemas/README.md +81 -0
  142. package/schemas/assurance-modes.schema.json +264 -0
  143. package/schemas/charter.schema.json +166 -0
  144. package/schemas/checklist.schema.json +114 -0
  145. package/schemas/decision-record.schema.json +88 -0
  146. package/schemas/final-report.schema.json +90 -0
  147. package/schemas/finding.schema.json +106 -0
  148. package/schemas/gate-registry.schema.json +260 -0
  149. package/schemas/mechanism-index.schema.json +94 -0
  150. package/schemas/plan.schema.json +300 -0
  151. package/schemas/report.schema.json +579 -0
  152. package/schemas/role-brief.schema.json +105 -0
  153. package/schemas/role-model-config.schema.json +90 -0
  154. package/schemas/status-line.schema.json +40 -0
  155. package/schemas/tuition.schema.json +191 -0
  156. package/schemas/verdict.schema.json +289 -0
  157. package/schemas/work-history.schema.json +183 -0
  158. package/templates/charter.example.yaml +54 -0
  159. package/templates/decision-record.example.yaml +27 -0
  160. package/templates/final-report.example.yaml +80 -0
  161. package/templates/plan.example.yaml +87 -0
  162. package/templates/report.example.yaml +236 -0
  163. package/templates/warnings.md +74 -0
  164. package/templates/work-history.example.yaml +185 -0
  165. package/tuition/README.md +76 -0
  166. package/tuition/T-001.yaml +48 -0
  167. package/tuition/T-002.yaml +51 -0
  168. package/tuition/T-003.yaml +100 -0
  169. package/tuition/T-004.yaml +52 -0
  170. package/tuition/T-005.yaml +72 -0
  171. package/tuition/T-006.yaml +81 -0
  172. package/tuition/T-007.yaml +56 -0
  173. package/tuition/T-008.yaml +111 -0
  174. package/tuition/T-009.yaml +50 -0
  175. package/tuition/T-015.yaml +36 -0
  176. package/tuition/T-016.yaml +36 -0
  177. package/tuition/T-017.yaml +46 -0
  178. package/tuition/T-018.yaml +84 -0
  179. package/tuition/T-021.yaml +40 -0
  180. package/tuition/T-022.yaml +36 -0
  181. package/tuition/mechanism-index.yaml +256 -0
@@ -0,0 +1,3 @@
1
+ export declare function readOwnVersion(): string;
2
+ /** The version subcommand: print the package.json version, exit 0. */
3
+ export declare function printVersion(): number;
@@ -0,0 +1,38 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ /**
5
+ * Locate this package's own package.json by walking up from the running
6
+ * module's directory. The walk is depth-independent on purpose: the same
7
+ * source runs from src/ (native type stripping) and from dist/src/
8
+ * (compiled output), which sit at different depths below the package root.
9
+ */
10
+ function findOwnPackageJson() {
11
+ let dir = dirname(fileURLToPath(import.meta.url));
12
+ for (;;) {
13
+ const candidate = join(dir, "package.json");
14
+ if (existsSync(candidate)) {
15
+ return candidate;
16
+ }
17
+ const parent = dirname(dir);
18
+ if (parent === dir) {
19
+ throw new Error("package.json not found above " + import.meta.url);
20
+ }
21
+ dir = parent;
22
+ }
23
+ }
24
+ export function readOwnVersion() {
25
+ const raw = readFileSync(findOwnPackageJson(), "utf8");
26
+ const parsed = JSON.parse(raw);
27
+ if (typeof parsed !== "object" ||
28
+ parsed === null ||
29
+ typeof parsed.version !== "string") {
30
+ throw new Error("package.json has no string version field");
31
+ }
32
+ return parsed.version;
33
+ }
34
+ /** The version subcommand: print the package.json version, exit 0. */
35
+ export function printVersion() {
36
+ process.stdout.write(`${readOwnVersion()}\n`);
37
+ return 0;
38
+ }
@@ -0,0 +1,275 @@
1
+ import type { Fleet } from "./fleet.ts";
2
+ import type { WatchCadence } from "./liveness.ts";
3
+ /**
4
+ * The watcher (kernel plan v1, M1-P5 step 1; R-078, R-079; DR-0007;
5
+ * plan constraints C-1, C-2 and C-3).
6
+ *
7
+ * ONE CORE, TWO ENTRY MODES (DR-0007). Resident mode (tiphys watch) is a
8
+ * plain foreground process the caller owns. Single-pass mode (tiphys
9
+ * watch --once) performs exactly one evaluation of the same wake sources
10
+ * and exits, for external triggers on reclaimable substrates. Both modes
11
+ * run the same evaluation function over the same on-disk state; there is
12
+ * no adapter and no second implementation.
13
+ *
14
+ * C-3, and why there is nothing here to guard: the kernel never runs a
15
+ * watcher outside the caller's own foreground process. No flag exists
16
+ * that would make it outlive its caller, this module launches no child
17
+ * process of any kind, and arming is explicit: the operator starts it
18
+ * and the beacon is what proves it ran (FM-054, where about 30 minutes
19
+ * of supervision were lost to a shell that helpfully moved one out of
20
+ * sight).
21
+ *
22
+ * C-2: nothing here has any notion of a running program. Wake sources
23
+ * are FILES, currency comes from files, and the only clock question ever
24
+ * asked is "is this timestamp older than that one".
25
+ *
26
+ * C-1: task currency is read from tasks/<id>/meta.json status and the
27
+ * turn-end file, never from a log tail. state/last-wake.json is written
28
+ * and never read back by any code path in this kernel; deleting it or
29
+ * filling it with garbage changes no decision this module makes.
30
+ *
31
+ * REASON LINES (grammar harvested from firstmate, FM-002):
32
+ *
33
+ * signal <task-id> turn-end a turn-end file appeared for an open task
34
+ * stale <task-id> deadline an open task passed its executor.json
35
+ * deadline with no turn-end (PR-207)
36
+ * check <name> a one-shot check was requested through
37
+ * state/check-request
38
+ * heartbeat <n> the nth heartbeat since the last reset
39
+ *
40
+ * A wake is ONE line on stdout and then exit 0. The watcher surfaces
41
+ * wakes and never classifies, triages or absorbs them (FM-057:
42
+ * firstmate's absorb-triage grew to most of its 1126 lines because its
43
+ * completion signals were weak; a proposal to add classification here is
44
+ * a signal-design red flag, not a feature request).
45
+ *
46
+ * THE STALE ENUMERATION, and why it has exactly two entries in M1. The
47
+ * plan's grammar is "stale <what>" for "an open task whose worktree or
48
+ * meta is in a contradictory state, enumerated in the module docs", and
49
+ * PR-207 names the first entry:
50
+ *
51
+ * stale <task-id> deadline the executor record's deadline passed and
52
+ * no turn-end arrived (PR-207)
53
+ * stale <task-id> meta tasks/<id>/meta.json EXISTS and does not
54
+ * parse, so this module cannot establish
55
+ * whether the task is open (fix round,
56
+ * second reviewer finding 2)
57
+ *
58
+ * The second entry is the plan's own "meta is in a contradictory state"
59
+ * clause, not an invention: a record that exists and cannot be read is
60
+ * the one case where staying quiet would mean asserting "nothing to
61
+ * supervise here" from evidence this module does not have. A task
62
+ * directory with NO meta.json at all is a different thing (a spawn in
63
+ * progress or a rollback residue) and is not surfaced.
64
+ *
65
+ * Tuition T-002 asked for "task open, no turn-end, worktree dirty" as a
66
+ * wake condition; the declared-deadline half of that is what this module
67
+ * detects, from file evidence alone, and it is witnessed against a spawn
68
+ * that was genuinely stopped mid-payload rather than against a
69
+ * hand-built file state. The deadline-less half is not invented here,
70
+ * because the plan's own not-proven list states that abandonment of a
71
+ * task launched WITHOUT a deadline is not auto-detected in M1.
72
+ *
73
+ * WHAT AN EVALUATION IS, and why it matters for the beacon (PR-206,
74
+ * PR-009). An EVALUATION is a pass over the wake sources that also
75
+ * rewrites state/watcher.beacon: it happens when a resident watcher
76
+ * starts, on every heartbeat tick, whenever a wake is surfaced, and on
77
+ * every watch --once pass including a no-wake one. Between heartbeats a
78
+ * resident watcher SCANS its wake sources every poll interval (and
79
+ * immediately on a filesystem change); a scan that surfaces nothing
80
+ * writes nothing. That is what makes the beacon mean "supervision
81
+ * executed on schedule" rather than "a process is alive", and it is what
82
+ * puts the oldest healthy beacon age at one backoff cap plus one poll
83
+ * interval, which is the bound the guard's threshold has to clear
84
+ * (PR-009, enforced in src/liveness.ts).
85
+ *
86
+ * CADENCE LIVES ON DISK (FM-006, FM-045), never in process memory, so a
87
+ * resident restart and a single pass share one schedule:
88
+ * state/watcher.cadence.json holds {lastHeartbeatAt, backoffStreak}. On
89
+ * a virgin fleet the first evaluation initializes it to the current time
90
+ * and surfaces no heartbeat; the first heartbeat falls due one base
91
+ * interval later (PR-205).
92
+ *
93
+ * Turn-end currency (PR-204). A turn-end wake is surfaced at most once
94
+ * across both modes. state/watcher.seen.json records, per task, the
95
+ * identity of the last surfaced turn-end (size, modification time and a
96
+ * content signature, FM-005). The record is advanced under a claim file,
97
+ * written stage-then-rename and confirmed by reading it back, which is
98
+ * the discipline src/lock.ts established (it is mirrored rather than
99
+ * reused: the lock's primitive is lease-shaped and src/lock.ts is not in
100
+ * this phase's edit scope). The wake is appended to state/last-wake.json
101
+ * BEFORE the seen record advances (enqueue-before-suppress, FM-046: a
102
+ * stop between the two duplicates a wake rather than dropping it). Two
103
+ * passes racing on the same turn-end therefore resolve to exactly one
104
+ * surfacing; the loser reports no-wake.
105
+ *
106
+ * NO HEALTH FROM AN ABSENCE OF EVIDENCE (fix round, the property both
107
+ * reviewers' blocking findings share). Silence from this module means
108
+ * "there is nothing to surface", and it may only be said about states
109
+ * this module has POSITIVELY ESTABLISHED. Wherever it cannot establish
110
+ * one, the output is the loud one, never the quiet one:
111
+ *
112
+ * - a seen-state claim it could not take within the bounded wait is a
113
+ * STUCK CLAIM, not ordinary contention: nonzero exit, one reason
114
+ * line naming the file, and NO beacon write, so the guard sees
115
+ * supervision stop. This is src/lock.ts's rule, adopted verbatim
116
+ * rather than reinvented (see the note below).
117
+ * - a meta.json that exists and does not parse is surfaced as
118
+ * "stale <task-id> meta" rather than skipped.
119
+ * - any other raise ends the pass loudly (see the classification
120
+ * below).
121
+ *
122
+ * WHERE THE STUCK-CLAIM RULE COMES FROM, recorded because it should have
123
+ * reached this module the first time and did not: src/lock.ts's
124
+ * applyLeaseMutation has used the identical O_EXCL claim-file pattern
125
+ * since M1-P3, and its module doc (src/lock.ts, "No steal protocol
126
+ * exists on purpose (FM-058)") states the rule this module now follows:
127
+ * a claim file left behind by a mutation that stopped makes later
128
+ * mutations FAIL LOUDLY after a bounded wait, naming the file for manual
129
+ * removal, and no code ever breaks someone else's claim. That behavior
130
+ * was established by M1-P3's CR-204 after the U-2 race investigation
131
+ * (delivery/verification/u2-race-flake-investigation.md,
132
+ * delivery/review/clean-room-m1-p3.md). The first draft of this module
133
+ * copied the mechanism and dropped the loudness, which the second
134
+ * M1-P5 reviewer reproduced as a permanent, silent loss of every future
135
+ * turn-end wake. Anyone adding a third claim-file user should read
136
+ * src/lock.ts first.
137
+ *
138
+ * HOW A RAISED ERROR IS CLASSIFIED, decided once and applied
139
+ * structurally, because this module does filesystem work in a loop and
140
+ * the previous phase's two worst defects were raises walking past
141
+ * handlers that only understood returned failures (M1-P4 F-1 and F-2):
142
+ *
143
+ * - A file that is ABSENT is not an error. Task directories appear and
144
+ * vanish under a concurrent teardown, so every read of task state
145
+ * goes through a helper that turns ENOENT into "not there" and
146
+ * rethrows everything else.
147
+ * - Any OTHER raise ends the pass with a reason line and a nonzero
148
+ * exit. The watcher stops loudly instead of looping blind: a stopped
149
+ * watcher stops advancing the beacon, and the liveness guard then
150
+ * tells the operator on the next spawn, teardown or doctor.
151
+ * - The whole pass is wrapped by runStep (src/task.ts), so this holds
152
+ * for every site in it without depending on remembering it at each.
153
+ */
154
+ /** Exit code of a --once pass that found nothing actionable. */
155
+ export declare const NO_WAKE_EXIT = 3;
156
+ /** Wake-source and cadence state files, all under the fleet's state/. */
157
+ export declare const CADENCE_STATE_FILE: string;
158
+ export declare const SEEN_STATE_FILE: string;
159
+ export declare const LAST_WAKE_FILE: string;
160
+ export declare const CHECK_REQUEST_FILE: string;
161
+ export declare function cadencePath(fleet: Fleet): string;
162
+ export declare function seenPath(fleet: Fleet): string;
163
+ export declare function lastWakePath(fleet: Fleet): string;
164
+ export declare function checkRequestPath(fleet: Fleet): string;
165
+ /** Identity of a turn-end file (FM-005), the seen-state's unit. */
166
+ export interface SignalIdentity {
167
+ size: number;
168
+ mtimeMs: number;
169
+ signature: string;
170
+ }
171
+ export type SeenState = Record<string, SignalIdentity>;
172
+ export declare function sameIdentity(a: SignalIdentity, b: SignalIdentity): boolean;
173
+ export declare function readSeenState(fleet: Fleet): SeenState;
174
+ /** Cadence state (JSON per DR-0006, plan decision D-3). */
175
+ export interface CadenceState {
176
+ lastHeartbeatAt: string;
177
+ backoffStreak: number;
178
+ }
179
+ export declare function readCadenceState(fleet: Fleet): CadenceState | undefined;
180
+ export declare function writeCadenceState(fleet: Fleet, state: CadenceState): void;
181
+ /** The doubling heartbeat interval, capped (FM-044). */
182
+ export declare function intervalMsFor(streak: number, cadence: WatchCadence): number;
183
+ /** When the next heartbeat falls due, from on-disk state alone. */
184
+ export declare function nextHeartbeatDueMs(state: CadenceState, cadence: WatchCadence): number;
185
+ /**
186
+ * Rewrite the beacon. The timestamp STRICTLY increases even when two
187
+ * evaluations land in the same millisecond, because criterion 8 turns on
188
+ * a no-wake pass advancing it and "advanced" has to mean something a
189
+ * reader can compare.
190
+ */
191
+ export declare function writeBeacon(fleet: Fleet, nowMs: number, streak: number, cadence: WatchCadence): number;
192
+ /** A surfaced wake, or the heartbeat that stands in for one. */
193
+ export type Wake = {
194
+ kind: "signal";
195
+ taskId: string;
196
+ event: "turn-end";
197
+ identity: SignalIdentity;
198
+ } | {
199
+ kind: "stale";
200
+ taskId: string;
201
+ what: "deadline" | "meta";
202
+ } | {
203
+ kind: "check";
204
+ name: string;
205
+ };
206
+ export declare function wakeLine(wake: Wake): string;
207
+ export declare function heartbeatLine(n: number): string;
208
+ export type ScanResult = {
209
+ ok: true;
210
+ wake: Wake | undefined;
211
+ } | {
212
+ ok: false;
213
+ reason: string;
214
+ };
215
+ /** Scan, with the module's raise classification applied structurally. */
216
+ export declare function scanWakeSources(fleet: Fleet, nowMs: number): ScanResult;
217
+ export type SurfaceResult = {
218
+ kind: "surfaced";
219
+ line: string;
220
+ } | {
221
+ kind: "lost";
222
+ } | {
223
+ kind: "stuck";
224
+ reason: string;
225
+ };
226
+ /**
227
+ * Surface a wake: make it durable, suppress repeats where the plan says
228
+ * repeats are wrong, and return the line to print. "lost" means another
229
+ * pass owns this wake and this one must report no-wake; "stuck" means
230
+ * this pass could not establish anything and must say so out loud.
231
+ */
232
+ export declare function surfaceWake(fleet: Fleet, wake: Wake, nowMs: number): Promise<SurfaceResult>;
233
+ export interface WatchOptions {
234
+ cadence: WatchCadence;
235
+ /**
236
+ * Resident mode only: exit with a heartbeat line after this many
237
+ * heartbeats HAVE TICKED IN THIS RUN. It is a bound on the run, not a
238
+ * position in the schedule, so a bounded run behaves the same way on a
239
+ * fleet that has been supervised for hours as on a virgin one. The
240
+ * schedule itself stays on disk (FM-006), which is why the number the
241
+ * line reports is the cadence ordinal and can be larger than this
242
+ * bound.
243
+ */
244
+ maxHeartbeats: number | undefined;
245
+ /** Injectable clock; tests use the real one, this is for determinism. */
246
+ now?: () => number;
247
+ }
248
+ export interface PassOutcome {
249
+ code: number;
250
+ /** stdout line, without its newline; empty means print nothing. */
251
+ line: string;
252
+ /** stderr reason for a nonzero, non-no-wake outcome. */
253
+ reason?: string;
254
+ }
255
+ /**
256
+ * A heartbeat evaluation: advance the streak, record it, rewrite the
257
+ * beacon. Returns the heartbeat ordinal since the last cadence reset,
258
+ * which is the n in "heartbeat <n>".
259
+ */
260
+ export declare function heartbeatTick(fleet: Fleet, state: CadenceState, cadence: WatchCadence, nowMs: number): number;
261
+ /**
262
+ * Single-pass mode: exactly one evaluation, then exit. A heartbeat that
263
+ * is due is surfaced (this pass may be the only supervision this fleet
264
+ * gets), and a pass with nothing actionable prints nothing and exits
265
+ * with the documented no-wake code. The beacon is rewritten either way
266
+ * (PR-206).
267
+ */
268
+ export declare function runOnce(fleet: Fleet, options: WatchOptions): Promise<PassOutcome>;
269
+ /**
270
+ * Resident mode: a plain foreground loop the caller owns. It evaluates
271
+ * once at startup (so an already-pending wake is surfaced immediately
272
+ * and the beacon proves arming), then alternates cheap scans at the poll
273
+ * interval with heartbeat evaluations on the backoff schedule.
274
+ */
275
+ export declare function runResident(fleet: Fleet, options: WatchOptions): Promise<PassOutcome>;