omp-conductor 0.3.1 → 0.3.2

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 (3) hide show
  1. package/README.md +24 -17
  2. package/package.json +1 -1
  3. package/src/daemon.ts +15 -3
package/README.md CHANGED
@@ -769,23 +769,30 @@ prompt was never consumed, and the extension:
769
769
  Both escapes deliberately leave the session, because a loop that cannot drain
770
770
  its queue cannot report on itself — that is the whole failure.
771
771
 
772
- **Two things read it.** A marker nobody consumes is an artifact, not an alert:
773
-
774
- - **The daemon**, on its own five-minute tick, and *before* its pause check. The
775
- orchestrator is a different process and can be wedged while the fleet is
776
- deliberately paused precisely the state the dogfood fleet was in when this
777
- happened. One tier-2 page per stall, dated, re-armed when the marker clears,
778
- and latched only once the page is confirmed delivered, so an escalation
779
- channel that fails on the one tick that noticed does not buy permanent
780
- silence. It restarts nothing: a wedge lands mid-turn, and this process cannot
781
- tell a half-applied edit from an idle loop.
782
- - **herdr-conductor's recovery**, which treats marker-present as *not live*. Its
783
- liveness test agent listed AND a non-shell foreground process passes
784
- straight through a wedge, so without this a recovery run certifies a stuck
785
- session as healthy and returns "nothing to do".
786
-
787
- The daemon is what covers a wedge beginning *between* herdr's lifecycle events,
788
- since a session that stays alive and stops working emits none of them.
772
+ **The daemon reads it.** A marker nobody consumes is an artifact, not an alert,
773
+ so the dispatch daemon checks it on its own five-minute tick — and *before* its
774
+ pause check. The orchestrator is a different process and can be wedged while
775
+ the fleet is deliberately paused, which is precisely the state the dogfood
776
+ fleet was in when this happened. One tier-2 page per stall, keyed on the
777
+ marker's own timestamp so a second wedge the same day is not swallowed as a
778
+ repeat, re-armed when the marker clears, and latched only once the page is
779
+ confirmed delivered — an escalation channel that fails on the one tick that
780
+ noticed must not buy permanent silence.
781
+
782
+ It restarts nothing. A wedge lands mid-turn, and no other process can tell a
783
+ half-applied edit from an idle loop; the operator attaches, looks, and decides.
784
+
785
+ **herdr-conductor deliberately does not read it**, though its liveness test
786
+ (agent listed AND a non-shell foreground process) passes straight through a
787
+ wedge. That plugin only runs on `startup`, `pane.exited` and
788
+ `pane.agent_detected`, and a session that stays alive and stops working emits
789
+ none of them — so the check could never fire during the wedge itself. What it
790
+ *would* catch is the recovery afterwards: the marker survives a restart until
791
+ the new session consumes a tick, so every operator SIGTERM-and-resume would
792
+ page about the healthy session they just fixed. Telling those apart needs the
793
+ process start time against the marker's, and herdr's `pane process-info`
794
+ reports pids, not start times. The daemon gives up at most one tick of
795
+ coverage and never cries wolf.
789
796
 
790
797
  The first tick that actually sends clears the counter and deletes the marker,
791
798
  and it deletes one it did not write: recovery normally arrives as a fresh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omp-conductor",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "A 24/7 dispatcher that takes ready GitHub issues to green, mergeable PRs using omp coding sessions, with tiered escalation first to an orchestrator session and then to a human.",
package/src/daemon.ts CHANGED
@@ -151,10 +151,13 @@ async function watchOrchestrator(d: Deps): Promise<void> {
151
151
  tier: 2,
152
152
  project: d.project.name,
153
153
  issue: NO_ISSUE,
154
- // Dated like the other tier-2 summaries: the dedup key is the summary, and
155
- // a second wedge next month must not read as a repeat of this one.
154
+ // Keyed on the marker's own timestamp, not the date. The dedup ledger keys
155
+ // on this summary, and two wedges in one day is not a hypothetical the
156
+ // failure mode is a session that gets stuck, gets restarted, and gets stuck
157
+ // again on the same cause an hour later. A day-keyed summary would report
158
+ // the first and silently swallow every one after it.
156
159
  summary:
157
- `Orchestrator session wedged on ${new Date().toISOString().slice(0, 10)} — ` +
160
+ `Orchestrator session wedged (${verdict.since ?? `marker at ${marker}`}) — ` +
158
161
  `it has stopped reading its queue (${d.project.name})`,
159
162
  detail: [
160
163
  verdict.since ?? "Marker present with no readable timestamp.",
@@ -731,6 +734,15 @@ async function tick(d: Deps): Promise<void> {
731
734
  // attributable. A legitimate deploy never trips it, because installing a new
732
735
  // build and restarting the unit re-records the baseline from the new files;
733
736
  // only an edit *underneath* a live daemon diverges from it.
737
+ //
738
+ // Below the pause gate on purpose, unlike the stall watch above. The property
739
+ // being defended is that no work is dispatched under a package the operator
740
+ // did not install — and a paused fleet dispatches nothing, so nothing needs
741
+ // attributing yet. Tampering during a pause is not missed, only deferred: the
742
+ // baseline is boot's, so the first tick after `resume` compares against it and
743
+ // pauses again before claiming anything. Checking above the gate instead would
744
+ // page on every legitimate build an operator deploys into a parked fleet,
745
+ // which is exactly when they deploy them.
734
746
  const integrity = checkIntegrity(d.integrity, packageManifest());
735
747
  if (integrity.pause) {
736
748
  const shown = integrity.diff.slice(0, INTEGRITY_SAMPLE);