omp-conductor 0.5.1 → 0.5.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.
package/README.md CHANGED
@@ -167,7 +167,8 @@ Also required on the host:
167
167
  account is invisible to the service. A unit running `User=fleet` cannot see
168
168
  `root`'s login, and workers then die at turn 0 with `No model selected`. Such a
169
169
  run is classified `env-start-failure` and charges **neither** the failure budget
170
- nor a continuation — an environment fault is not a failed implementation but
170
+ nor a continuation — an environment fault is not a failed implementation, and a
171
+ run that recorded no turn, no commit and no error did not attempt anything — but
171
172
  nothing dispatches successfully until the credential is reachable.
172
173
  - `gh`, already authenticated: every tracker operation shells out to it, so the
173
174
  daemon never handles a GitHub token itself.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omp-conductor",
3
- "version": "0.5.1",
3
+ "version": "0.5.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.",
@@ -65,10 +65,11 @@ function normalise(state: string): string {
65
65
  /**
66
66
  * Harness start failures, matched on the text it prints.
67
67
  *
68
- * Deliberately a small closed list rather than "anything at turn 0": a run that
69
- * ended at turn 0 for a reason nobody has named is exactly what `unknown` is for,
70
- * and quietly declaring it an environment fault would waive an attempt that may
71
- * have been genuinely spent.
68
+ * A small closed list rather than "any error at turn 0": a run that ended at
69
+ * turn 0 with an error nobody has named is exactly what `unknown` is for, and
70
+ * declaring that an environment fault would waive an attempt that may have been
71
+ * genuinely spent. A mirror fetch that failed is not a session that never
72
+ * started.
72
73
  */
73
74
  const START_FAILURE_SIGNATURES = [
74
75
  "no model selected",
@@ -90,6 +91,42 @@ export function startFailure(lastError: string | undefined): string | undefined
90
91
  return hit === undefined ? undefined : lastError.split("\n")[0]?.trim();
91
92
  }
92
93
 
94
+ /**
95
+ * Evidence that this run never started, or `undefined` when something did happen.
96
+ *
97
+ * Two shapes, and the second is the one the reference fleet actually produced.
98
+ *
99
+ * 1. Turn zero with a recognised harness start error — the session said why.
100
+ * 2. Turn zero, **no artifacts and no error text at all**. The row records
101
+ * nothing: no turn, no head, no salvage, not even a message. On the reference
102
+ * fleet every worker died at turn 0 with `No model selected`, and that string
103
+ * went into the tier-1 escalation rather than into `lastError` — so the rows
104
+ * for issues 373, 359, 343 and 319 carry no error whatsoever and were logged
105
+ * `unknown`, each charging an implementation attempt (#152).
106
+ *
107
+ * Shape 2 is a deliberate judgement, not a loosening. There is no evidence an
108
+ * attempt was spent, so charging one is charging for a run whose only trace is
109
+ * that it existed; and the two ways of being wrong are not symmetric — being
110
+ * generous grants an issue one more attempt a human will see, while being strict
111
+ * leaves it permanently undispatchable for a fault that was never in its code.
112
+ * A turn-0 run carrying an *unrecognised* error still reads `unknown`, because
113
+ * there the row does say something happened.
114
+ *
115
+ * Shared with the store's one-time repair so history and future agree.
116
+ */
117
+ export function neverStarted(
118
+ run: Pick<RunRecord, "turns" | "lastError" | "prUrl" | "headSha" | "salvageSha">,
119
+ ): string | undefined {
120
+ if (run.turns !== 0) return undefined;
121
+ const detail = startFailure(run.lastError);
122
+ if (detail !== undefined) return `the session never started: ${detail}`;
123
+ const produced = run.prUrl !== undefined || run.headSha !== undefined || run.salvageSha !== undefined;
124
+ if (!produced && run.lastError === undefined) {
125
+ return "the session never started: no turns, no commit and no error were recorded";
126
+ }
127
+ return undefined;
128
+ }
129
+
93
130
  export function classifyRun(run: RunRecord, facts: ClassifyFacts): Classification {
94
131
  const hasArtifacts = run.prUrl !== undefined || run.headSha !== undefined || run.salvageSha !== undefined;
95
132
 
@@ -113,19 +150,14 @@ export function classifyRun(run: RunRecord, facts: ClassifyFacts): Classificatio
113
150
  };
114
151
  }
115
152
 
116
- // Turn zero with an explicit harness start error is the most classifiable
117
- // failure there is, and the least deserving of an implementation attempt: the
118
- // session never got as far as reading the issue. 27 recoveries on the reference
119
- // fleet were this, logged as `unknown` while the session's own output carried
120
- // the literal string `No model selected` (#152).
121
- if ((run.state === "failed" || run.state === "killed") && run.turns === 0) {
122
- const detail = startFailure(run.lastError);
123
- if (detail !== undefined) {
124
- return {
125
- cls: "env-start-failure",
126
- recovery: "escalate",
127
- evidence: `the session never started: ${detail}`,
128
- };
153
+ // A run that never started is the most classifiable failure there is, and the
154
+ // least deserving of an implementation attempt: the session did not get as far
155
+ // as reading the issue. See {@link neverStarted} for the two shapes and why the
156
+ // second one counts.
157
+ if (run.state === "failed" || run.state === "killed") {
158
+ const evidence = neverStarted(run);
159
+ if (evidence !== undefined) {
160
+ return { cls: "env-start-failure", recovery: "escalate", evidence };
129
161
  }
130
162
  }
131
163
 
package/src/store.ts CHANGED
@@ -15,7 +15,7 @@ import { dirname, join } from "node:path";
15
15
 
16
16
  import { stateDir } from "./config.ts";
17
17
  import { DECISION_TTL_MS, DEFAULT_CAPS } from "./types.ts";
18
- import { startFailure } from "./failure-class.ts";
18
+ import { neverStarted } from "./failure-class.ts";
19
19
  import type {
20
20
  DecisionDraft,
21
21
  FailureClass,
@@ -693,18 +693,32 @@ export function openStore(dbPath: string): Store {
693
693
  // charged, and an issue sitting at its cap for a fault that was never in its
694
694
  // code stays undispatchable forever.
695
695
  //
696
- // Narrow by construction: `turns = 0` (the session never took a turn), an
697
- // error the classifier itself recognises as a start failure, and a class of
696
+ // Narrow by construction, and narrow through the CLASSIFIER's own predicate
697
+ // rather than a second copy of the rule: {@link neverStarted}. A class of
698
698
  // exactly `unknown` — a row a human or a later release classified as anything
699
- // else is left alone. Idempotent for free: afterwards those rows read
700
- // `env-start-failure`, which the WHERE no longer matches.
699
+ // else is left alone plus zero turns and either a recognised start error or
700
+ // nothing recorded at all. A turn-0 row carrying an unrecognised error (a
701
+ // failed mirror fetch, say) is untouched. Idempotent for free: afterwards these
702
+ // rows read `env-start-failure`, which the WHERE no longer matches.
701
703
  const misread = db
702
704
  .query<
703
- { id: string; lastError: string | null },
705
+ { id: string; lastError: string | null; prUrl: string | null; headSha: string | null; salvageSha: string | null },
704
706
  []
705
- >(`SELECT id, lastError FROM runs WHERE failureClass = 'unknown' AND turns = 0 AND lastError IS NOT NULL`)
707
+ >(
708
+ `SELECT id, lastError, prUrl, headSha, salvageSha FROM runs
709
+ WHERE failureClass = 'unknown' AND turns = 0`,
710
+ )
706
711
  .all()
707
- .filter((row) => startFailure(row.lastError ?? undefined) !== undefined);
712
+ .filter(
713
+ (row) =>
714
+ neverStarted({
715
+ turns: 0,
716
+ ...(row.lastError === null ? {} : { lastError: row.lastError }),
717
+ ...(row.prUrl === null ? {} : { prUrl: row.prUrl }),
718
+ ...(row.headSha === null ? {} : { headSha: row.headSha }),
719
+ ...(row.salvageSha === null ? {} : { salvageSha: row.salvageSha }),
720
+ }) !== undefined,
721
+ );
708
722
  if (misread.length > 0) {
709
723
  const reclassify = db.query<unknown, [string]>(
710
724
  `UPDATE runs SET failureClass = 'env-start-failure' WHERE id = ?`,