omp-conductor 0.18.0 → 0.18.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 +35 -1
- package/REFERENCE.md +61 -11
- package/agents/to-spec.md +94 -0
- package/package.json +2 -1
- package/schema/config.schema.json +35 -1
- package/src/admission.ts +204 -75
- package/src/arm-challenge.ts +250 -57
- package/src/ask.ts +268 -7
- package/src/board.ts +17 -3
- package/src/briefs/orchestrator.md +62 -21
- package/src/briefs/to-spec.md +88 -0
- package/src/briefs/worker.md +2 -1
- package/src/cli.ts +124 -1
- package/src/command-help.ts +11 -0
- package/src/command-manifest.ts +38 -5
- package/src/commands/arm.ts +1 -1
- package/src/commands/context.ts +1 -0
- package/src/commands/drain.ts +176 -0
- package/src/commands/extend.ts +6 -10
- package/src/commands/intake.ts +4 -19
- package/src/commands/status.ts +5 -1
- package/src/commands/watch.ts +51 -16
- package/src/commands/worker.ts +9 -10
- package/src/config-schema.ts +43 -6
- package/src/config.ts +65 -9
- package/src/daemon.ts +879 -41
- package/src/dashboard/app.js +4 -1
- package/src/dashboard/server.ts +5 -2
- package/src/decisions.ts +243 -17
- package/src/diff-flags.ts +75 -1
- package/src/doctor.ts +60 -82
- package/src/escalate.ts +31 -14
- package/src/failure-class.ts +28 -2
- package/src/fleet.ts +239 -240
- package/src/gitops.ts +188 -81
- package/src/graph-health.ts +35 -1
- package/src/graph.ts +66 -1
- package/src/harness-loader.ts +59 -0
- package/src/host.ts +242 -2
- package/src/lifecycle.ts +122 -1
- package/src/omp-settings.ts +19 -0
- package/src/omp.ts +183 -21
- package/src/orchestrator-tick.ts +1591 -32
- package/src/orchestrator.ts +12 -0
- package/src/privileged.ts +1 -4
- package/src/release-policy.ts +503 -9
- package/src/session-host.ts +65 -6
- package/src/settlement.ts +69 -17
- package/src/setup-host.ts +1225 -9
- package/src/setup-install.ts +28 -0
- package/src/setup-wizard.ts +154 -3
- package/src/setup.ts +83 -17
- package/src/shell.ts +15 -0
- package/src/status-render.ts +216 -12
- package/src/store.ts +443 -42
- package/src/to-spec.ts +408 -0
- package/src/tracker/github.ts +104 -14
- package/src/types.ts +405 -19
- package/src/upgrade-verify.ts +209 -2
- package/src/upgrade.ts +175 -1
- package/src/verbs/protocol.ts +39 -0
- package/src/verbs/server.ts +765 -56
- package/src/verbs/socket.ts +24 -5
- package/src/worker.ts +12 -2
- package/src/worktree.ts +29 -12
package/src/types.ts
CHANGED
|
@@ -638,26 +638,76 @@ export const REVIEW_MAX_ROUNDS_MAX = 6;
|
|
|
638
638
|
* for real correction cycles, and a hard stop against endless polishing. */
|
|
639
639
|
export const DEFAULT_REVIEW_MAX_ROUNDS = 3;
|
|
640
640
|
|
|
641
|
+
/**
|
|
642
|
+
* The one spelling a review policy may ask the adjudicator for: a token naming
|
|
643
|
+
* an OMP model role (`"task"`, `"worker"`, a project's own `modelRoles` key).
|
|
644
|
+
* A role name is never a provider/model — OMP owns provider selection and
|
|
645
|
+
* named model roles, and conductor stores only the role. Used by the config
|
|
646
|
+
* grammar and by the setup dialog's availability check; they must read the
|
|
647
|
+
* same shape or one of them accepts an answer the other rejects.
|
|
648
|
+
*/
|
|
649
|
+
export const REVIEW_ADJUDICATOR_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Which OMP model role carries a PR's terminal review-ceiling adjudication
|
|
653
|
+
* (#875) when no project policy names one. The deterministic migration default
|
|
654
|
+
* every existing project loads until its operator answers setup: the general
|
|
655
|
+
* OMP session role each install can launch without a provider or model being
|
|
656
|
+
* pinned here.
|
|
657
|
+
*/
|
|
658
|
+
export const DEFAULT_REVIEW_ADJUDICATOR_ROLE = "task";
|
|
659
|
+
|
|
641
660
|
/**
|
|
642
661
|
* One project's review policy (#678): the strictness its orchestrator applies
|
|
643
|
-
* when deciding whether a green PR is returned to its worker,
|
|
644
|
-
* ceiling on how many such rounds one PR lifecycle may consume
|
|
645
|
-
*
|
|
646
|
-
*
|
|
647
|
-
*
|
|
662
|
+
* when deciding whether a green PR is returned to its worker, the hard
|
|
663
|
+
* ceiling on how many such rounds one PR lifecycle may consume, and which OMP
|
|
664
|
+
* model role carries the final adjudication once that ceiling is reached
|
|
665
|
+
* (#875). The ceiling is enforced by the dispatch side, never left to the
|
|
666
|
+
* session's reading of the brief: at the ceiling `conductor_pr_review`
|
|
667
|
+
* refuses, the orchestrator leaves the PR open, records the unresolved
|
|
668
|
+
* findings and escalates once.
|
|
648
669
|
*/
|
|
649
670
|
export interface ReviewPolicy {
|
|
650
671
|
strictness: ReviewStrictness;
|
|
651
672
|
maxRounds: number;
|
|
673
|
+
/**
|
|
674
|
+
* The OMP model role that runs a PR's terminal review-ceiling adjudication:
|
|
675
|
+
* a named role from OMP's configured model roles (`"task"`, `"worker"`, or a
|
|
676
|
+
* project's own `modelRoles` key), in omp's model-role syntax — never a
|
|
677
|
+
* provider/model, which OMP owns. Absent in config loads as
|
|
678
|
+
* {@link DEFAULT_REVIEW_ADJUDICATOR_ROLE}, the deterministic default every
|
|
679
|
+
* existing install upgrades to.
|
|
680
|
+
*/
|
|
681
|
+
adjudicator: string;
|
|
652
682
|
}
|
|
653
683
|
|
|
654
|
-
/** The documented migration default: what a config written before the
|
|
684
|
+
/** The documented migration default: what a config written before the fields
|
|
655
685
|
* existed loads as, deterministically, until the operator answers setup. */
|
|
656
686
|
export const DEFAULT_REVIEW_POLICY: ReviewPolicy = {
|
|
657
687
|
strictness: DEFAULT_REVIEW_STRICTNESS,
|
|
658
688
|
maxRounds: DEFAULT_REVIEW_MAX_ROUNDS,
|
|
689
|
+
adjudicator: DEFAULT_REVIEW_ADJUDICATOR_ROLE,
|
|
659
690
|
};
|
|
660
691
|
|
|
692
|
+
/**
|
|
693
|
+
* The resolved launch provenance of one review-ceiling adjudication (#875):
|
|
694
|
+
* what the policy named and what the role actually resolved to when the
|
|
695
|
+
* adjudicator session launched. OMP owns named roles and exact provider
|
|
696
|
+
* selection, so this record is where a launch's real model/provider lands —
|
|
697
|
+
* the typed fields later status/ledger code reads to show what genuinely
|
|
698
|
+
* adjudicated, not what a config said it should.
|
|
699
|
+
*/
|
|
700
|
+
export interface ReviewAdjudicationProvenance {
|
|
701
|
+
/** The policy's adjudicator role, verbatim from {@link ReviewPolicy.adjudicator}. */
|
|
702
|
+
readonly role: string;
|
|
703
|
+
/** The model pattern the role resolved to at launch, in omp's model syntax. */
|
|
704
|
+
readonly model: string;
|
|
705
|
+
/** The provider OMP selected, when the harness could name one at launch. */
|
|
706
|
+
readonly provider?: string;
|
|
707
|
+
/** Epoch ms when this launch resolution was recorded. */
|
|
708
|
+
readonly resolvedAt: number;
|
|
709
|
+
}
|
|
710
|
+
|
|
661
711
|
/**
|
|
662
712
|
* One durable review-revision request (#677): the orchestrator returned a
|
|
663
713
|
* green, run-owned pull request to its worker with blocking findings, and
|
|
@@ -690,6 +740,21 @@ export interface ReviewRevisionRecord {
|
|
|
690
740
|
outcome?: ReviewRevisionOutcome;
|
|
691
741
|
}
|
|
692
742
|
|
|
743
|
+
/**
|
|
744
|
+
* The outcome of recording one `conductor_pr_review` finding against a run's
|
|
745
|
+
* review-revision outbox (#786). `created` opened a new revision round for the
|
|
746
|
+
* run; `appended` folded the finding into the revision already pending
|
|
747
|
+
* undelivered on the same reviewed head — one round, one row, one worker
|
|
748
|
+
* attempt either way, and the resumed session reads both findings. `refused`
|
|
749
|
+
* names the one in-flight shape that cannot be amended: a pending revision at
|
|
750
|
+
* a different head, which is a review of a moved PR and must dispatch and
|
|
751
|
+
* settle before another finding for the same run is recorded.
|
|
752
|
+
*/
|
|
753
|
+
export type ReviewRevisionEnqueue =
|
|
754
|
+
| { kind: "created"; record: ReviewRevisionRecord }
|
|
755
|
+
| { kind: "appended"; record: ReviewRevisionRecord }
|
|
756
|
+
| { kind: "refused"; block: "pending-different-head" };
|
|
757
|
+
|
|
693
758
|
/**
|
|
694
759
|
* A model-supplied justification: one value out of a closed set, plus prose that
|
|
695
760
|
* is written down and read by nothing that decides.
|
|
@@ -926,6 +991,38 @@ export const CONFIG_VERSION = 2;
|
|
|
926
991
|
*/
|
|
927
992
|
export const READABLE_CONFIG_VERSIONS = [1, CONFIG_VERSION] as const;
|
|
928
993
|
|
|
994
|
+
/**
|
|
995
|
+
* Operator-authored host facts rendered into every worker brief (#721) — the
|
|
996
|
+
* typed replacement for host prose hand-written into an untracked agent
|
|
997
|
+
* context file. Only what cannot be derived lives here: cores and RAM are
|
|
998
|
+
* read from the host by `host.ts` at render time, and the guarded shell
|
|
999
|
+
* suites stay `SHARED_HOST_SCRIPTS`'s single source — nothing in this object
|
|
1000
|
+
* may re-name a refused command.
|
|
1001
|
+
*
|
|
1002
|
+
* Every key is optional; a fleet that never fills the object loads a config
|
|
1003
|
+
* with no `host` field at all and dispatches briefs byte-for-byte as before.
|
|
1004
|
+
*/
|
|
1005
|
+
export interface HostConstraints {
|
|
1006
|
+
/**
|
|
1007
|
+
* What this host is and what else it runs, in the operator's words ("a
|
|
1008
|
+
* shared 4-core VPS that also runs Langfuse and the fleet"). The renderer
|
|
1009
|
+
* folds the derived core/RAM count into this line.
|
|
1010
|
+
*/
|
|
1011
|
+
description?: string;
|
|
1012
|
+
/**
|
|
1013
|
+
* The non-interactive PATH a script or `ssh host '<cmd>'` invocation must
|
|
1014
|
+
* export (`/root/.bun/bin:/root/.local/bin:$PATH` on this fleet) — commands
|
|
1015
|
+
* run from an ssh session do not see the interactive shell's PATH.
|
|
1016
|
+
*/
|
|
1017
|
+
path?: string;
|
|
1018
|
+
/**
|
|
1019
|
+
* Per-repo command conventions, keyed by the repo slug the brief renders
|
|
1020
|
+
* as its `{{REPO}}` (`owner/repo`). A repo without an entry renders nothing
|
|
1021
|
+
* for it.
|
|
1022
|
+
*/
|
|
1023
|
+
conventions?: Record<string, string>;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
929
1026
|
/**
|
|
930
1027
|
* On-disk root config. `version` is present from day one so a format change
|
|
931
1028
|
* can be migrated instead of silently misread by an older daemon.
|
|
@@ -938,6 +1035,9 @@ export interface ConductorConfig {
|
|
|
938
1035
|
* `<stateDir()>/backups/db` when omitted. A snapshot that cannot land there
|
|
939
1036
|
* is `doctor`'s `db-backup` failure. */
|
|
940
1037
|
dbBackupDir?: string;
|
|
1038
|
+
/** Host facts every worker brief renders (#721). Absent or empty renders
|
|
1039
|
+
* nothing — no section — and the brief stays exactly what it always was. */
|
|
1040
|
+
host?: HostConstraints;
|
|
941
1041
|
}
|
|
942
1042
|
|
|
943
1043
|
/**
|
|
@@ -1025,6 +1125,11 @@ export const SETTLEMENT_FLAG_KINDS = [
|
|
|
1025
1125
|
"assertions-removed",
|
|
1026
1126
|
/** A named timeout in a test file went up. */
|
|
1027
1127
|
"test-timeout-raised",
|
|
1128
|
+
/** The PR diff touches files outside the effective declared file lane —
|
|
1129
|
+
* the declaration admission enforced and the brief rendered, so a widened
|
|
1130
|
+
* lane is named on evidence rather than found by reading the file list by
|
|
1131
|
+
* hand, and the queued work it blocks is explainable (#739). */
|
|
1132
|
+
"lane-escape",
|
|
1028
1133
|
/** A command the PR body claims as proof, with no matching command in the
|
|
1029
1134
|
* run's transcript — the claim has no attempt behind it at all. */
|
|
1030
1135
|
"claimed-proof-missing",
|
|
@@ -1046,8 +1151,8 @@ export type SettlementFlagKind = (typeof SETTLEMENT_FLAG_KINDS)[number];
|
|
|
1046
1151
|
*/
|
|
1047
1152
|
export interface SettlementFlag {
|
|
1048
1153
|
kind: SettlementFlagKind;
|
|
1049
|
-
/** Repo-relative path, `(report)` for report evidence,
|
|
1050
|
-
* a post-merge workflow result. */
|
|
1154
|
+
/** Repo-relative path, `(report)` for report evidence, `(lane)` for a
|
|
1155
|
+
* lane-escape finding, or `(base branch)` for a post-merge workflow result. */
|
|
1051
1156
|
file: string;
|
|
1052
1157
|
/** 1-based line in the side of the diff the evidence came from: the
|
|
1053
1158
|
* post-image for an added line, the pre-image for a removed one. */
|
|
@@ -1193,8 +1298,19 @@ export interface Tracker {
|
|
|
1193
1298
|
issueBody(issue: number): Promise<string | undefined>;
|
|
1194
1299
|
/**
|
|
1195
1300
|
* The state of one specific pull request, or undefined when this adapter
|
|
1196
|
-
* could not tell — a network failure, a
|
|
1197
|
-
* Undefined never means
|
|
1301
|
+
* could not tell — a network failure, a revoked token, a URL it cannot
|
|
1302
|
+
* parse, or a 404 the adapter could not corroborate. Undefined never means
|
|
1303
|
+
* "no". The one exception is a definitively missing PR: a REST 404 on the
|
|
1304
|
+
* individual read that a same-repository pulls-list read corroborates (a
|
|
1305
|
+
* deleted or never-created number in a repository the credential can still
|
|
1306
|
+
* read) is reported by throwing a classified `GhPrMissingError`. A
|
|
1307
|
+
* repository hidden from the credential answers the same 404 bytes, so a
|
|
1308
|
+
* bare 404 — token scope/SSO loss included — is never "missing": it stays
|
|
1309
|
+
* undefined and the row stays retryable. Every other tracker implementation
|
|
1310
|
+
* keeps the "never throws, undefined is could-not-tell" contract, and every
|
|
1311
|
+
* caller not acting on a missing PR keeps its existing fail-closed handling
|
|
1312
|
+
* by catching the throw exactly as it already catches any other tracker
|
|
1313
|
+
* failure.
|
|
1198
1314
|
*
|
|
1199
1315
|
* Deliberately separate from {@link Tracker.openCloserFor}, which asks
|
|
1200
1316
|
* whether any OPEN closing PR exists and answers undefined for merged,
|
|
@@ -1203,6 +1319,11 @@ export interface Tracker {
|
|
|
1203
1319
|
* is how a PR a human rejected gets recorded as merged.
|
|
1204
1320
|
*/
|
|
1205
1321
|
prState(url: string): Promise<PrState | undefined>;
|
|
1322
|
+
/** The live head commit SHA of one pull request, or undefined when the
|
|
1323
|
+
* tracker could not tell. `pr-checks-green` binds its green verdict to the
|
|
1324
|
+
* head it was read at, so a head read that cannot answer is "not yet
|
|
1325
|
+
* satisfied" for that pass, never a green verdict (#808). */
|
|
1326
|
+
prHead(url: string): Promise<string | undefined>;
|
|
1206
1327
|
/** The merge commit and base ref for a merged pull request. */
|
|
1207
1328
|
mergedPrInfo(url: string): Promise<MergedPrInfo | undefined>;
|
|
1208
1329
|
/** Workflow runs GitHub associated with one exact commit SHA. */
|
|
@@ -1292,9 +1413,9 @@ export interface Tracker {
|
|
|
1292
1413
|
/** Issues carrying `label`, open or closed, bounded. Empty on any failure —
|
|
1293
1414
|
* a reconcile that cannot list must remove no labels. */
|
|
1294
1415
|
listLabeled(label: string, limit?: number): Promise<{ number: number; state: IssueState }[]>;
|
|
1295
|
-
/** Sub-issues of `issue`, with their states.
|
|
1296
|
-
*
|
|
1297
|
-
*
|
|
1416
|
+
/** Sub-issues of `issue`, with their states. Propagates lookup failures so
|
|
1417
|
+
* callers can choose the safe polarity: settlement catches and treats an
|
|
1418
|
+
* unread probe as no positive evidence, while launch selection fails closed. */
|
|
1298
1419
|
childrenOf(issue: number): Promise<{ number: number; state: IssueState }[]>;
|
|
1299
1420
|
}
|
|
1300
1421
|
|
|
@@ -1432,6 +1553,72 @@ export type RunState =
|
|
|
1432
1553
|
/** In flight when its daemon process died; reconciled at the next startup. */
|
|
1433
1554
|
| "orphaned";
|
|
1434
1555
|
|
|
1556
|
+
/** One parsed `File lane:` declaration: the paths and the verbatim source
|
|
1557
|
+
* line, so a renderer can reproduce the declaration itself rather than a
|
|
1558
|
+
* summary of it. Exported for the brief's guarantee that the gate's effective
|
|
1559
|
+
* lane is always visible to the worker (#608). */
|
|
1560
|
+
export interface LaneDeclaration {
|
|
1561
|
+
files: string[];
|
|
1562
|
+
/** The declaration line verbatim, as written on the surface it came from. */
|
|
1563
|
+
source: string;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
/**
|
|
1567
|
+
* The effective file lane as both admission and the worker brief must read it
|
|
1568
|
+
* (#608): the latest `File lane:` declaration among the issue body and every
|
|
1569
|
+
* comment, in the tracker's oldest-first order. This is the "later correction
|
|
1570
|
+
* visibly supersedes" contract applied across both surfaces at once, and it is
|
|
1571
|
+
* the single source of truth the gate enforces and the brief renders — so a
|
|
1572
|
+
* declaration can never control admission while staying invisible to the
|
|
1573
|
+
* worker. `at` records which surface won (`"body"`, or the 0-based comment
|
|
1574
|
+
* index), letting the brief reproduce the declaration verbatim when the
|
|
1575
|
+
* winning comment sits beyond its rendered discussion budget.
|
|
1576
|
+
*/
|
|
1577
|
+
export interface FileLane extends LaneDeclaration {
|
|
1578
|
+
at: "body" | number;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
/** One parsed `Model:` declaration (#535): the selector and the verbatim
|
|
1582
|
+
* source line, so a renderer can reproduce the declaration itself rather
|
|
1583
|
+
* than a summary of it. */
|
|
1584
|
+
export interface ModelDeclaration {
|
|
1585
|
+
/** The selector in omp's own model syntax — a role alias (`@slow`) or a
|
|
1586
|
+
* concrete `provider/model`, either optionally `:thinking`-suffixed —
|
|
1587
|
+
* passed straight through as the session's `modelPattern`, for omp's
|
|
1588
|
+
* resolver to interpret. */
|
|
1589
|
+
model: string;
|
|
1590
|
+
/** The declaration line verbatim, as written on the surface it came from. */
|
|
1591
|
+
source: string;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
/**
|
|
1595
|
+
* The effective model as both dispatch and the worker brief must read it
|
|
1596
|
+
* (#535): the latest `Model:` declaration among the issue body and every
|
|
1597
|
+
* comment, in the tracker's oldest-first order — the same "later correction
|
|
1598
|
+
* visibly supersedes" contract applied across both surfaces at once as
|
|
1599
|
+
* {@link FileLane}, so a pre-dispatch comment can change the model a
|
|
1600
|
+
* promotion brief named in the body. `at` records which surface won
|
|
1601
|
+
* (`"body"`, or the 0-based comment index).
|
|
1602
|
+
*/
|
|
1603
|
+
export interface EffectiveModel extends ModelDeclaration {
|
|
1604
|
+
at: "body" | number;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
/**
|
|
1608
|
+
* One dispatched run's observation of the code-graph MCP tools (#726): a
|
|
1609
|
+
* runtime fact read off the session's own registry at session start, never a
|
|
1610
|
+
* config guess. `present: true` and `present: false` are both observations —
|
|
1611
|
+
* an absent field (no `graphTools` on the row at all) means no observation was
|
|
1612
|
+
* recorded, which is deliberately distinct from "tools absent": the whole
|
|
1613
|
+
* point of the boolean is that "the model ignored a tool it had" and "the tool
|
|
1614
|
+
* was missing" no longer look identical from outside the session.
|
|
1615
|
+
*/
|
|
1616
|
+
export interface GraphToolsObservation {
|
|
1617
|
+
present: boolean;
|
|
1618
|
+
/** Session-start wall clock (ms epoch), the moment the registry was read. */
|
|
1619
|
+
at: number;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1435
1622
|
/**
|
|
1436
1623
|
* One attempt at one issue. Persisted so a daemon restart can reconcile
|
|
1437
1624
|
* orphaned worktrees and branches instead of leaking them.
|
|
@@ -1447,6 +1634,17 @@ export interface RunRecord {
|
|
|
1447
1634
|
state: RunState;
|
|
1448
1635
|
/** 1-based attempt number, checked against `Caps.maxAttemptsPerIssue`. */
|
|
1449
1636
|
attempt: number;
|
|
1637
|
+
/**
|
|
1638
|
+
* The file-lane declaration admission resolved for this run, persisted at
|
|
1639
|
+
* dispatch from the carried `Admission.lane` (#744). It is the exact
|
|
1640
|
+
* snapshot the overlap gate enforced — the same value the brief renders —
|
|
1641
|
+
* and it is durable on purpose: the next dispatch pass builds lane occupancy
|
|
1642
|
+
* from it, so a run admitted with a declared lane it has not yet written
|
|
1643
|
+
* still holds those files across passes, until the row reaches a terminal
|
|
1644
|
+
* state and leaves the active set. Absent means the run predates the column
|
|
1645
|
+
* or was admitted with no declaration (fail open) — never "empty lane".
|
|
1646
|
+
*/
|
|
1647
|
+
lane?: FileLane;
|
|
1450
1648
|
turns: number;
|
|
1451
1649
|
/** Effective turn ceiling for this run; operators may only raise it. */
|
|
1452
1650
|
maxTurns: number;
|
|
@@ -1519,6 +1717,18 @@ export interface RunRecord {
|
|
|
1519
1717
|
* (#286).
|
|
1520
1718
|
*/
|
|
1521
1719
|
model?: string;
|
|
1720
|
+
/**
|
|
1721
|
+
* The code-graph session observation (#726): whether the graph MCP tools were
|
|
1722
|
+
* in this run's own session registry when the session started. Read off the
|
|
1723
|
+
* session itself, once, at dispatch — never derived from `mcp.json`, which
|
|
1724
|
+
* only says what a session *should* mount. It is the difference between "the
|
|
1725
|
+
* model ignored a tool it had" and "the tool was missing", which look
|
|
1726
|
+
* identical from outside a session. Absent means the run predates the column
|
|
1727
|
+
* or no worker session recorded an observation — never "graph tools were
|
|
1728
|
+
* absent": `present: false` is the only absence claim, and it is a runtime
|
|
1729
|
+
* fact, not an inference.
|
|
1730
|
+
*/
|
|
1731
|
+
graphTools?: GraphToolsObservation;
|
|
1522
1732
|
/** When an operator accepted the loss or recovered the tree by hand
|
|
1523
1733
|
* (`unblock --force`). Clears the hold without erasing what happened. */
|
|
1524
1734
|
salvageAckAt?: number;
|
|
@@ -1530,6 +1740,18 @@ export interface RunRecord {
|
|
|
1530
1740
|
* Absent means never quarantined, or no longer — never "quarantine not
|
|
1531
1741
|
* checked", because every retained-tree pass checks. */
|
|
1532
1742
|
quarantineDetail?: string;
|
|
1743
|
+
/** Failed-attempt budget charges this row has accumulated through review
|
|
1744
|
+
* claims (#795 review round 2). Every chargeable terminal event that was
|
|
1745
|
+
* claimed into a review round adds one here, so `failuresFor` keeps
|
|
1746
|
+
* counting each event even after the round leaves the row `pushed-green`;
|
|
1747
|
+
* repeated failed rounds therefore keep consuming the failed-attempt
|
|
1748
|
+
* budget. Absent means zero charges were ever preserved on this row. */
|
|
1749
|
+
failureCharges?: number;
|
|
1750
|
+
/** Continuation budget charges this row has accumulated through review
|
|
1751
|
+
* claims, same semantics as {@link RunRecord.failureCharges}: each
|
|
1752
|
+
* chargeable cap or returned-for-revision event claimed into a round adds
|
|
1753
|
+
* one, and `continuationsFor` sums the column. */
|
|
1754
|
+
continuationCharges?: number;
|
|
1533
1755
|
startedAt: number;
|
|
1534
1756
|
endedAt?: number;
|
|
1535
1757
|
/** Last failure text, surfaced verbatim in escalations. */
|
|
@@ -1640,6 +1862,14 @@ export interface DispatchSummary {
|
|
|
1640
1862
|
* sweep and by classification recovery. Optional: persisted old rows lack
|
|
1641
1863
|
* it, so readers use `?? 0`. (#497) */
|
|
1642
1864
|
settled?: number;
|
|
1865
|
+
/**
|
|
1866
|
+
* Queue-labelled candidates the operator's park label disqualified — the
|
|
1867
|
+
* same eligibility read the claim gate uses, so the status number cannot
|
|
1868
|
+
* disagree with what admission would hold (#507). Distinguishes "0 claimable,
|
|
1869
|
+
* 12 parked" from "0 claimable, nothing to do". Optional: persisted old rows
|
|
1870
|
+
* lack it, and a pass with nothing parked omits it, so readers use `?? 0`.
|
|
1871
|
+
*/
|
|
1872
|
+
parked?: number;
|
|
1643
1873
|
/** True for a held pass's own record (#497): the queue was never routed, so
|
|
1644
1874
|
* ready/routed/claimed are absent queue facts, not an empty queue. */
|
|
1645
1875
|
paused?: boolean;
|
|
@@ -1879,6 +2109,49 @@ export interface IntakeDraft {
|
|
|
1879
2109
|
at: number;
|
|
1880
2110
|
}
|
|
1881
2111
|
|
|
2112
|
+
/**
|
|
2113
|
+
* The verdict surface of per-issue grooming state (#735). Admission writes
|
|
2114
|
+
* `blocked` when it holds a candidate for a file-lane or dependency reason —
|
|
2115
|
+
* the durable half of the misleading "groom the backlog" fix, because the tick
|
|
2116
|
+
* reads these instead of re-deriving why the runway cannot move. `promotable`
|
|
2117
|
+
* and `considered` are the outcomes #679's scout loop writes into the same
|
|
2118
|
+
* table, so grooming knowledge lives in one store rather than a second memory.
|
|
2119
|
+
*/
|
|
2120
|
+
export const GROOMING_VERDICTS = ["promotable", "blocked", "considered"] as const;
|
|
2121
|
+
|
|
2122
|
+
export type GroomingVerdict = (typeof GROOMING_VERDICTS)[number];
|
|
2123
|
+
|
|
2124
|
+
/**
|
|
2125
|
+
* One issue's current grooming verdict, durable across restarts and keyed by
|
|
2126
|
+
* project + issue — one row per issue, replaced in place by upsert, never a
|
|
2127
|
+
* history. Deliberately NOT a decision row: a verdict is neither owed to the
|
|
2128
|
+
* operator nor TTL-exempt, so it sits outside `decisions` (7-day expiry,
|
|
2129
|
+
* operator-facing) and `material_events` (append-only digest outbox) alike.
|
|
2130
|
+
*/
|
|
2131
|
+
export interface GroomingRecord {
|
|
2132
|
+
project: string;
|
|
2133
|
+
issue: number;
|
|
2134
|
+
verdict: GroomingVerdict;
|
|
2135
|
+
/** Why this verdict — admission's hold reason for `blocked` (`file-lane`,
|
|
2136
|
+
* `depends-on`), or the groomer's own label for #679's verdicts. */
|
|
2137
|
+
reason: string;
|
|
2138
|
+
/** What proved it: the hold detail naming the overlapping file/holder run,
|
|
2139
|
+
* or a scout summary. Free text, bounded at the write site. */
|
|
2140
|
+
evidence: string;
|
|
2141
|
+
recordedAt: number;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
/** What a caller hands over. The store owns the timestamp and the replace-in-
|
|
2145
|
+
* place semantics, nothing else. */
|
|
2146
|
+
export interface GroomingDraft {
|
|
2147
|
+
project: string;
|
|
2148
|
+
issue: number;
|
|
2149
|
+
verdict: GroomingVerdict;
|
|
2150
|
+
reason: string;
|
|
2151
|
+
evidence: string;
|
|
2152
|
+
at: number;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
1882
2155
|
/**
|
|
1883
2156
|
* Where one operator decision stands (#136).
|
|
1884
2157
|
*
|
|
@@ -1930,6 +2203,12 @@ export interface DecisionRecord {
|
|
|
1930
2203
|
/** When that precondition was first observed true. Set once; the digest
|
|
1931
2204
|
* promotes the row from "parked" to "act on this now". */
|
|
1932
2205
|
conditionMetAt?: number;
|
|
2206
|
+
/** For `pr-checks-green` / `pr-review-ready`: the exact PR head whose checks
|
|
2207
|
+
* satisfied the condition, read in the same pass as the green verdict. A
|
|
2208
|
+
* head change invalidates the verdict, so a met row whose PR head has moved
|
|
2209
|
+
* returns to pending instead of rendering `[CONDITION MET]` for a stale
|
|
2210
|
+
* head (#808). */
|
|
2211
|
+
conditionHead?: string;
|
|
1933
2212
|
state: DecisionState;
|
|
1934
2213
|
resolvedAt?: number;
|
|
1935
2214
|
/** The answer, the withdrawal reason, or the expiry note. */
|
|
@@ -1987,6 +2266,17 @@ export interface Store {
|
|
|
1987
2266
|
/** Partial patch; an explicit `null` clears a column, `undefined`/absence leaves it alone (#468). */
|
|
1988
2267
|
updateRun(id: string, patch: RunPatch): void;
|
|
1989
2268
|
getRun(id: string): RunRecord | undefined;
|
|
2269
|
+
/**
|
|
2270
|
+
* Counts of this project's runs that recorded a graph-tools session
|
|
2271
|
+
* observation (#726), split by the observed truth value. The doctor reads
|
|
2272
|
+
* these to report what dispatched sessions actually saw, and reports nothing
|
|
2273
|
+
* as "observed" when no run has recorded one.
|
|
2274
|
+
*/
|
|
2275
|
+
graphToolsObservationCounts(project: string): {
|
|
2276
|
+
recorded: number;
|
|
2277
|
+
present: number;
|
|
2278
|
+
absent: number;
|
|
2279
|
+
};
|
|
1990
2280
|
/** Runs whose issue is occupied: a live worker, or a green PR awaiting merge. */
|
|
1991
2281
|
activeRuns(project: string): RunRecord[];
|
|
1992
2282
|
/** Runs backed by a worker process — what capacity counts. Subset of {@link Store.activeRuns}. */
|
|
@@ -2002,10 +2292,20 @@ export interface Store {
|
|
|
2002
2292
|
* predecessor opened. Merged rows keep the same recent-history bound. */
|
|
2003
2293
|
runsForProjectPr(project: string, prUrl: string, mergedSinceEpochMs: number): RunRecord[];
|
|
2004
2294
|
/** Persist one review-revision request (#677), durably, before anything is
|
|
2005
|
-
* woken.
|
|
2006
|
-
* pending undelivered —
|
|
2007
|
-
*
|
|
2008
|
-
|
|
2295
|
+
* woken. A same-run, same-head finding folds into the revision already
|
|
2296
|
+
* pending undelivered (`appended` — one round, one row, one worker
|
|
2297
|
+
* attempt, and the resumed session gets both findings); otherwise it opens
|
|
2298
|
+
* a new round (`created`). A revision already pending at a different head
|
|
2299
|
+
* cannot be amended (`refused`) — that round must dispatch and settle
|
|
2300
|
+
* first. The duplicate in-flight guard is the same single transaction, so
|
|
2301
|
+
* two concurrent same-head findings cannot both create a row or overwrite
|
|
2302
|
+
* each other (the run row's state is the other half once the revision is
|
|
2303
|
+
* dispatched). */
|
|
2304
|
+
enqueueReviewRevision(draft: Omit<ReviewRevisionRecord, "id">): ReviewRevisionEnqueue;
|
|
2305
|
+
/** The pending revision for one run, if any — the header the verb reads to
|
|
2306
|
+
* tell "this call folds into the in-flight round" from "this call opens a
|
|
2307
|
+
* new round" before the round-ceiling gate applies. */
|
|
2308
|
+
pendingReviewForRun(project: string, runId: string): ReviewRevisionRecord | undefined;
|
|
2009
2309
|
/** Review revisions not yet handed to a worker, oldest first — what the
|
|
2010
2310
|
* daemon's dispatch pass wakes on its next tick. */
|
|
2011
2311
|
pendingReviewRevisions(project: string): ReviewRevisionRecord[];
|
|
@@ -2155,6 +2455,28 @@ export interface Store {
|
|
|
2155
2455
|
/** Resolve one idea to `groomed` (recording the issue URL #300 chose) or
|
|
2156
2456
|
* `dismissed`. `false` when the id is unknown. */
|
|
2157
2457
|
resolveIntake(id: string, state: "groomed" | "dismissed", issueUrl?: string): boolean;
|
|
2458
|
+
/** Record the current grooming verdict for one issue, replacing any prior
|
|
2459
|
+
* row — one row per project + issue, never a history (#735). */
|
|
2460
|
+
upsertGrooming(draft: GroomingDraft): void;
|
|
2461
|
+
/** The current grooming verdict for one issue, or undefined when none. */
|
|
2462
|
+
grooming(project: string, issue: number): GroomingRecord | undefined;
|
|
2463
|
+
/** Current grooming verdicts for a project, issue-ascending; `verdict`
|
|
2464
|
+
* narrows to one verdict — the tick's known-blocked read is
|
|
2465
|
+
* `groomingVerdicts(project, "blocked")`. */
|
|
2466
|
+
groomingVerdicts(project: string, verdict?: GroomingVerdict): GroomingRecord[];
|
|
2467
|
+
/**
|
|
2468
|
+
* Reconcile admission's lane/dependency holds against the persisted blocked
|
|
2469
|
+
* verdicts: each currently held issue is recorded as `blocked`, and in the
|
|
2470
|
+
* same transaction every `blocked` row whose lane/dependency hold this pass
|
|
2471
|
+
* does not name is deleted — the hold no longer applies, so the state
|
|
2472
|
+
* self-heals instead of lingering as a cache someone must invalidate. Runs
|
|
2473
|
+
* at the end of every admission pass. Never touches `promotable` or
|
|
2474
|
+
* `considered` rows, which belong to #679's scout loop.
|
|
2475
|
+
*/
|
|
2476
|
+
reconcileGrooming(
|
|
2477
|
+
project: string,
|
|
2478
|
+
holds: readonly { issue: number; reason: AdmissionHoldReason; detail?: string }[],
|
|
2479
|
+
): void;
|
|
2158
2480
|
/** Count and age source for status and digest prompt bounds. */
|
|
2159
2481
|
digestBacklog(project: string): DigestBacklog;
|
|
2160
2482
|
/** Add one bounded observation to the per-day friction rollup. */
|
|
@@ -2340,8 +2662,21 @@ export interface Store {
|
|
|
2340
2662
|
/** Answer or withdraw one. `false` when the id is unknown or already closed,
|
|
2341
2663
|
* so a double-resolve cannot overwrite the first answer. */
|
|
2342
2664
|
resolveDecision(id: string, state: "answered" | "withdrawn", resolution: string, at: number): boolean;
|
|
2343
|
-
/**
|
|
2344
|
-
|
|
2665
|
+
/**
|
|
2666
|
+
* First observation that a row's condition came true. Idempotent per head:
|
|
2667
|
+
* a second call changes nothing, but a `pr-checks-green` / `pr-review-ready`
|
|
2668
|
+
* row whose PR head moved is cleared first and then re-marks against the new
|
|
2669
|
+
* head.
|
|
2670
|
+
* `head` binds the verdict to the PR head it was observed at (#808).
|
|
2671
|
+
*/
|
|
2672
|
+
markDecisionConditionMet(id: string, at: number, head?: string): boolean;
|
|
2673
|
+
/**
|
|
2674
|
+
* Drop a condition's met state — timestamp and any head binding — when the
|
|
2675
|
+
* observation it was set from stopped holding. The head-bound conditions
|
|
2676
|
+
* call this when the PR head changed, so the row returns to pending instead
|
|
2677
|
+
* of rendering `[CONDITION MET]` for checks that are still running (#808).
|
|
2678
|
+
*/
|
|
2679
|
+
clearDecisionConditionMet(id: string): boolean;
|
|
2345
2680
|
/** Close every open row past its deadline and return them, so the caller can
|
|
2346
2681
|
* say what it just closed rather than reporting a count. */
|
|
2347
2682
|
expireDueDecisions(project: string, now: number): DecisionRecord[];
|
|
@@ -2588,6 +2923,16 @@ export const VERB_NAMES = [
|
|
|
2588
2923
|
* OMP session on the existing branch and PR. No close, no reopen, no
|
|
2589
2924
|
* redispatch. */
|
|
2590
2925
|
"conductor_pr_review",
|
|
2926
|
+
/** The orchestrator-only, settled-run recovery operation (#806): open (or
|
|
2927
|
+
* adopt) the missing pull request for a terminal run whose stored branch
|
|
2928
|
+
* exists at its exact recorded head. The recovery proves everything a
|
|
2929
|
+
* worker's own `conductor_pr_create` took for granted from a live channel —
|
|
2930
|
+
* no worker is live, the routed repo matches, the remote branch is at the
|
|
2931
|
+
* exact recorded head, the issue is still open, and no PR already
|
|
2932
|
+
* represents that head — so a stranded `pushed-pending` / lost-PR run can
|
|
2933
|
+
* re-enter normal review/merge without a fresh clone. Idempotent: an
|
|
2934
|
+
* already-matching PR is returned, not duplicated. */
|
|
2935
|
+
"conductor_pr_recover",
|
|
2591
2936
|
/** The one read verb. It answers with the merge gate's own verdict, so
|
|
2592
2937
|
* "pushed-green" is the dispatcher's reading of the PR rather than a claim the
|
|
2593
2938
|
* worker makes about itself from whatever it happened to run. */
|
|
@@ -2653,6 +2998,38 @@ export const VERB_REFUSALS = [
|
|
|
2653
2998
|
"pr-not-this-run",
|
|
2654
2999
|
/** An unrecorded recovery PR was authorized, but not for these exact inputs. */
|
|
2655
3000
|
"recovery-authorization-mismatch",
|
|
3001
|
+
/** The recovery target resolved to a run that does not exist in the store. */
|
|
3002
|
+
"recovery-no-run",
|
|
3003
|
+
/** The run the recovery was pointed at still has a live worker on it. */
|
|
3004
|
+
"recovery-run-live",
|
|
3005
|
+
/** The run is terminal but not in a state the settlement sweep owns, so a
|
|
3006
|
+
* recovered PR would never re-enter verification/review/merge. */
|
|
3007
|
+
"recovery-unsettled-state",
|
|
3008
|
+
/** The tracker could not say whether the issue is open. Recovery fails
|
|
3009
|
+
* closed rather than opening a PR beside (or for) a possibly-closed
|
|
3010
|
+
* issue. */
|
|
3011
|
+
"issue-state-unreadable",
|
|
3012
|
+
/** The recovered work has already landed or been declined: the issue is
|
|
3013
|
+
* closed, the run settled merged, or its recorded PR merged. Nothing to
|
|
3014
|
+
* recover, and recovery must not reopen it. */
|
|
3015
|
+
"recovery-issue-resolved",
|
|
3016
|
+
/** The run records no exact 40-hex head to compare its stored branch
|
|
3017
|
+
* against, so there is nothing durable to verify a recovered PR would
|
|
3018
|
+
* publish. */
|
|
3019
|
+
"recovery-unrecorded-head",
|
|
3020
|
+
/** The branch exists but not at the run's recorded head (replaced,
|
|
3021
|
+
* force-pushed or superseded by a live PR), so a created PR would publish
|
|
3022
|
+
* work the daemon never verified. */
|
|
3023
|
+
"recovery-head-mismatch",
|
|
3024
|
+
/** The routed repository a terminal run recorded no longer has an entry in
|
|
3025
|
+
* this project's routing, so the daemon cannot create a PR for it. */
|
|
3026
|
+
"recovery-repo-unrouted",
|
|
3027
|
+
/** The run's durable `prUrl` is a non-URL value the tracker cannot address
|
|
3028
|
+
* (other than the normalized `pending` settlement sentinel, #866). There is
|
|
3029
|
+
* no PR to read and none to guess at — a corrupt record, not an unreadable
|
|
3030
|
+
* one, so it refuses by name instead of reading as a never-clearing
|
|
3031
|
+
* `head-unresolvable`. */
|
|
3032
|
+
"recorded-pr-corrupt",
|
|
2656
3033
|
/** The run has no pull request to act on. */
|
|
2657
3034
|
"pr-missing",
|
|
2658
3035
|
/**
|
|
@@ -2695,6 +3072,15 @@ export const VERB_REFUSALS = [
|
|
|
2695
3072
|
"label-not-in-vocabulary",
|
|
2696
3073
|
/** The label is a lifecycle label; those transitions stay the daemon's (#26). */
|
|
2697
3074
|
"label-is-lifecycle",
|
|
3075
|
+
/** The label is the operator's park gesture; only the operator may set or
|
|
3076
|
+
* clear it, orchestrator and worker alike (#507). */
|
|
3077
|
+
"label-is-operator-owned",
|
|
3078
|
+
/** Adding the queue label was refused because the issue carries a clearly
|
|
3079
|
+
* delimited write-lane section heading that parsed no path-like files
|
|
3080
|
+
* (#825): promoting it would fail open beside overlapping work, so the
|
|
3081
|
+
* verb refuses with an actionable syntax error instead of echoing a
|
|
3082
|
+
* fail-open the heading contradicts. */
|
|
3083
|
+
"file-lane-unparseable",
|
|
2698
3084
|
/** The release grant does not permit this shape for this caller. */
|
|
2699
3085
|
"release-not-granted",
|
|
2700
3086
|
/** The artefact or environment is not one this project declared (#129). */
|