omp-conductor 0.18.0 → 0.18.1

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 (61) hide show
  1. package/README.md +34 -0
  2. package/REFERENCE.md +60 -10
  3. package/agents/to-spec.md +90 -0
  4. package/package.json +2 -1
  5. package/schema/config.schema.json +29 -0
  6. package/src/admission.ts +204 -75
  7. package/src/ask.ts +268 -7
  8. package/src/board.ts +17 -3
  9. package/src/briefs/orchestrator.md +42 -14
  10. package/src/briefs/to-spec.md +84 -0
  11. package/src/briefs/worker.md +2 -1
  12. package/src/cli.ts +2 -0
  13. package/src/command-help.ts +11 -0
  14. package/src/command-manifest.ts +22 -0
  15. package/src/commands/context.ts +1 -0
  16. package/src/commands/drain.ts +176 -0
  17. package/src/commands/extend.ts +6 -10
  18. package/src/commands/status.ts +5 -1
  19. package/src/commands/watch.ts +50 -2
  20. package/src/commands/worker.ts +9 -10
  21. package/src/config-schema.ts +24 -0
  22. package/src/config.ts +42 -1
  23. package/src/daemon.ts +965 -36
  24. package/src/dashboard/app.js +4 -1
  25. package/src/dashboard/server.ts +5 -2
  26. package/src/decisions.ts +235 -17
  27. package/src/diff-flags.ts +75 -1
  28. package/src/doctor.ts +52 -0
  29. package/src/escalate.ts +9 -3
  30. package/src/failure-class.ts +28 -2
  31. package/src/fleet.ts +146 -22
  32. package/src/gitops.ts +188 -81
  33. package/src/graph-health.ts +35 -1
  34. package/src/graph.ts +66 -1
  35. package/src/harness-loader.ts +59 -0
  36. package/src/host.ts +567 -2
  37. package/src/lifecycle.ts +122 -1
  38. package/src/omp.ts +227 -20
  39. package/src/orchestrator-tick.ts +1386 -15
  40. package/src/orchestrator.ts +12 -0
  41. package/src/privileged.ts +1 -4
  42. package/src/release-policy.ts +503 -9
  43. package/src/session-host.ts +99 -5
  44. package/src/settlement.ts +69 -17
  45. package/src/setup-host.ts +1205 -6
  46. package/src/setup-install.ts +28 -0
  47. package/src/setup-wizard.ts +13 -2
  48. package/src/setup.ts +29 -13
  49. package/src/shell.ts +15 -0
  50. package/src/status-render.ts +78 -11
  51. package/src/store.ts +443 -42
  52. package/src/to-spec.ts +387 -0
  53. package/src/tracker/github.ts +104 -14
  54. package/src/types.ts +343 -13
  55. package/src/upgrade-verify.ts +209 -2
  56. package/src/upgrade.ts +175 -1
  57. package/src/verbs/protocol.ts +39 -0
  58. package/src/verbs/server.ts +730 -56
  59. package/src/verbs/socket.ts +24 -5
  60. package/src/worker.ts +25 -2
  61. package/src/worktree.ts +29 -12
package/src/types.ts CHANGED
@@ -690,6 +690,21 @@ export interface ReviewRevisionRecord {
690
690
  outcome?: ReviewRevisionOutcome;
691
691
  }
692
692
 
693
+ /**
694
+ * The outcome of recording one `conductor_pr_review` finding against a run's
695
+ * review-revision outbox (#786). `created` opened a new revision round for the
696
+ * run; `appended` folded the finding into the revision already pending
697
+ * undelivered on the same reviewed head — one round, one row, one worker
698
+ * attempt either way, and the resumed session reads both findings. `refused`
699
+ * names the one in-flight shape that cannot be amended: a pending revision at
700
+ * a different head, which is a review of a moved PR and must dispatch and
701
+ * settle before another finding for the same run is recorded.
702
+ */
703
+ export type ReviewRevisionEnqueue =
704
+ | { kind: "created"; record: ReviewRevisionRecord }
705
+ | { kind: "appended"; record: ReviewRevisionRecord }
706
+ | { kind: "refused"; block: "pending-different-head" };
707
+
693
708
  /**
694
709
  * A model-supplied justification: one value out of a closed set, plus prose that
695
710
  * is written down and read by nothing that decides.
@@ -926,6 +941,38 @@ export const CONFIG_VERSION = 2;
926
941
  */
927
942
  export const READABLE_CONFIG_VERSIONS = [1, CONFIG_VERSION] as const;
928
943
 
944
+ /**
945
+ * Operator-authored host facts rendered into every worker brief (#721) — the
946
+ * typed replacement for host prose hand-written into an untracked agent
947
+ * context file. Only what cannot be derived lives here: cores and RAM are
948
+ * read from the host by `host.ts` at render time, and the guarded shell
949
+ * suites stay `SHARED_HOST_SCRIPTS`'s single source — nothing in this object
950
+ * may re-name a refused command.
951
+ *
952
+ * Every key is optional; a fleet that never fills the object loads a config
953
+ * with no `host` field at all and dispatches briefs byte-for-byte as before.
954
+ */
955
+ export interface HostConstraints {
956
+ /**
957
+ * What this host is and what else it runs, in the operator's words ("a
958
+ * shared 4-core VPS that also runs Langfuse and the fleet"). The renderer
959
+ * folds the derived core/RAM count into this line.
960
+ */
961
+ description?: string;
962
+ /**
963
+ * The non-interactive PATH a script or `ssh host '<cmd>'` invocation must
964
+ * export (`/root/.bun/bin:/root/.local/bin:$PATH` on this fleet) — commands
965
+ * run from an ssh session do not see the interactive shell's PATH.
966
+ */
967
+ path?: string;
968
+ /**
969
+ * Per-repo command conventions, keyed by the repo slug the brief renders
970
+ * as its `{{REPO}}` (`owner/repo`). A repo without an entry renders nothing
971
+ * for it.
972
+ */
973
+ conventions?: Record<string, string>;
974
+ }
975
+
929
976
  /**
930
977
  * On-disk root config. `version` is present from day one so a format change
931
978
  * can be migrated instead of silently misread by an older daemon.
@@ -938,6 +985,9 @@ export interface ConductorConfig {
938
985
  * `<stateDir()>/backups/db` when omitted. A snapshot that cannot land there
939
986
  * is `doctor`'s `db-backup` failure. */
940
987
  dbBackupDir?: string;
988
+ /** Host facts every worker brief renders (#721). Absent or empty renders
989
+ * nothing — no section — and the brief stays exactly what it always was. */
990
+ host?: HostConstraints;
941
991
  }
942
992
 
943
993
  /**
@@ -1025,6 +1075,11 @@ export const SETTLEMENT_FLAG_KINDS = [
1025
1075
  "assertions-removed",
1026
1076
  /** A named timeout in a test file went up. */
1027
1077
  "test-timeout-raised",
1078
+ /** The PR diff touches files outside the effective declared file lane —
1079
+ * the declaration admission enforced and the brief rendered, so a widened
1080
+ * lane is named on evidence rather than found by reading the file list by
1081
+ * hand, and the queued work it blocks is explainable (#739). */
1082
+ "lane-escape",
1028
1083
  /** A command the PR body claims as proof, with no matching command in the
1029
1084
  * run's transcript — the claim has no attempt behind it at all. */
1030
1085
  "claimed-proof-missing",
@@ -1046,8 +1101,8 @@ export type SettlementFlagKind = (typeof SETTLEMENT_FLAG_KINDS)[number];
1046
1101
  */
1047
1102
  export interface SettlementFlag {
1048
1103
  kind: SettlementFlagKind;
1049
- /** Repo-relative path, `(report)` for report evidence, or `(base branch)` for
1050
- * a post-merge workflow result. */
1104
+ /** Repo-relative path, `(report)` for report evidence, `(lane)` for a
1105
+ * lane-escape finding, or `(base branch)` for a post-merge workflow result. */
1051
1106
  file: string;
1052
1107
  /** 1-based line in the side of the diff the evidence came from: the
1053
1108
  * post-image for an added line, the pre-image for a removed one. */
@@ -1193,8 +1248,19 @@ export interface Tracker {
1193
1248
  issueBody(issue: number): Promise<string | undefined>;
1194
1249
  /**
1195
1250
  * The state of one specific pull request, or undefined when this adapter
1196
- * could not tell — a network failure, a deleted PR, a URL it cannot parse.
1197
- * Undefined never means "no".
1251
+ * could not tell — a network failure, a revoked token, a URL it cannot
1252
+ * parse, or a 404 the adapter could not corroborate. Undefined never means
1253
+ * "no". The one exception is a definitively missing PR: a REST 404 on the
1254
+ * individual read that a same-repository pulls-list read corroborates (a
1255
+ * deleted or never-created number in a repository the credential can still
1256
+ * read) is reported by throwing a classified `GhPrMissingError`. A
1257
+ * repository hidden from the credential answers the same 404 bytes, so a
1258
+ * bare 404 — token scope/SSO loss included — is never "missing": it stays
1259
+ * undefined and the row stays retryable. Every other tracker implementation
1260
+ * keeps the "never throws, undefined is could-not-tell" contract, and every
1261
+ * caller not acting on a missing PR keeps its existing fail-closed handling
1262
+ * by catching the throw exactly as it already catches any other tracker
1263
+ * failure.
1198
1264
  *
1199
1265
  * Deliberately separate from {@link Tracker.openCloserFor}, which asks
1200
1266
  * whether any OPEN closing PR exists and answers undefined for merged,
@@ -1203,6 +1269,11 @@ export interface Tracker {
1203
1269
  * is how a PR a human rejected gets recorded as merged.
1204
1270
  */
1205
1271
  prState(url: string): Promise<PrState | undefined>;
1272
+ /** The live head commit SHA of one pull request, or undefined when the
1273
+ * tracker could not tell. `pr-checks-green` binds its green verdict to the
1274
+ * head it was read at, so a head read that cannot answer is "not yet
1275
+ * satisfied" for that pass, never a green verdict (#808). */
1276
+ prHead(url: string): Promise<string | undefined>;
1206
1277
  /** The merge commit and base ref for a merged pull request. */
1207
1278
  mergedPrInfo(url: string): Promise<MergedPrInfo | undefined>;
1208
1279
  /** Workflow runs GitHub associated with one exact commit SHA. */
@@ -1292,9 +1363,9 @@ export interface Tracker {
1292
1363
  /** Issues carrying `label`, open or closed, bounded. Empty on any failure —
1293
1364
  * a reconcile that cannot list must remove no labels. */
1294
1365
  listLabeled(label: string, limit?: number): Promise<{ number: number; state: IssueState }[]>;
1295
- /** Sub-issues of `issue`, with their states. Empty when there are none *or*
1296
- * when the lookup failed: both mean "no evidence this was decomposed", and
1297
- * the reconcile below only ever acts on positive evidence. */
1366
+ /** Sub-issues of `issue`, with their states. Propagates lookup failures so
1367
+ * callers can choose the safe polarity: settlement catches and treats an
1368
+ * unread probe as no positive evidence, while launch selection fails closed. */
1298
1369
  childrenOf(issue: number): Promise<{ number: number; state: IssueState }[]>;
1299
1370
  }
1300
1371
 
@@ -1432,6 +1503,72 @@ export type RunState =
1432
1503
  /** In flight when its daemon process died; reconciled at the next startup. */
1433
1504
  | "orphaned";
1434
1505
 
1506
+ /** One parsed `File lane:` declaration: the paths and the verbatim source
1507
+ * line, so a renderer can reproduce the declaration itself rather than a
1508
+ * summary of it. Exported for the brief's guarantee that the gate's effective
1509
+ * lane is always visible to the worker (#608). */
1510
+ export interface LaneDeclaration {
1511
+ files: string[];
1512
+ /** The declaration line verbatim, as written on the surface it came from. */
1513
+ source: string;
1514
+ }
1515
+
1516
+ /**
1517
+ * The effective file lane as both admission and the worker brief must read it
1518
+ * (#608): the latest `File lane:` declaration among the issue body and every
1519
+ * comment, in the tracker's oldest-first order. This is the "later correction
1520
+ * visibly supersedes" contract applied across both surfaces at once, and it is
1521
+ * the single source of truth the gate enforces and the brief renders — so a
1522
+ * declaration can never control admission while staying invisible to the
1523
+ * worker. `at` records which surface won (`"body"`, or the 0-based comment
1524
+ * index), letting the brief reproduce the declaration verbatim when the
1525
+ * winning comment sits beyond its rendered discussion budget.
1526
+ */
1527
+ export interface FileLane extends LaneDeclaration {
1528
+ at: "body" | number;
1529
+ }
1530
+
1531
+ /** One parsed `Model:` declaration (#535): the selector and the verbatim
1532
+ * source line, so a renderer can reproduce the declaration itself rather
1533
+ * than a summary of it. */
1534
+ export interface ModelDeclaration {
1535
+ /** The selector in omp's own model syntax — a role alias (`@slow`) or a
1536
+ * concrete `provider/model`, either optionally `:thinking`-suffixed —
1537
+ * passed straight through as the session's `modelPattern`, for omp's
1538
+ * resolver to interpret. */
1539
+ model: string;
1540
+ /** The declaration line verbatim, as written on the surface it came from. */
1541
+ source: string;
1542
+ }
1543
+
1544
+ /**
1545
+ * The effective model as both dispatch and the worker brief must read it
1546
+ * (#535): the latest `Model:` declaration among the issue body and every
1547
+ * comment, in the tracker's oldest-first order — the same "later correction
1548
+ * visibly supersedes" contract applied across both surfaces at once as
1549
+ * {@link FileLane}, so a pre-dispatch comment can change the model a
1550
+ * promotion brief named in the body. `at` records which surface won
1551
+ * (`"body"`, or the 0-based comment index).
1552
+ */
1553
+ export interface EffectiveModel extends ModelDeclaration {
1554
+ at: "body" | number;
1555
+ }
1556
+
1557
+ /**
1558
+ * One dispatched run's observation of the code-graph MCP tools (#726): a
1559
+ * runtime fact read off the session's own registry at session start, never a
1560
+ * config guess. `present: true` and `present: false` are both observations —
1561
+ * an absent field (no `graphTools` on the row at all) means no observation was
1562
+ * recorded, which is deliberately distinct from "tools absent": the whole
1563
+ * point of the boolean is that "the model ignored a tool it had" and "the tool
1564
+ * was missing" no longer look identical from outside the session.
1565
+ */
1566
+ export interface GraphToolsObservation {
1567
+ present: boolean;
1568
+ /** Session-start wall clock (ms epoch), the moment the registry was read. */
1569
+ at: number;
1570
+ }
1571
+
1435
1572
  /**
1436
1573
  * One attempt at one issue. Persisted so a daemon restart can reconcile
1437
1574
  * orphaned worktrees and branches instead of leaking them.
@@ -1447,6 +1584,17 @@ export interface RunRecord {
1447
1584
  state: RunState;
1448
1585
  /** 1-based attempt number, checked against `Caps.maxAttemptsPerIssue`. */
1449
1586
  attempt: number;
1587
+ /**
1588
+ * The file-lane declaration admission resolved for this run, persisted at
1589
+ * dispatch from the carried `Admission.lane` (#744). It is the exact
1590
+ * snapshot the overlap gate enforced — the same value the brief renders —
1591
+ * and it is durable on purpose: the next dispatch pass builds lane occupancy
1592
+ * from it, so a run admitted with a declared lane it has not yet written
1593
+ * still holds those files across passes, until the row reaches a terminal
1594
+ * state and leaves the active set. Absent means the run predates the column
1595
+ * or was admitted with no declaration (fail open) — never "empty lane".
1596
+ */
1597
+ lane?: FileLane;
1450
1598
  turns: number;
1451
1599
  /** Effective turn ceiling for this run; operators may only raise it. */
1452
1600
  maxTurns: number;
@@ -1519,6 +1667,18 @@ export interface RunRecord {
1519
1667
  * (#286).
1520
1668
  */
1521
1669
  model?: string;
1670
+ /**
1671
+ * The code-graph session observation (#726): whether the graph MCP tools were
1672
+ * in this run's own session registry when the session started. Read off the
1673
+ * session itself, once, at dispatch — never derived from `mcp.json`, which
1674
+ * only says what a session *should* mount. It is the difference between "the
1675
+ * model ignored a tool it had" and "the tool was missing", which look
1676
+ * identical from outside a session. Absent means the run predates the column
1677
+ * or no worker session recorded an observation — never "graph tools were
1678
+ * absent": `present: false` is the only absence claim, and it is a runtime
1679
+ * fact, not an inference.
1680
+ */
1681
+ graphTools?: GraphToolsObservation;
1522
1682
  /** When an operator accepted the loss or recovered the tree by hand
1523
1683
  * (`unblock --force`). Clears the hold without erasing what happened. */
1524
1684
  salvageAckAt?: number;
@@ -1530,6 +1690,18 @@ export interface RunRecord {
1530
1690
  * Absent means never quarantined, or no longer — never "quarantine not
1531
1691
  * checked", because every retained-tree pass checks. */
1532
1692
  quarantineDetail?: string;
1693
+ /** Failed-attempt budget charges this row has accumulated through review
1694
+ * claims (#795 review round 2). Every chargeable terminal event that was
1695
+ * claimed into a review round adds one here, so `failuresFor` keeps
1696
+ * counting each event even after the round leaves the row `pushed-green`;
1697
+ * repeated failed rounds therefore keep consuming the failed-attempt
1698
+ * budget. Absent means zero charges were ever preserved on this row. */
1699
+ failureCharges?: number;
1700
+ /** Continuation budget charges this row has accumulated through review
1701
+ * claims, same semantics as {@link RunRecord.failureCharges}: each
1702
+ * chargeable cap or returned-for-revision event claimed into a round adds
1703
+ * one, and `continuationsFor` sums the column. */
1704
+ continuationCharges?: number;
1533
1705
  startedAt: number;
1534
1706
  endedAt?: number;
1535
1707
  /** Last failure text, surfaced verbatim in escalations. */
@@ -1640,6 +1812,14 @@ export interface DispatchSummary {
1640
1812
  * sweep and by classification recovery. Optional: persisted old rows lack
1641
1813
  * it, so readers use `?? 0`. (#497) */
1642
1814
  settled?: number;
1815
+ /**
1816
+ * Queue-labelled candidates the operator's park label disqualified — the
1817
+ * same eligibility read the claim gate uses, so the status number cannot
1818
+ * disagree with what admission would hold (#507). Distinguishes "0 claimable,
1819
+ * 12 parked" from "0 claimable, nothing to do". Optional: persisted old rows
1820
+ * lack it, and a pass with nothing parked omits it, so readers use `?? 0`.
1821
+ */
1822
+ parked?: number;
1643
1823
  /** True for a held pass's own record (#497): the queue was never routed, so
1644
1824
  * ready/routed/claimed are absent queue facts, not an empty queue. */
1645
1825
  paused?: boolean;
@@ -1879,6 +2059,49 @@ export interface IntakeDraft {
1879
2059
  at: number;
1880
2060
  }
1881
2061
 
2062
+ /**
2063
+ * The verdict surface of per-issue grooming state (#735). Admission writes
2064
+ * `blocked` when it holds a candidate for a file-lane or dependency reason —
2065
+ * the durable half of the misleading "groom the backlog" fix, because the tick
2066
+ * reads these instead of re-deriving why the runway cannot move. `promotable`
2067
+ * and `considered` are the outcomes #679's scout loop writes into the same
2068
+ * table, so grooming knowledge lives in one store rather than a second memory.
2069
+ */
2070
+ export const GROOMING_VERDICTS = ["promotable", "blocked", "considered"] as const;
2071
+
2072
+ export type GroomingVerdict = (typeof GROOMING_VERDICTS)[number];
2073
+
2074
+ /**
2075
+ * One issue's current grooming verdict, durable across restarts and keyed by
2076
+ * project + issue — one row per issue, replaced in place by upsert, never a
2077
+ * history. Deliberately NOT a decision row: a verdict is neither owed to the
2078
+ * operator nor TTL-exempt, so it sits outside `decisions` (7-day expiry,
2079
+ * operator-facing) and `material_events` (append-only digest outbox) alike.
2080
+ */
2081
+ export interface GroomingRecord {
2082
+ project: string;
2083
+ issue: number;
2084
+ verdict: GroomingVerdict;
2085
+ /** Why this verdict — admission's hold reason for `blocked` (`file-lane`,
2086
+ * `depends-on`), or the groomer's own label for #679's verdicts. */
2087
+ reason: string;
2088
+ /** What proved it: the hold detail naming the overlapping file/holder run,
2089
+ * or a scout summary. Free text, bounded at the write site. */
2090
+ evidence: string;
2091
+ recordedAt: number;
2092
+ }
2093
+
2094
+ /** What a caller hands over. The store owns the timestamp and the replace-in-
2095
+ * place semantics, nothing else. */
2096
+ export interface GroomingDraft {
2097
+ project: string;
2098
+ issue: number;
2099
+ verdict: GroomingVerdict;
2100
+ reason: string;
2101
+ evidence: string;
2102
+ at: number;
2103
+ }
2104
+
1882
2105
  /**
1883
2106
  * Where one operator decision stands (#136).
1884
2107
  *
@@ -1930,6 +2153,12 @@ export interface DecisionRecord {
1930
2153
  /** When that precondition was first observed true. Set once; the digest
1931
2154
  * promotes the row from "parked" to "act on this now". */
1932
2155
  conditionMetAt?: number;
2156
+ /** For `pr-checks-green` / `pr-review-ready`: the exact PR head whose checks
2157
+ * satisfied the condition, read in the same pass as the green verdict. A
2158
+ * head change invalidates the verdict, so a met row whose PR head has moved
2159
+ * returns to pending instead of rendering `[CONDITION MET]` for a stale
2160
+ * head (#808). */
2161
+ conditionHead?: string;
1933
2162
  state: DecisionState;
1934
2163
  resolvedAt?: number;
1935
2164
  /** The answer, the withdrawal reason, or the expiry note. */
@@ -1987,6 +2216,17 @@ export interface Store {
1987
2216
  /** Partial patch; an explicit `null` clears a column, `undefined`/absence leaves it alone (#468). */
1988
2217
  updateRun(id: string, patch: RunPatch): void;
1989
2218
  getRun(id: string): RunRecord | undefined;
2219
+ /**
2220
+ * Counts of this project's runs that recorded a graph-tools session
2221
+ * observation (#726), split by the observed truth value. The doctor reads
2222
+ * these to report what dispatched sessions actually saw, and reports nothing
2223
+ * as "observed" when no run has recorded one.
2224
+ */
2225
+ graphToolsObservationCounts(project: string): {
2226
+ recorded: number;
2227
+ present: number;
2228
+ absent: number;
2229
+ };
1990
2230
  /** Runs whose issue is occupied: a live worker, or a green PR awaiting merge. */
1991
2231
  activeRuns(project: string): RunRecord[];
1992
2232
  /** Runs backed by a worker process — what capacity counts. Subset of {@link Store.activeRuns}. */
@@ -2002,10 +2242,20 @@ export interface Store {
2002
2242
  * predecessor opened. Merged rows keep the same recent-history bound. */
2003
2243
  runsForProjectPr(project: string, prUrl: string, mergedSinceEpochMs: number): RunRecord[];
2004
2244
  /** Persist one review-revision request (#677), durably, before anything is
2005
- * woken. Returns `undefined` when a revision for the same run is already
2006
- * pending undelivered — the atomic half of the duplicate in-flight guard
2007
- * (the other half is the run row's state once the revision is dispatched). */
2008
- createReviewRevision(draft: Omit<ReviewRevisionRecord, "id">): ReviewRevisionRecord | undefined;
2245
+ * woken. A same-run, same-head finding folds into the revision already
2246
+ * pending undelivered (`appended` one round, one row, one worker
2247
+ * attempt, and the resumed session gets both findings); otherwise it opens
2248
+ * a new round (`created`). A revision already pending at a different head
2249
+ * cannot be amended (`refused`) — that round must dispatch and settle
2250
+ * first. The duplicate in-flight guard is the same single transaction, so
2251
+ * two concurrent same-head findings cannot both create a row or overwrite
2252
+ * each other (the run row's state is the other half once the revision is
2253
+ * dispatched). */
2254
+ enqueueReviewRevision(draft: Omit<ReviewRevisionRecord, "id">): ReviewRevisionEnqueue;
2255
+ /** The pending revision for one run, if any — the header the verb reads to
2256
+ * tell "this call folds into the in-flight round" from "this call opens a
2257
+ * new round" before the round-ceiling gate applies. */
2258
+ pendingReviewForRun(project: string, runId: string): ReviewRevisionRecord | undefined;
2009
2259
  /** Review revisions not yet handed to a worker, oldest first — what the
2010
2260
  * daemon's dispatch pass wakes on its next tick. */
2011
2261
  pendingReviewRevisions(project: string): ReviewRevisionRecord[];
@@ -2155,6 +2405,28 @@ export interface Store {
2155
2405
  /** Resolve one idea to `groomed` (recording the issue URL #300 chose) or
2156
2406
  * `dismissed`. `false` when the id is unknown. */
2157
2407
  resolveIntake(id: string, state: "groomed" | "dismissed", issueUrl?: string): boolean;
2408
+ /** Record the current grooming verdict for one issue, replacing any prior
2409
+ * row — one row per project + issue, never a history (#735). */
2410
+ upsertGrooming(draft: GroomingDraft): void;
2411
+ /** The current grooming verdict for one issue, or undefined when none. */
2412
+ grooming(project: string, issue: number): GroomingRecord | undefined;
2413
+ /** Current grooming verdicts for a project, issue-ascending; `verdict`
2414
+ * narrows to one verdict — the tick's known-blocked read is
2415
+ * `groomingVerdicts(project, "blocked")`. */
2416
+ groomingVerdicts(project: string, verdict?: GroomingVerdict): GroomingRecord[];
2417
+ /**
2418
+ * Reconcile admission's lane/dependency holds against the persisted blocked
2419
+ * verdicts: each currently held issue is recorded as `blocked`, and in the
2420
+ * same transaction every `blocked` row whose lane/dependency hold this pass
2421
+ * does not name is deleted — the hold no longer applies, so the state
2422
+ * self-heals instead of lingering as a cache someone must invalidate. Runs
2423
+ * at the end of every admission pass. Never touches `promotable` or
2424
+ * `considered` rows, which belong to #679's scout loop.
2425
+ */
2426
+ reconcileGrooming(
2427
+ project: string,
2428
+ holds: readonly { issue: number; reason: AdmissionHoldReason; detail?: string }[],
2429
+ ): void;
2158
2430
  /** Count and age source for status and digest prompt bounds. */
2159
2431
  digestBacklog(project: string): DigestBacklog;
2160
2432
  /** Add one bounded observation to the per-day friction rollup. */
@@ -2340,8 +2612,21 @@ export interface Store {
2340
2612
  /** Answer or withdraw one. `false` when the id is unknown or already closed,
2341
2613
  * so a double-resolve cannot overwrite the first answer. */
2342
2614
  resolveDecision(id: string, state: "answered" | "withdrawn", resolution: string, at: number): boolean;
2343
- /** First observation that a row's condition came true. Idempotent. */
2344
- markDecisionConditionMet(id: string, at: number): boolean;
2615
+ /**
2616
+ * First observation that a row's condition came true. Idempotent per head:
2617
+ * a second call changes nothing, but a `pr-checks-green` / `pr-review-ready`
2618
+ * row whose PR head moved is cleared first and then re-marks against the new
2619
+ * head.
2620
+ * `head` binds the verdict to the PR head it was observed at (#808).
2621
+ */
2622
+ markDecisionConditionMet(id: string, at: number, head?: string): boolean;
2623
+ /**
2624
+ * Drop a condition's met state — timestamp and any head binding — when the
2625
+ * observation it was set from stopped holding. The head-bound conditions
2626
+ * call this when the PR head changed, so the row returns to pending instead
2627
+ * of rendering `[CONDITION MET]` for checks that are still running (#808).
2628
+ */
2629
+ clearDecisionConditionMet(id: string): boolean;
2345
2630
  /** Close every open row past its deadline and return them, so the caller can
2346
2631
  * say what it just closed rather than reporting a count. */
2347
2632
  expireDueDecisions(project: string, now: number): DecisionRecord[];
@@ -2588,6 +2873,16 @@ export const VERB_NAMES = [
2588
2873
  * OMP session on the existing branch and PR. No close, no reopen, no
2589
2874
  * redispatch. */
2590
2875
  "conductor_pr_review",
2876
+ /** The orchestrator-only, settled-run recovery operation (#806): open (or
2877
+ * adopt) the missing pull request for a terminal run whose stored branch
2878
+ * exists at its exact recorded head. The recovery proves everything a
2879
+ * worker's own `conductor_pr_create` took for granted from a live channel —
2880
+ * no worker is live, the routed repo matches, the remote branch is at the
2881
+ * exact recorded head, the issue is still open, and no PR already
2882
+ * represents that head — so a stranded `pushed-pending` / lost-PR run can
2883
+ * re-enter normal review/merge without a fresh clone. Idempotent: an
2884
+ * already-matching PR is returned, not duplicated. */
2885
+ "conductor_pr_recover",
2591
2886
  /** The one read verb. It answers with the merge gate's own verdict, so
2592
2887
  * "pushed-green" is the dispatcher's reading of the PR rather than a claim the
2593
2888
  * worker makes about itself from whatever it happened to run. */
@@ -2653,6 +2948,32 @@ export const VERB_REFUSALS = [
2653
2948
  "pr-not-this-run",
2654
2949
  /** An unrecorded recovery PR was authorized, but not for these exact inputs. */
2655
2950
  "recovery-authorization-mismatch",
2951
+ /** The recovery target resolved to a run that does not exist in the store. */
2952
+ "recovery-no-run",
2953
+ /** The run the recovery was pointed at still has a live worker on it. */
2954
+ "recovery-run-live",
2955
+ /** The run is terminal but not in a state the settlement sweep owns, so a
2956
+ * recovered PR would never re-enter verification/review/merge. */
2957
+ "recovery-unsettled-state",
2958
+ /** The tracker could not say whether the issue is open. Recovery fails
2959
+ * closed rather than opening a PR beside (or for) a possibly-closed
2960
+ * issue. */
2961
+ "issue-state-unreadable",
2962
+ /** The recovered work has already landed or been declined: the issue is
2963
+ * closed, the run settled merged, or its recorded PR merged. Nothing to
2964
+ * recover, and recovery must not reopen it. */
2965
+ "recovery-issue-resolved",
2966
+ /** The run records no exact 40-hex head to compare its stored branch
2967
+ * against, so there is nothing durable to verify a recovered PR would
2968
+ * publish. */
2969
+ "recovery-unrecorded-head",
2970
+ /** The branch exists but not at the run's recorded head (replaced,
2971
+ * force-pushed or superseded by a live PR), so a created PR would publish
2972
+ * work the daemon never verified. */
2973
+ "recovery-head-mismatch",
2974
+ /** The routed repository a terminal run recorded no longer has an entry in
2975
+ * this project's routing, so the daemon cannot create a PR for it. */
2976
+ "recovery-repo-unrouted",
2656
2977
  /** The run has no pull request to act on. */
2657
2978
  "pr-missing",
2658
2979
  /**
@@ -2695,6 +3016,15 @@ export const VERB_REFUSALS = [
2695
3016
  "label-not-in-vocabulary",
2696
3017
  /** The label is a lifecycle label; those transitions stay the daemon's (#26). */
2697
3018
  "label-is-lifecycle",
3019
+ /** The label is the operator's park gesture; only the operator may set or
3020
+ * clear it, orchestrator and worker alike (#507). */
3021
+ "label-is-operator-owned",
3022
+ /** Adding the queue label was refused because the issue carries a clearly
3023
+ * delimited write-lane section heading that parsed no path-like files
3024
+ * (#825): promoting it would fail open beside overlapping work, so the
3025
+ * verb refuses with an actionable syntax error instead of echoing a
3026
+ * fail-open the heading contradicts. */
3027
+ "file-lane-unparseable",
2698
3028
  /** The release grant does not permit this shape for this caller. */
2699
3029
  "release-not-granted",
2700
3030
  /** The artefact or environment is not one this project declared (#129). */