omp-conductor 0.18.2 → 0.19.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.
- package/README.md +105 -40
- package/REFERENCE.md +865 -30
- package/package.json +1 -1
- package/schema/config.schema.json +26 -0
- package/src/admission.ts +212 -26
- package/src/ask.ts +288 -1
- package/src/briefs/orchestrator.md +6 -5
- package/src/cli.ts +5 -1
- package/src/command-help.ts +9 -1
- package/src/command-manifest.ts +36 -3
- package/src/commands/arm.ts +5 -1
- package/src/commands/context.ts +2 -0
- package/src/commands/message.ts +26 -2
- package/src/commands/reconcile-units.ts +104 -0
- package/src/commands/release-composition.ts +232 -0
- package/src/commands/resume.ts +2 -27
- package/src/commands/setup.ts +101 -16
- package/src/commands/stats.ts +11 -30
- package/src/commands/tail.ts +31 -1
- package/src/commands/upgrade.ts +20 -3
- package/src/commands/verb.ts +2 -1
- package/src/config-schema.ts +19 -0
- package/src/config.ts +80 -0
- package/src/credential-class.ts +366 -0
- package/src/daemon.ts +1218 -288
- package/src/dashboard/app.js +504 -2
- package/src/dashboard/controls.ts +336 -0
- package/src/dashboard/index.html +30 -0
- package/src/dashboard/server.ts +271 -30
- package/src/dashboard/style.css +116 -0
- package/src/dashboard/transcript.ts +173 -0
- package/src/doctor.ts +377 -20
- package/src/failure-class.ts +59 -0
- package/src/fleet.ts +497 -15
- package/src/host.ts +6 -130
- package/src/omp.ts +29 -0
- package/src/orchestrator-tick.ts +343 -88
- package/src/pause.ts +233 -0
- package/src/settlement.ts +159 -2
- package/src/setup-answers.ts +97 -0
- package/src/setup-host.ts +321 -1155
- package/src/setup-install.ts +204 -27
- package/src/setup-wizard.ts +111 -50
- package/src/setup.ts +33 -0
- package/src/spend-telemetry.ts +117 -0
- package/src/stats.ts +35 -0
- package/src/status-render.ts +348 -19
- package/src/store.ts +1229 -55
- package/src/telegram-freshness.ts +269 -0
- package/src/to-spec.ts +27 -0
- package/src/types.ts +697 -4
- package/src/unblock.ts +22 -0
- package/src/unit-reconcile.ts +303 -0
- package/src/upgrade-verify.ts +8 -1
- package/src/upgrade.ts +299 -12
- package/src/verbs/actions.ts +124 -10
- package/src/verbs/protocol.ts +70 -2
- package/src/verbs/server.ts +447 -8
- package/src/wake.ts +48 -0
- package/src/worker.ts +403 -3
package/src/types.ts
CHANGED
|
@@ -51,6 +51,29 @@ export interface Caps {
|
|
|
51
51
|
* still apply). `0` is a hard stop — deliberate, not "unset".
|
|
52
52
|
*/
|
|
53
53
|
dailySpendUsd: number | null;
|
|
54
|
+
/**
|
|
55
|
+
* Ceiling on what ONE run may spend, and the size of the reservation
|
|
56
|
+
* admission takes out of the daily budget before it launches anything
|
|
57
|
+
* (#851).
|
|
58
|
+
*
|
|
59
|
+
* The daily cap alone is a post-spend stop: it compares spend-to-date
|
|
60
|
+
* against the ceiling at admission time and pauses the fleet on the *next*
|
|
61
|
+
* pass, so one expensive run can cross it by its whole cost before anything
|
|
62
|
+
* notices. On 2026-08-21 a single `@slow` run spent an estimated $25.16 of a
|
|
63
|
+
* $25.00 daily cap unopposed, and the fleet paused only once it had settled.
|
|
64
|
+
*
|
|
65
|
+
* `null` means "derive it": `dailySpendUsd / maxConcurrentWorkers`, the
|
|
66
|
+
* largest per-run allowance that still lets the fleet run at its configured
|
|
67
|
+
* concurrency. Reserving the whole daily cap per run would bound overshoot
|
|
68
|
+
* equally well and silently serialise every fleet on defaults, turning a
|
|
69
|
+
* spend guard into a concurrency change nobody asked for; N reservations of
|
|
70
|
+
* cap/N are still the cap, so nothing is given away. Set it explicitly to
|
|
71
|
+
* bound one run harder. It may never exceed `dailySpendUsd` — a per-run
|
|
72
|
+
* allowance larger than the day's budget is a reservation that can never be
|
|
73
|
+
* satisfied. With `dailySpendUsd: null` the fleet is unmetered and an
|
|
74
|
+
* explicit value still bounds one run.
|
|
75
|
+
*/
|
|
76
|
+
maxRunSpendUsd: number | null;
|
|
54
77
|
/**
|
|
55
78
|
* Subscription/plan allowance guard, independent of `dailySpendUsd` and
|
|
56
79
|
* enforced from the provider's own reported usage rather than from a dollar
|
|
@@ -708,6 +731,105 @@ export interface ReviewAdjudicationProvenance {
|
|
|
708
731
|
readonly resolvedAt: number;
|
|
709
732
|
}
|
|
710
733
|
|
|
734
|
+
/**
|
|
735
|
+
* The lifecycle of one PR's terminal review-ceiling adjudication (#874).
|
|
736
|
+
*
|
|
737
|
+
* The terminal states ARE the verdict — there is deliberately no second
|
|
738
|
+
* `verdict` column beside this one. A state plus an independently-stored verdict
|
|
739
|
+
* is two sources of truth for one fact, and the copy nobody updates is the one a
|
|
740
|
+
* renderer reads. What is stored alongside is what the state cannot say:
|
|
741
|
+
* the evidence that produced it and the disposition applied because of it.
|
|
742
|
+
*
|
|
743
|
+
* - `pending` — recorded, one-shot claimed, no session launched yet.
|
|
744
|
+
* - `running` — an adjudicator session is live on it.
|
|
745
|
+
* - `cleared` — the PR may take the ordinary orchestrator-owned merge path.
|
|
746
|
+
* - `rejected` — it may not; #876 owns the autonomous disposition.
|
|
747
|
+
* - `unavailable-model` — the configured role could not be resolved or
|
|
748
|
+
* launched. Distinct from `failed` because the remedy is configuration, not
|
|
749
|
+
* a retry.
|
|
750
|
+
* - `stale-head` — the PR moved after this adjudication was recorded, so its
|
|
751
|
+
* verdict can no longer describe what would merge.
|
|
752
|
+
* - `failed` — it ran and could not produce a verdict.
|
|
753
|
+
*/
|
|
754
|
+
export type ReviewAdjudicationState =
|
|
755
|
+
| "pending"
|
|
756
|
+
| "running"
|
|
757
|
+
| "cleared"
|
|
758
|
+
| "rejected"
|
|
759
|
+
| "unavailable-model"
|
|
760
|
+
| "stale-head"
|
|
761
|
+
| "failed";
|
|
762
|
+
|
|
763
|
+
/** The terminal states: a row in one of these is finished forever, and is the
|
|
764
|
+
* verdict. Exported so the store's transition guard and every reader agree on
|
|
765
|
+
* the same set rather than each spelling its own list. */
|
|
766
|
+
export const TERMINAL_REVIEW_ADJUDICATION_STATES: readonly ReviewAdjudicationState[] = [
|
|
767
|
+
"cleared",
|
|
768
|
+
"rejected",
|
|
769
|
+
"unavailable-model",
|
|
770
|
+
"stale-head",
|
|
771
|
+
"failed",
|
|
772
|
+
];
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* One durable review-ceiling adjudication (#874): the record that makes the
|
|
776
|
+
* final adjudication a lifecycle rather than an in-memory fourth review round.
|
|
777
|
+
*
|
|
778
|
+
* Its identity is the exact reviewed head, and that is the one-shot guarantee:
|
|
779
|
+
* a repeated tick, a racing dispatcher or a daemon restart re-reads this row
|
|
780
|
+
* instead of launching a second adjudicator. A round in `review_revisions` is
|
|
781
|
+
* deliberately NOT reused for this — that would spend a worker review round the
|
|
782
|
+
* ceiling has already exhausted, and would let the same session and model
|
|
783
|
+
* produce a nominal fourth opinion, which is exactly what #873 forbids.
|
|
784
|
+
*/
|
|
785
|
+
export interface ReviewAdjudicationRecord {
|
|
786
|
+
id: string;
|
|
787
|
+
project: string;
|
|
788
|
+
issue: number;
|
|
789
|
+
prUrl: string;
|
|
790
|
+
/** The exact head this adjudication is about. The one-shot key, with
|
|
791
|
+
* `project` and `prUrl`: a moved head is a different adjudication, and a
|
|
792
|
+
* verdict never travels to a head it did not read. */
|
|
793
|
+
headSha: string;
|
|
794
|
+
/** The policy's adjudicator role at admission, verbatim — what was asked for,
|
|
795
|
+
* which stays readable even when the role later resolves differently. */
|
|
796
|
+
role: string;
|
|
797
|
+
/** What the role actually resolved to at launch (#875). Absent while
|
|
798
|
+
* `pending`: nothing has resolved a model yet, and recording an intention as
|
|
799
|
+
* provenance would make status claim a launch that never happened. */
|
|
800
|
+
provenance?: ReviewAdjudicationProvenance;
|
|
801
|
+
state: ReviewAdjudicationState;
|
|
802
|
+
/** Why the terminal state is what it is: the adjudicator's verdict text, or
|
|
803
|
+
* the diagnostic for a fail-closed outcome (the two heads of a `stale-head`,
|
|
804
|
+
* the unresolvable role, the failure). */
|
|
805
|
+
evidence?: string;
|
|
806
|
+
/** What was done about the verdict (#876 writes it), kept separate from the
|
|
807
|
+
* verdict itself so "rejected" and "rejected, and here is what happened"
|
|
808
|
+
* are never conflated. */
|
|
809
|
+
disposition?: string;
|
|
810
|
+
requestedAt: number;
|
|
811
|
+
/** Set when an adjudicator session was launched for this row. */
|
|
812
|
+
dispatchedAt?: number;
|
|
813
|
+
settledAt?: number;
|
|
814
|
+
/** The findings that provoked the escalation (#932): what the orchestrator was
|
|
815
|
+
* submitting when `conductor_pr_review` hit the ceiling. Evidence for the
|
|
816
|
+
* adjudicator, deliberately not a `review_revisions` round — a round would
|
|
817
|
+
* spend a ceiling that is already exhausted and would wake an implementer. */
|
|
818
|
+
findings?: string;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* What admitting an adjudication did (#874). Idempotent by construction: the
|
|
823
|
+
* second caller for one head is told the row already exists rather than being
|
|
824
|
+
* allowed to create a twin, and a non-terminal adjudication at a DIFFERENT head
|
|
825
|
+
* on the same PR refuses — that PR moved, and adjudicating two heads at once is
|
|
826
|
+
* the duplicate this lifecycle exists to prevent.
|
|
827
|
+
*/
|
|
828
|
+
export type ReviewAdjudicationAdmission =
|
|
829
|
+
| { kind: "created"; record: ReviewAdjudicationRecord }
|
|
830
|
+
| { kind: "existing"; record: ReviewAdjudicationRecord }
|
|
831
|
+
| { kind: "refused"; block: "in-flight-different-head"; record: ReviewAdjudicationRecord };
|
|
832
|
+
|
|
711
833
|
/**
|
|
712
834
|
* One durable review-revision request (#677): the orchestrator returned a
|
|
713
835
|
* green, run-owned pull request to its worker with blocking findings, and
|
|
@@ -738,6 +860,12 @@ export interface ReviewRevisionRecord {
|
|
|
738
860
|
dispatchedAt?: number;
|
|
739
861
|
settledAt?: number;
|
|
740
862
|
outcome?: ReviewRevisionOutcome;
|
|
863
|
+
/** How many times an infrastructure kill has returned this round to the
|
|
864
|
+
* pending set (#903). A dispatch that died before the resumed session took
|
|
865
|
+
* a turn spent no review round, so the row is re-queued rather than settled
|
|
866
|
+
* `failed` — and this counter is what bounds that, durably, so an outage
|
|
867
|
+
* loop cannot retry one round forever. Absent means zero. */
|
|
868
|
+
infraRetries?: number;
|
|
741
869
|
}
|
|
742
870
|
|
|
743
871
|
/**
|
|
@@ -755,6 +883,72 @@ export type ReviewRevisionEnqueue =
|
|
|
755
883
|
| { kind: "appended"; record: ReviewRevisionRecord }
|
|
756
884
|
| { kind: "refused"; block: "pending-different-head" };
|
|
757
885
|
|
|
886
|
+
/**
|
|
887
|
+
* How many times one review round may be returned to the pending set after an
|
|
888
|
+
* infrastructure kill before the daemon stops retrying it (#903).
|
|
889
|
+
*
|
|
890
|
+
* The same bound, and the same reasoning, as the run-level infra strike caps:
|
|
891
|
+
* three dispatches killed before the resumed session took a turn mean the host
|
|
892
|
+
* or the harness is broken rather than unlucky, and a fourth retry per tick
|
|
893
|
+
* would loop forever against it. Below the bound the round is free — it spent
|
|
894
|
+
* no worker turn, so charging it against the review-round ceiling would let an
|
|
895
|
+
* outage close a PR's last route to a merge; at the bound it settles `failed`
|
|
896
|
+
* exactly as it does today and escalates once.
|
|
897
|
+
*/
|
|
898
|
+
export const REVIEW_ROUND_INFRA_MAX_RETRIES = 3;
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* What the store did with a review round whose dispatch was killed by
|
|
902
|
+
* infrastructure (#903). `requeued` cleared the dispatch marker so the next
|
|
903
|
+
* pass resumes the same session on the same round; `exhausted` means the bound
|
|
904
|
+
* is spent and the caller must settle the round the ordinary way.
|
|
905
|
+
*/
|
|
906
|
+
export type ReviewRevisionRetry =
|
|
907
|
+
| { kind: "requeued"; retries: number }
|
|
908
|
+
| { kind: "exhausted"; retries: number };
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* One recorded review clearance (#913): an explicit orchestrator disposition
|
|
912
|
+
* that the durable review findings standing at one exact head are settled, so
|
|
913
|
+
* `conductor_pr_merge` may proceed at that head.
|
|
914
|
+
*
|
|
915
|
+
* It exists because the #888 merge gate had no reachable clearance at an
|
|
916
|
+
* unchanged head: a re-review at the same head *adds* a blocking round rather
|
|
917
|
+
* than superseding one, and nothing an orchestrator can call settles a round —
|
|
918
|
+
* so a PR at the review-round ceiling was permanently unmergeable. Rows are
|
|
919
|
+
* append-only audit records, and they clear only **backwards**: a clearance
|
|
920
|
+
* suppresses a revision row whose own last activity is at or before the
|
|
921
|
+
* clearance's `at`, never a finding recorded afterwards. It bypasses nothing
|
|
922
|
+
* but the review gate — authority, pause, base-red-freeze, exact-head,
|
|
923
|
+
* green-checks, release composition and single-flight all still apply.
|
|
924
|
+
*/
|
|
925
|
+
export interface ReviewClearance {
|
|
926
|
+
id: string;
|
|
927
|
+
project: string;
|
|
928
|
+
prUrl: string;
|
|
929
|
+
/** The exact head the findings were cleared at. Matched case-insensitively. */
|
|
930
|
+
headSha: string;
|
|
931
|
+
at: number;
|
|
932
|
+
/** The caller's role, as the verb channel resolved it. */
|
|
933
|
+
by: string;
|
|
934
|
+
reason: string;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* The durable review evidence standing between one pushed-green PR and a
|
|
939
|
+
* merge at its exact recorded head (#888). Derived from the same
|
|
940
|
+
* `review_revisions` rows `conductor_pr_merge` refuses on, so status can
|
|
941
|
+
* never present a green PR as merge-ready while the privileged verb will
|
|
942
|
+
* (correctly) refuse it.
|
|
943
|
+
*/
|
|
944
|
+
export interface ReviewHeadBlocker {
|
|
945
|
+
/** The review round whose findings still stand at this head. */
|
|
946
|
+
round: number;
|
|
947
|
+
/** Why the round still blocks: queued and not yet addressed, dispatched but
|
|
948
|
+
* never finished (crashed mid-review), or settled `failed` at this head. */
|
|
949
|
+
state: "pending" | "crashed" | "failed";
|
|
950
|
+
}
|
|
951
|
+
|
|
758
952
|
/**
|
|
759
953
|
* A model-supplied justification: one value out of a closed set, plus prose that
|
|
760
954
|
* is written down and read by nothing that decides.
|
|
@@ -861,6 +1055,36 @@ export interface ProjectConfig {
|
|
|
861
1055
|
* when absent or unusable.
|
|
862
1056
|
*/
|
|
863
1057
|
modelFallbackThreshold?: number;
|
|
1058
|
+
/**
|
|
1059
|
+
* An opaque omp selector for one stronger worker tier, used exactly once per
|
|
1060
|
+
* issue chain when a run spun to a cap with nothing to show (#807).
|
|
1061
|
+
*
|
|
1062
|
+
* Conductor never resolves it: `@slow`, a role name or a bare model pattern
|
|
1063
|
+
* are all the same string here, handed to the harness at dispatch the way
|
|
1064
|
+
* {@link workerModel} is. It is deliberately NOT part of
|
|
1065
|
+
* {@link modelFallbacks} — that chain answers provider faults and must stay
|
|
1066
|
+
* provider-only — and absent or blank preserves today's settle/escalate
|
|
1067
|
+
* behaviour byte for byte.
|
|
1068
|
+
*/
|
|
1069
|
+
workerEscalationModel?: string;
|
|
1070
|
+
/**
|
|
1071
|
+
* Providers whose routes must bill to a subscription (OAuth) credential, and
|
|
1072
|
+
* never to an API key (#852).
|
|
1073
|
+
*
|
|
1074
|
+
* Declared per **provider**, not per model or per run, because that is the only
|
|
1075
|
+
* truthful unit: a credential belongs to a provider — a model has none of its
|
|
1076
|
+
* own — and a run's provider is not knowable in advance, since
|
|
1077
|
+
* {@link workerModel} is an opaque selector that may be a role alias and the
|
|
1078
|
+
* provider-fault fallback chain (#286) can move a live run onto another
|
|
1079
|
+
* provider mid-flight. So the declaration reads "this provider must bill to its
|
|
1080
|
+
* subscription", and every candidate is held while that is untrue.
|
|
1081
|
+
*
|
|
1082
|
+
* Enforced by conductor rather than omp because omp cannot express it:
|
|
1083
|
+
* `AuthStorage.getApiKey` cascades from a disabled OAuth credential into the
|
|
1084
|
+
* same provider's API key, and no setting, selector suffix or request option
|
|
1085
|
+
* stops it. Absent or empty dispatches byte-for-byte as it always has.
|
|
1086
|
+
*/
|
|
1087
|
+
requireOauthProviders?: readonly string[];
|
|
864
1088
|
/**
|
|
865
1089
|
* An opaque omp settings map layered into every worker session this project
|
|
866
1090
|
* dispatches (#537). At dispatch it is materialised verbatim to a fleet-owned
|
|
@@ -1467,9 +1691,23 @@ export type FailureClass = (typeof FAILURE_CLASSES)[number];
|
|
|
1467
1691
|
/**
|
|
1468
1692
|
* What the daemon does about a class.
|
|
1469
1693
|
*
|
|
1470
|
-
*
|
|
1471
|
-
*
|
|
1472
|
-
*
|
|
1694
|
+
* Two of these perform nothing, and they are not the same nothing:
|
|
1695
|
+
*
|
|
1696
|
+
* - `hold` — recorded and deliberately left alone, because acting would destroy
|
|
1697
|
+
* something. `orphan-dirty` is the case: the tree is the only copy, so
|
|
1698
|
+
* `unblock` refuses rather than re-claiming over it.
|
|
1699
|
+
* - `none` — the classifier declining to name a failure at all. Two callers
|
|
1700
|
+
* reach it, and neither is "unclassifiable": `classifyRun` returns it for a
|
|
1701
|
+
* terminal row whose PR is open and fully green, where `classifyAndRecover`
|
|
1702
|
+
* restores `pushed-green` *without* persisting a class (#766); and the settle
|
|
1703
|
+
* sweep persists it beside `returned-for-revision` when a reviewer closes
|
|
1704
|
+
* pushed work without merging, where the remedy is the queue label the sweep
|
|
1705
|
+
* deliberately leaves on.
|
|
1706
|
+
*
|
|
1707
|
+
* An *unclassifiable* run is `unknown` → `escalate`, and never a silent retry.
|
|
1708
|
+
* This comment previously said nothing in the decision table maps to `none`,
|
|
1709
|
+
* which both callers above contradict; it cost a reading of two modules to
|
|
1710
|
+
* discover, which is what a wrong comment costs (#132).
|
|
1473
1711
|
*/
|
|
1474
1712
|
export const RECOVERY_ACTIONS = [
|
|
1475
1713
|
"requeue",
|
|
@@ -1529,6 +1767,48 @@ export interface BaseFreeze {
|
|
|
1529
1767
|
clearedReason?: string;
|
|
1530
1768
|
}
|
|
1531
1769
|
|
|
1770
|
+
/**
|
|
1771
|
+
* The active release composition for one project (#850): the campaign whose
|
|
1772
|
+
* release is being assembled, and the exact PRs allowed into it. While
|
|
1773
|
+
* `closedAt` is absent the composition is active and `prMergeVerb` refuses
|
|
1774
|
+
* every `conductor_pr_merge` whose PR is neither named here nor covered by a
|
|
1775
|
+
* recorded operator override, with the `outside-active-release` refusal — the
|
|
1776
|
+
* mechanical form of "a patch contains no unrelated merges". The row is
|
|
1777
|
+
* SQLite-backed, so the guard survives daemon and orchestrator restarts, and
|
|
1778
|
+
* it retires only through an explicit completion or cancellation; each
|
|
1779
|
+
* transition also writes an immutable material event as its audit trail.
|
|
1780
|
+
*/
|
|
1781
|
+
export interface ReleaseComposition {
|
|
1782
|
+
/** The campaign identifier, e.g. `v0.18.1-reliability`. */
|
|
1783
|
+
campaign: string;
|
|
1784
|
+
/** Full PR URLs explicitly allowed into this release. */
|
|
1785
|
+
allowedPrUrls: string[];
|
|
1786
|
+
declaredAt: number;
|
|
1787
|
+
declaredBy?: string;
|
|
1788
|
+
/** When the composition retired — absent means it is active. */
|
|
1789
|
+
closedAt?: number;
|
|
1790
|
+
/** `operator` via the CLI on every path that retires a composition. */
|
|
1791
|
+
closedBy?: string;
|
|
1792
|
+
closedReason?: string;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
/**
|
|
1796
|
+
* One operator override admitting exactly one PR into one release (#850).
|
|
1797
|
+
* Rows are append-only audit records: an override applies to its campaign
|
|
1798
|
+
* only, never outlives it, and bypasses nothing but the composition check —
|
|
1799
|
+
* authority, pause, base-red-freeze, exact-head, green-checks and
|
|
1800
|
+
* single-flight all still apply to the overridden merge.
|
|
1801
|
+
*/
|
|
1802
|
+
export interface ReleaseCompositionOverride {
|
|
1803
|
+
id: string;
|
|
1804
|
+
project: string;
|
|
1805
|
+
campaign: string;
|
|
1806
|
+
prUrl: string;
|
|
1807
|
+
at: number;
|
|
1808
|
+
by: string;
|
|
1809
|
+
reason: string;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1532
1812
|
/**
|
|
1533
1813
|
* Execution state is separate from the tracker's own labels on purpose: labels
|
|
1534
1814
|
* are coarse and human-editable, while the loop needs to distinguish "pushed
|
|
@@ -1649,6 +1929,18 @@ export interface RunRecord {
|
|
|
1649
1929
|
/** Effective turn ceiling for this run; operators may only raise it. */
|
|
1650
1930
|
maxTurns: number;
|
|
1651
1931
|
spendUsd: number;
|
|
1932
|
+
/**
|
|
1933
|
+
* The per-run spend allowance admission reserved out of the daily budget
|
|
1934
|
+
* before this run launched (#851). Held for as long as the row is live, and
|
|
1935
|
+
* released the moment it is not — reservations are summed over live rows, so
|
|
1936
|
+
* a settle releases the unspent remainder with no bookkeeping to forget and
|
|
1937
|
+
* a daemon restart re-reads the same reservations off the same rows.
|
|
1938
|
+
*
|
|
1939
|
+
* Absent means the row predates the reservation, or was admitted while the
|
|
1940
|
+
* fleet had no spend cap at all. Never "reserved nothing": a run admitted
|
|
1941
|
+
* under a cap always records what it reserved.
|
|
1942
|
+
*/
|
|
1943
|
+
spendReservedUsd?: number;
|
|
1652
1944
|
/** How many in-session HTTP 429 responses the harness retried before the run
|
|
1653
1945
|
* ended, counted by the worker from the session's `message_end` events while
|
|
1654
1946
|
* the run was live (#573). Absent means the column predates the count or no
|
|
@@ -1684,6 +1976,42 @@ export interface RunRecord {
|
|
|
1684
1976
|
autoCompactionCount?: number;
|
|
1685
1977
|
/** omp session transcript, so a human can read what the worker actually did. */
|
|
1686
1978
|
sessionFile?: string;
|
|
1979
|
+
/**
|
|
1980
|
+
* The exact `session-host` pid this run's Herdr representation was reported
|
|
1981
|
+
* for, and the pane Herdr knows it as (#842).
|
|
1982
|
+
*
|
|
1983
|
+
* Durable so a restart can find the pane its dead worker left behind, rather
|
|
1984
|
+
* than leaving a workspace showing a live worker that no longer exists. All
|
|
1985
|
+
* three are absent together — a run with a pane always has a pid, because the
|
|
1986
|
+
* pid is what the pane was opened from.
|
|
1987
|
+
*
|
|
1988
|
+
* A matching pid is deliberately NOT treated as proof of life: pids are reused,
|
|
1989
|
+
* and a session-host child dies with the daemon that owns its socket, so a live
|
|
1990
|
+
* row after a restart is an orphan whatever pid it recorded.
|
|
1991
|
+
*/
|
|
1992
|
+
workerPid?: number;
|
|
1993
|
+
paneId?: string;
|
|
1994
|
+
paneLabel?: string;
|
|
1995
|
+
/**
|
|
1996
|
+
* Why this run has no workspace representation right now (#841).
|
|
1997
|
+
*
|
|
1998
|
+
* Present only beside a live run with no pane, and cleared the moment one is
|
|
1999
|
+
* established. This is what makes Herdr unavailability a *named* degraded state
|
|
2000
|
+
* rather than an absence: `status` reads it, so a fleet running blind says so
|
|
2001
|
+
* instead of looking identical to a fleet with no workers.
|
|
2002
|
+
*/
|
|
2003
|
+
paneUnavailable?: string;
|
|
2004
|
+
/**
|
|
2005
|
+
* Cumulative output and reasoning tokens for this run (#518).
|
|
2006
|
+
*
|
|
2007
|
+
* Both are read off the same `usage` block the spend already comes from, so
|
|
2008
|
+
* they cost no extra provider call. Their ratio is the fact that explains a run
|
|
2009
|
+
* reading `24/180 turns $0.05` after 43 minutes: 96% of its output was
|
|
2010
|
+
* reasoning. Absent rather than zero when a provider reports no token block —
|
|
2011
|
+
* a share computed from a fabricated zero is worse than no share.
|
|
2012
|
+
*/
|
|
2013
|
+
outputTokens?: number;
|
|
2014
|
+
reasoningTokens?: number;
|
|
1687
2015
|
/** The run id of the orphan-clean attempt whose session this row resumes
|
|
1688
2016
|
* (#536, #567). Set only when the dispatch-time resume verdict fired;
|
|
1689
2017
|
* absent means a fresh dispatch, never "resumed from unknown". Readable
|
|
@@ -1796,6 +2124,25 @@ export interface TurnOverride {
|
|
|
1796
2124
|
setAt: number;
|
|
1797
2125
|
}
|
|
1798
2126
|
|
|
2127
|
+
/**
|
|
2128
|
+
* The one-shot model escalation a run chain has already spent (#807).
|
|
2129
|
+
*
|
|
2130
|
+
* Written before the continuation it authorises is queued, and never removed:
|
|
2131
|
+
* its presence is both "this chain dispatches on the stronger tier" and "this
|
|
2132
|
+
* chain may never escalate again", so a restart mid-recovery cannot hand the
|
|
2133
|
+
* same chain a second tier.
|
|
2134
|
+
*/
|
|
2135
|
+
export interface ModelEscalation {
|
|
2136
|
+
/** The opaque selector the chain escalated to — the project's
|
|
2137
|
+
* `workerEscalationModel` as configured when the marker was claimed. */
|
|
2138
|
+
model: string;
|
|
2139
|
+
/** The spinning-cap class that bought the escalation. */
|
|
2140
|
+
failureClass: FailureClass;
|
|
2141
|
+
/** The run whose settlement claimed it. */
|
|
2142
|
+
runId: string;
|
|
2143
|
+
at: number;
|
|
2144
|
+
}
|
|
2145
|
+
|
|
1799
2146
|
export type AdmissionHoldReason =
|
|
1800
2147
|
| "capacity"
|
|
1801
2148
|
| "issue-active"
|
|
@@ -1815,7 +2162,20 @@ export type AdmissionHoldReason =
|
|
|
1815
2162
|
| "open-pr"
|
|
1816
2163
|
| "unsalvaged-wip"
|
|
1817
2164
|
| "daily-spend-cap"
|
|
2165
|
+
/** The candidate's own per-run spend reservation does not fit in what is
|
|
2166
|
+
* left of the daily budget once spend-to-date and every live run's
|
|
2167
|
+
* reservation are counted (#851). Distinct from `daily-spend-cap`, which is
|
|
2168
|
+
* the fleet-stopping "today is already spent" verdict: this one holds a
|
|
2169
|
+
* candidate while admitted work is still running, and clears by itself as
|
|
2170
|
+
* those runs settle for less than they reserved. */
|
|
2171
|
+
| "spend-reservation"
|
|
1818
2172
|
| "plan-usage-cap"
|
|
2173
|
+
/** A provider this project requires to bill to its subscription is currently
|
|
2174
|
+
* resolving to something else, or could not be verified (#852). Fleet-wide
|
|
2175
|
+
* like `plan-usage-cap`, and self-clearing: the operator re-authenticates and
|
|
2176
|
+
* the next pass admits. The hold's detail carries which provider, what it
|
|
2177
|
+
* resolves to, and the remediation. */
|
|
2178
|
+
| "credential-class"
|
|
1819
2179
|
| "shutting-down"
|
|
1820
2180
|
| "stale-base"
|
|
1821
2181
|
/** A ready issue whose lifecycle state label is residual: its newest run is
|
|
@@ -2042,6 +2402,42 @@ export interface ReportEnqueue {
|
|
|
2042
2402
|
* stay first, so a busy day drains deterministically over later digests. */
|
|
2043
2403
|
export const DIGEST_BACKLOG_LIMIT = 20;
|
|
2044
2404
|
|
|
2405
|
+
/**
|
|
2406
|
+
* The three install identities one host carried at one moment (#919).
|
|
2407
|
+
*
|
|
2408
|
+
* Recorded by the daemon's dispatch pass rather than probed at render time:
|
|
2409
|
+
* reading them costs three subprocesses (`omp-conductor --version`, `omp plugin
|
|
2410
|
+
* list --json`, `herdr … plugin list`), and calling that from the tick took the
|
|
2411
|
+
* tick suite from 8.4s to 83.4s — three spawns every fifteen minutes, forever,
|
|
2412
|
+
* to answer a question whose answer changes only when someone installs
|
|
2413
|
+
* something. So the surfaces that an operator and an agent actually read
|
|
2414
|
+
* consult this row and spawn nothing.
|
|
2415
|
+
*
|
|
2416
|
+
* A missing `ompVersion` or `herdrSource` is "this host does not carry that
|
|
2417
|
+
* surface", which `doctor` reports with full nuance. Only a proven
|
|
2418
|
+
* disagreement — two different releases live at once — is a fault the cheap
|
|
2419
|
+
* surfaces name.
|
|
2420
|
+
*/
|
|
2421
|
+
export interface InstallSurfaceObservation {
|
|
2422
|
+
/** When the daemon read the surfaces. */
|
|
2423
|
+
at: number;
|
|
2424
|
+
cliVersion: string;
|
|
2425
|
+
ompVersion?: string;
|
|
2426
|
+
herdrSource?: string;
|
|
2427
|
+
/**
|
|
2428
|
+
* The `omp-telegram` peer's three versions (#961), each absent when that
|
|
2429
|
+
* surface did not answer.
|
|
2430
|
+
*
|
|
2431
|
+
* Absent is not agreement. A pass with no registry access records the two it
|
|
2432
|
+
* could read and leaves `telegramPublished` off, and every renderer must show
|
|
2433
|
+
* that as unverified — the whole point being that a stale plugin silently
|
|
2434
|
+
* broke a contract the brief mandates, and "no finding" is what let it.
|
|
2435
|
+
*/
|
|
2436
|
+
telegramInstalled?: string;
|
|
2437
|
+
telegramDaemon?: string;
|
|
2438
|
+
telegramPublished?: string;
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2045
2441
|
/** One ordinary material outcome waiting for a rendered digest (#274). */
|
|
2046
2442
|
export interface MaterialEvent {
|
|
2047
2443
|
id: string;
|
|
@@ -2196,6 +2592,21 @@ export interface DecisionRecord {
|
|
|
2196
2592
|
blocks?: string;
|
|
2197
2593
|
askedAt: number;
|
|
2198
2594
|
expiresAt: number;
|
|
2595
|
+
/**
|
|
2596
|
+
* The questionnaire this row belongs to (#947): N rows written in one
|
|
2597
|
+
* transaction, delivered as one message, resolved independently. Absent on
|
|
2598
|
+
* every ordinary single ask, which is why the column is nullable rather than
|
|
2599
|
+
* defaulted — a historical row was never part of a group and must not read as
|
|
2600
|
+
* a group of one.
|
|
2601
|
+
*/
|
|
2602
|
+
groupId?: string;
|
|
2603
|
+
/**
|
|
2604
|
+
* The issue being specced when this question was asked (#947). A
|
|
2605
|
+
* questionnaire's whole point is that its answers are the spec's provenance,
|
|
2606
|
+
* so a later reader can see why a slice is shaped the way it is instead of
|
|
2607
|
+
* re-litigating it; a group without one is refused at the store boundary.
|
|
2608
|
+
*/
|
|
2609
|
+
specIssue?: number;
|
|
2199
2610
|
/** Raw machine-checkable precondition, e.g. `pr-merged:<url>`. Stored as
|
|
2200
2611
|
* written and parsed on read, so a grammar this build does not know is a row
|
|
2201
2612
|
* that still lists rather than a row that fails to load. */
|
|
@@ -2226,6 +2637,10 @@ export interface DecisionDraft {
|
|
|
2226
2637
|
kind?: DecisionKind;
|
|
2227
2638
|
blocks?: string;
|
|
2228
2639
|
condition?: string;
|
|
2640
|
+
/** Set by {@link Store.createDecisionGroup}; never by a single ask (#947). */
|
|
2641
|
+
groupId?: string;
|
|
2642
|
+
/** The issue this question specs out (#947). Required inside a group. */
|
|
2643
|
+
specIssue?: number;
|
|
2229
2644
|
at: number;
|
|
2230
2645
|
}
|
|
2231
2646
|
|
|
@@ -2281,6 +2696,19 @@ export interface Store {
|
|
|
2281
2696
|
activeRuns(project: string): RunRecord[];
|
|
2282
2697
|
/** Runs backed by a worker process — what capacity counts. Subset of {@link Store.activeRuns}. */
|
|
2283
2698
|
liveRuns(project: string): RunRecord[];
|
|
2699
|
+
/**
|
|
2700
|
+
* Runs holding an **active mutation lease** (#899, #925): something is
|
|
2701
|
+
* writing through them right now — a live worker, or a review revision the
|
|
2702
|
+
* daemon has dispatched and that has not settled. Narrower than
|
|
2703
|
+
* {@link Store.activeRuns}, whose worker-free `pushed-*` rows own an issue
|
|
2704
|
+
* and an artifact but no longer own their files.
|
|
2705
|
+
*
|
|
2706
|
+
* This is the one answer to "may this file be written", and it is a single
|
|
2707
|
+
* query because two independent readers ask it — admission's file-lane gate
|
|
2708
|
+
* and `conductor_pr_recover`'s fail-closed lane check — and a lease rule that
|
|
2709
|
+
* disagreed between them would be the exact bug both exist to prevent.
|
|
2710
|
+
*/
|
|
2711
|
+
leasedRuns(project: string): RunRecord[];
|
|
2284
2712
|
/** Failed/killed/orphaned rows whose retained tree has not been reaped. */
|
|
2285
2713
|
retainedRuns(project: string): RunRecord[];
|
|
2286
2714
|
/** Newest attempt per issue for the live board. Non-merged work remains
|
|
@@ -2325,11 +2753,107 @@ export interface Store {
|
|
|
2325
2753
|
* marker) so the next dispatch pass wakes it again — the reopen half of
|
|
2326
2754
|
* restart recovery, paired with restoring its run to `pushed-green`. */
|
|
2327
2755
|
requeueReviewRevision(id: string): void;
|
|
2756
|
+
/** Return one round to the pending set after an infrastructure kill, and
|
|
2757
|
+
* count it (#903), or report the bound spent. One transaction: the count is
|
|
2758
|
+
* read and written with the requeue, so two daemons cannot both spend the
|
|
2759
|
+
* same retry. `max` is {@link REVIEW_ROUND_INFRA_MAX_RETRIES}, passed in so
|
|
2760
|
+
* the bound stays a caller's policy rather than a number buried in SQL. A
|
|
2761
|
+
* settled row is never revived — `exhausted` is the honest answer for one. */
|
|
2762
|
+
retryReviewRevisionAfterInfra(id: string, max: number): ReviewRevisionRetry;
|
|
2328
2763
|
/** Atomically take a settled green run out of reviewable state:
|
|
2329
2764
|
* `pushed-green` → `running`, and only from `pushed-green`. False when the
|
|
2330
2765
|
* row already moved (a prior revision was dispatched, the PR settled), so
|
|
2331
2766
|
* two concurrent dispatchers cannot both wake one run. */
|
|
2332
2767
|
claimRunForReview(runId: string): boolean;
|
|
2768
|
+
/** Every review-revision row that blocks merging one PR at one exact head
|
|
2769
|
+
* (#888): rows not yet settled (queued, or dispatched and crashed
|
|
2770
|
+
* mid-review) and rows settled `failed` at that head, oldest first.
|
|
2771
|
+
* Keyed by project + PR + head — never by a label or eventually consistent
|
|
2772
|
+
* issue search — so a finding tied only to an older head never poisons a
|
|
2773
|
+
* corrected new head. A row is excluded when a {@link ReviewClearance} for
|
|
2774
|
+
* the same project + PR + head was recorded at or after that row's own last
|
|
2775
|
+
* activity (#913) — the clearance is the one reachable disposition at an
|
|
2776
|
+
* unchanged head, and the timestamp bound keeps it from blessing findings
|
|
2777
|
+
* recorded after it. */
|
|
2778
|
+
mergeBlockingReviews(project: string, prUrl: string, headSha: string): ReviewRevisionRecord[];
|
|
2779
|
+
/** Append one review clearance for an exact head (#913). Append-only: a
|
|
2780
|
+
* second clearance at the same head is another audit row, never an update. */
|
|
2781
|
+
recordReviewClearance(clearance: {
|
|
2782
|
+
project: string;
|
|
2783
|
+
prUrl: string;
|
|
2784
|
+
headSha: string;
|
|
2785
|
+
by: string;
|
|
2786
|
+
reason: string;
|
|
2787
|
+
at?: number;
|
|
2788
|
+
}): ReviewClearance;
|
|
2789
|
+
/** Clearances recorded for one PR at one exact head, newest first (#913).
|
|
2790
|
+
* Head comparison is case-insensitive, exactly as the merge gate's. */
|
|
2791
|
+
reviewClearances(project: string, prUrl: string, headSha: string): ReviewClearance[];
|
|
2792
|
+
/**
|
|
2793
|
+
* Admit one terminal review-ceiling adjudication for an exact PR head (#874),
|
|
2794
|
+
* idempotently. The second caller for the same head is handed the existing
|
|
2795
|
+
* row (`existing`), and a non-terminal adjudication on the same PR at a
|
|
2796
|
+
* DIFFERENT head refuses (`in-flight-different-head`) — the PR moved, and two
|
|
2797
|
+
* live adjudications on one PR is the duplicate this lifecycle prevents.
|
|
2798
|
+
*
|
|
2799
|
+
* Never consumes a review round: this is a separate table from
|
|
2800
|
+
* `review_revisions` precisely so a ceiling that is already exhausted is not
|
|
2801
|
+
* charged again.
|
|
2802
|
+
*/
|
|
2803
|
+
openReviewAdjudication(draft: {
|
|
2804
|
+
project: string;
|
|
2805
|
+
issue: number;
|
|
2806
|
+
prUrl: string;
|
|
2807
|
+
headSha: string;
|
|
2808
|
+
role: string;
|
|
2809
|
+
requestedAt?: number;
|
|
2810
|
+
/** The findings that provoked the escalation, when a caller has them. */
|
|
2811
|
+
findings?: string;
|
|
2812
|
+
}): ReviewAdjudicationAdmission;
|
|
2813
|
+
/** `pending` → `running`, recording what the role actually resolved to at
|
|
2814
|
+
* launch. The transition is the WHERE clause, so two dispatchers racing one
|
|
2815
|
+
* row cannot both launch: the loser changes nothing and reads `false`. */
|
|
2816
|
+
markReviewAdjudicationRunning(
|
|
2817
|
+
id: string,
|
|
2818
|
+
provenance: ReviewAdjudicationProvenance,
|
|
2819
|
+
at?: number,
|
|
2820
|
+
): boolean;
|
|
2821
|
+
/** Settle into a terminal state, once, with the evidence that produced it.
|
|
2822
|
+
* A non-terminal target state or an already-settled row changes nothing and
|
|
2823
|
+
* reads `false`, so an invalid transition preserves the diagnostic the row
|
|
2824
|
+
* already carried instead of overwriting it. */
|
|
2825
|
+
settleReviewAdjudication(
|
|
2826
|
+
id: string,
|
|
2827
|
+
state: ReviewAdjudicationState,
|
|
2828
|
+
evidence?: string,
|
|
2829
|
+
at?: number,
|
|
2830
|
+
): boolean;
|
|
2831
|
+
/** Record what was done about a verdict (#876's half), kept separate from the
|
|
2832
|
+
* verdict so the two are never conflated. */
|
|
2833
|
+
recordReviewAdjudicationDisposition(id: string, disposition: string): boolean;
|
|
2834
|
+
/** One adjudication by id. */
|
|
2835
|
+
reviewAdjudication(id: string): ReviewAdjudicationRecord | undefined;
|
|
2836
|
+
/** The adjudication for one exact PR head, if any. Case-insensitive on the
|
|
2837
|
+
* SHA, exactly as the merge gate and the clearance lookup are. */
|
|
2838
|
+
reviewAdjudicationForHead(
|
|
2839
|
+
project: string,
|
|
2840
|
+
prUrl: string,
|
|
2841
|
+
headSha: string,
|
|
2842
|
+
): ReviewAdjudicationRecord | undefined;
|
|
2843
|
+
/** Every non-terminal adjudication for a project, oldest first: what a daemon
|
|
2844
|
+
* restart re-reads instead of launching a second adjudicator, and what status
|
|
2845
|
+
* projects as pending/running. */
|
|
2846
|
+
openReviewAdjudications(project: string): ReviewAdjudicationRecord[];
|
|
2847
|
+
/** Every adjudication recorded for one PR, newest first. */
|
|
2848
|
+
reviewAdjudicationsForPr(project: string, prUrl: string): ReviewAdjudicationRecord[];
|
|
2849
|
+
/**
|
|
2850
|
+
* Every review round recorded for one PR, oldest first (#932): the history an
|
|
2851
|
+
* adjudication is shown. Keyed by PR rather than by run so a continuation that
|
|
2852
|
+
* inherited the PR cannot hide its predecessor's findings — and a finding
|
|
2853
|
+
* without its settled outcome reads as an outstanding complaint even when the
|
|
2854
|
+
* worker fixed it, so both travel together.
|
|
2855
|
+
*/
|
|
2856
|
+
reviewRevisionsForPr(project: string, prUrl: string): ReviewRevisionRecord[];
|
|
2333
2857
|
/** Merged rows whose post-merge workflow verdict is still pending, oldest first. */
|
|
2334
2858
|
runsNeedingBaseCheck(project: string, limit?: number): RunRecord[];
|
|
2335
2859
|
/** Replace the current live-head health row for one routed repository. */
|
|
@@ -2370,6 +2894,44 @@ export interface Store {
|
|
|
2370
2894
|
reason: string,
|
|
2371
2895
|
at?: number,
|
|
2372
2896
|
): boolean;
|
|
2897
|
+
/** The project's active release composition, if one is declared (#850). */
|
|
2898
|
+
activeReleaseComposition(project: string): ReleaseComposition | undefined;
|
|
2899
|
+
/**
|
|
2900
|
+
* Declare the active release composition for a project (#850). One at a
|
|
2901
|
+
* time per project: returns the still-active conflicting composition
|
|
2902
|
+
* unchanged when one exists (the caller refuses), `undefined` when the
|
|
2903
|
+
* declaration took. Replaces only already-closed rows.
|
|
2904
|
+
*/
|
|
2905
|
+
declareReleaseComposition(
|
|
2906
|
+
project: string,
|
|
2907
|
+
composition: {
|
|
2908
|
+
campaign: string;
|
|
2909
|
+
allowedPrUrls: string[];
|
|
2910
|
+
declaredBy?: string;
|
|
2911
|
+
declaredAt?: number;
|
|
2912
|
+
},
|
|
2913
|
+
): ReleaseComposition | undefined;
|
|
2914
|
+
/**
|
|
2915
|
+
* Retire the active composition as completed (#850). Records who closed it
|
|
2916
|
+
* and why on the row itself. Returns true only when an active composition
|
|
2917
|
+
* was actually retired, so the CLI event fires once.
|
|
2918
|
+
*/
|
|
2919
|
+
completeReleaseComposition(project: string, by: string, reason: string, at?: number): boolean;
|
|
2920
|
+
/** Retire the active composition as cancelled (#850), same contract as completion. */
|
|
2921
|
+
cancelReleaseComposition(project: string, by: string, reason: string, at?: number): boolean;
|
|
2922
|
+
/** Append one operator override admitting `prUrl` into `campaign` (#850). */
|
|
2923
|
+
grantReleaseCompositionOverride(
|
|
2924
|
+
project: string,
|
|
2925
|
+
override: { campaign: string; prUrl: string; by: string; reason: string; at?: number },
|
|
2926
|
+
): ReleaseCompositionOverride;
|
|
2927
|
+
/** The newest live override for one PR in one campaign, if recorded (#850). */
|
|
2928
|
+
releaseCompositionOverride(
|
|
2929
|
+
project: string,
|
|
2930
|
+
campaign: string,
|
|
2931
|
+
prUrl: string,
|
|
2932
|
+
): ReleaseCompositionOverride | undefined;
|
|
2933
|
+
/** Every override recorded for one campaign, newest first (#850). */
|
|
2934
|
+
releaseCompositionOverrides(project: string, campaign: string): ReleaseCompositionOverride[];
|
|
2373
2935
|
/** Newest attempt per issue that preserved work or failed to, so `status`
|
|
2374
2936
|
* can name every WIP tip a re-claim would build on and every tree that is
|
|
2375
2937
|
* still the only copy. */
|
|
@@ -2396,6 +2958,29 @@ export interface Store {
|
|
|
2396
2958
|
/** Every attempt for one issue, oldest first, so an escalation can group the
|
|
2397
2959
|
* continuation budget by failure class (#439). */
|
|
2398
2960
|
runsForIssue(project: string, issue: number): RunRecord[];
|
|
2961
|
+
/**
|
|
2962
|
+
* Spends the single model escalation an issue chain may buy (#807): the
|
|
2963
|
+
* one-shot marker, the `swapFrom` → `queueLabel` hand-back that the
|
|
2964
|
+
* escalation authorises, and the settling run's recovery stamp, all in one
|
|
2965
|
+
* transaction. `true` when this call spent it, `false` when the chain had
|
|
2966
|
+
* already escalated and nothing was written — the second-cap decomposition
|
|
2967
|
+
* verdict. Atomic on purpose: a restart must never find a chain that reads
|
|
2968
|
+
* as escalated with the continuation it promised never queued. Throws when
|
|
2969
|
+
* `runId` names no row.
|
|
2970
|
+
*/
|
|
2971
|
+
escalateModel(escalation: {
|
|
2972
|
+
project: string;
|
|
2973
|
+
issue: number;
|
|
2974
|
+
model: string;
|
|
2975
|
+
failureClass: FailureClass;
|
|
2976
|
+
runId: string;
|
|
2977
|
+
swapFrom: string;
|
|
2978
|
+
queueLabel: string;
|
|
2979
|
+
at?: number;
|
|
2980
|
+
}): boolean;
|
|
2981
|
+
/** The escalation this chain has already spent; `undefined` for every chain
|
|
2982
|
+
* that never bought one, which is what dispatch reads for a fresh issue. */
|
|
2983
|
+
modelEscalation(project: string, issue: number): ModelEscalation | undefined;
|
|
2399
2984
|
/** Persist a one-shot ceiling and append the operator action to its audit ledger. */
|
|
2400
2985
|
setTurnOverride(project: string, issue: number, maxTurns: number, setAt?: number): void;
|
|
2401
2986
|
turnOverride(project: string, issue: number): number | undefined;
|
|
@@ -2409,11 +2994,26 @@ export interface Store {
|
|
|
2409
2994
|
): TurnOverride[];
|
|
2410
2995
|
runsStartedSince(project: string, sinceEpochMs: number): number;
|
|
2411
2996
|
spendSince(project: string, sinceEpochMs: number): number;
|
|
2997
|
+
/** Total per-run allowance currently reserved by LIVE runs (#851). What
|
|
2998
|
+
* admission must subtract from the daily budget on top of spend-to-date:
|
|
2999
|
+
* a run that has spent little has still committed its whole allowance, and
|
|
3000
|
+
* reading the sum off the live rows means a settle releases the remainder
|
|
3001
|
+
* and a restart re-reads it rather than losing it. */
|
|
3002
|
+
reservedSpendUsd(project: string): number;
|
|
2412
3003
|
/** The rows one stats window needs (#282): every run settling at or after
|
|
2413
3004
|
* `sinceEpochMs`, plus the full attempt chain of each issue whose merge
|
|
2414
3005
|
* settled there (so continuation chains collapse into one journey), ordered
|
|
2415
3006
|
* by issue then startedAt. Read-only. */
|
|
2416
3007
|
statsRuns(project: string, sinceEpochMs: number): RunRecord[];
|
|
3008
|
+
/**
|
|
3009
|
+
* The newest terminal runs, as `{ turns, spendUsd }`, for judging whether the
|
|
3010
|
+
* spend cap's input is still arriving (#970).
|
|
3011
|
+
*
|
|
3012
|
+
* Bounded by `limit` rows, not by `limit` *working* runs: the judgement
|
|
3013
|
+
* discards 0-turn rows, so the caller over-samples and lets the shared
|
|
3014
|
+
* predicate pick the window. Read-only.
|
|
3015
|
+
*/
|
|
3016
|
+
recentSpendSamples(project: string, limit: number): { turns: number; spendUsd: number }[];
|
|
2417
3017
|
/** Total tracked `gh` calls between two UTC day keys, inclusive (#198). */
|
|
2418
3018
|
ghCallsBetween(sinceDay: string, untilDay: string): number;
|
|
2419
3019
|
/** Idempotence guard so a retry loop cannot page a human repeatedly for the
|
|
@@ -2443,6 +3043,14 @@ export interface Store {
|
|
|
2443
3043
|
categories?: readonly InterruptCategory[],
|
|
2444
3044
|
urgent?: boolean,
|
|
2445
3045
|
): HeldNotice[];
|
|
3046
|
+
/** Record the install identities this host carried on this pass (#919).
|
|
3047
|
+
* One row, replaced: the question is "what is installed now", and a history
|
|
3048
|
+
* of installs is what the upgrade journal already keeps. */
|
|
3049
|
+
recordInstallSurfaces(observation: InstallSurfaceObservation): void;
|
|
3050
|
+
/** The newest recorded install-surface observation, or `undefined` when no
|
|
3051
|
+
* pass has read them yet — which the surfaces must render as "not observed",
|
|
3052
|
+
* never as agreement (#919). */
|
|
3053
|
+
installSurfaces(): InstallSurfaceObservation | undefined;
|
|
2446
3054
|
/** Persist one ordinary material outcome without sending it. */
|
|
2447
3055
|
recordMaterialEvent(event: MaterialEventDraft): MaterialEvent;
|
|
2448
3056
|
getMaterialEvent(id: string): MaterialEvent | undefined;
|
|
@@ -2477,6 +3085,21 @@ export interface Store {
|
|
|
2477
3085
|
project: string,
|
|
2478
3086
|
holds: readonly { issue: number; reason: AdmissionHoldReason; detail?: string }[],
|
|
2479
3087
|
): void;
|
|
3088
|
+
/**
|
|
3089
|
+
* Delete every grooming row whose issue is not in a **complete** open-issue
|
|
3090
|
+
* snapshot, returning the issues retired (#964).
|
|
3091
|
+
*
|
|
3092
|
+
* A verdict describes an open issue: "promotable" means "promote this", and a
|
|
3093
|
+
* closed issue cannot be promoted. Nothing retired one before, so the rows
|
|
3094
|
+
* accumulated and `status` reported a groomed backlog that did not exist —
|
|
3095
|
+
* measured at 64 of 65 rows, including all 26 promotable ones.
|
|
3096
|
+
*
|
|
3097
|
+
* The completeness of the snapshot is the caller's contract, not this
|
|
3098
|
+
* method's: pass the result of `Tracker.listOpenIssues` (which throws rather
|
|
3099
|
+
* than returning a short list), never a label-filtered read. A partial
|
|
3100
|
+
* snapshot here deletes verdicts nobody re-derived.
|
|
3101
|
+
*/
|
|
3102
|
+
retireGroomingNotOpen(project: string, openIssues: readonly number[]): number[];
|
|
2480
3103
|
/** Count and age source for status and digest prompt bounds. */
|
|
2481
3104
|
digestBacklog(project: string): DigestBacklog;
|
|
2482
3105
|
/** Add one bounded observation to the per-day friction rollup. */
|
|
@@ -2644,8 +3267,24 @@ export interface Store {
|
|
|
2644
3267
|
* into recovery. False when the row was no longer `ci-deterministic`: a
|
|
2645
3268
|
* no-op, exactly what a second repair pass must be. */
|
|
2646
3269
|
reclassifyInfra(id: string): boolean;
|
|
2647
|
-
/**
|
|
3270
|
+
/**
|
|
3271
|
+
* Rows still **awaiting a recovery the sweep will take**, per class (#132).
|
|
3272
|
+
*
|
|
3273
|
+
* Excludes the recorded-only actions (`none`, `hold`), which never stamp
|
|
3274
|
+
* `recoveredAt` by design — see {@link Store.recordedOnlyClassCounts}. Empty
|
|
3275
|
+
* when nothing is waiting, which is the healthy state and reads as one.
|
|
3276
|
+
*/
|
|
2648
3277
|
failureClassCounts(project: string): { cls: FailureClass; n: number }[];
|
|
3278
|
+
/**
|
|
3279
|
+
* Rows classified but deliberately not acted on, per class (#132): the
|
|
3280
|
+
* `none`/`hold` actions.
|
|
3281
|
+
*
|
|
3282
|
+
* A separate reader because these are a *signal*, not a backlog — 44
|
|
3283
|
+
* `returned-for-revision` rows say something real about review outcomes, and
|
|
3284
|
+
* nothing about work that is stuck. Counting them as unrecovered is what hid
|
|
3285
|
+
* every actionable class behind a monotonically growing tally.
|
|
3286
|
+
*/
|
|
3287
|
+
recordedOnlyClassCounts(project: string): { cls: FailureClass; n: number }[];
|
|
2649
3288
|
/** Rows whose recovery ran at or after `since`, newest first — the tick's
|
|
2650
3289
|
* "auto-recovered since last tick" line, and its count. */
|
|
2651
3290
|
recoveredSince(project: string, since: number): RunRecord[];
|
|
@@ -2655,6 +3294,17 @@ export interface Store {
|
|
|
2655
3294
|
* remembering things.
|
|
2656
3295
|
*/
|
|
2657
3296
|
createDecision(draft: DecisionDraft): DecisionRecord;
|
|
3297
|
+
/**
|
|
3298
|
+
* Record one spec-out questionnaire (#947): every item durable in a single
|
|
3299
|
+
* transaction, before its single delivery, sharing a minted group id and the
|
|
3300
|
+
* issue being specced. Refuses an empty batch and a non-issue spec binding —
|
|
3301
|
+
* a questionnaire with nothing waiting on it, or with no spec, is a caller
|
|
3302
|
+
* bug rather than a degenerate case worth supporting.
|
|
3303
|
+
*/
|
|
3304
|
+
createDecisionGroup(drafts: readonly DecisionDraft[], specIssue: number): DecisionRecord[];
|
|
3305
|
+
/** One questionnaire's rows in asked order, whatever their states — the
|
|
3306
|
+
* per-item view a digest renders and a timeout pass resolves against. */
|
|
3307
|
+
decisionGroup(groupId: string): DecisionRecord[];
|
|
2658
3308
|
/** Everything still owed an answer, oldest first — what a tick digest reads. */
|
|
2659
3309
|
openDecisions(project: string): DecisionRecord[];
|
|
2660
3310
|
/** One row by id, whatever its state — what a bounded ask polls while waiting. */
|
|
@@ -2889,6 +3539,10 @@ export const DEFAULT_CAPS: Caps = {
|
|
|
2889
3539
|
maxConcurrentWorkers: 2,
|
|
2890
3540
|
maxConcurrentWorkersPerRepo: 1,
|
|
2891
3541
|
dailySpendUsd: 25,
|
|
3542
|
+
// Derived as `dailySpendUsd / maxConcurrentWorkers` unless an operator names
|
|
3543
|
+
// a per-run allowance: bounded overshoot without changing how many workers
|
|
3544
|
+
// the fleet runs (#851).
|
|
3545
|
+
maxRunSpendUsd: null,
|
|
2892
3546
|
// Off unless an operator names a window. A default threshold would need a
|
|
2893
3547
|
// default window id, and guessing which allowance a fleet lives on is how a
|
|
2894
3548
|
// guard silently watches the wrong meter (#110).
|
|
@@ -2923,6 +3577,11 @@ export const VERB_NAMES = [
|
|
|
2923
3577
|
* OMP session on the existing branch and PR. No close, no reopen, no
|
|
2924
3578
|
* redispatch. */
|
|
2925
3579
|
"conductor_pr_review",
|
|
3580
|
+
/** Record that the durable review findings standing at one exact head are
|
|
3581
|
+
* settled, so the #888 merge gate stops refusing that head (#913). A
|
|
3582
|
+
* separate verb, never an argument on the merge: the disposition is its own
|
|
3583
|
+
* recorded act, and it clears only rows older than itself. */
|
|
3584
|
+
"conductor_pr_review_clear",
|
|
2926
3585
|
/** The orchestrator-only, settled-run recovery operation (#806): open (or
|
|
2927
3586
|
* adopt) the missing pull request for a terminal run whose stored branch
|
|
2928
3587
|
* exists at its exact recorded head. The recovery proves everything a
|
|
@@ -3024,6 +3683,15 @@ export const VERB_REFUSALS = [
|
|
|
3024
3683
|
/** The routed repository a terminal run recorded no longer has an entry in
|
|
3025
3684
|
* this project's routing, so the daemon cannot create a PR for it. */
|
|
3026
3685
|
"recovery-repo-unrouted",
|
|
3686
|
+
/** A live mutation lease already holds a file the preserved branch carries
|
|
3687
|
+
* (#925): publishing it now would put a frozen diff into review beside a
|
|
3688
|
+
* worker still rewriting the same file. Retryable — the lease releases when
|
|
3689
|
+
* that worker settles. */
|
|
3690
|
+
"recovery-lane-occupied",
|
|
3691
|
+
/** The lane a recovery would publish could not be read (#925). Unlike
|
|
3692
|
+
* admission's fail-open interlock, a single deliberate recovery call refuses
|
|
3693
|
+
* on "could not tell" rather than publishing on an unproven lane. */
|
|
3694
|
+
"recovery-lane-unprovable",
|
|
3027
3695
|
/** The run's durable `prUrl` is a non-URL value the tracker cannot address
|
|
3028
3696
|
* (other than the normalized `pending` settlement sentinel, #866). There is
|
|
3029
3697
|
* no PR to read and none to guess at — a corrupt record, not an unreadable
|
|
@@ -3068,6 +3736,31 @@ export const VERB_REFUSALS = [
|
|
|
3068
3736
|
* repos are unaffected.
|
|
3069
3737
|
*/
|
|
3070
3738
|
"base-red-freeze",
|
|
3739
|
+
/**
|
|
3740
|
+
* The requested merge head carries durable, unresolved review evidence
|
|
3741
|
+
* (#888): a review revision for this PR that is queued or crashed at
|
|
3742
|
+
* exactly that head, or one settled `failed` there. Green checks do not
|
|
3743
|
+
* clear it — the head must be re-reviewed with `conductor_pr_review` or
|
|
3744
|
+
* superseded by a corrected push to a different head.
|
|
3745
|
+
*/
|
|
3746
|
+
"merge-blocked-by-review",
|
|
3747
|
+
/**
|
|
3748
|
+
* A review clearance was asked for at a head where nothing blocks (#913).
|
|
3749
|
+
* A clearance is a disposition of specific evidence, so recording one "just
|
|
3750
|
+
* in case" — before a round exists, or after a corrected push moved the
|
|
3751
|
+
* head — is refused rather than stored as a standing pass.
|
|
3752
|
+
*/
|
|
3753
|
+
"review-clearance-vacuous",
|
|
3754
|
+
/**
|
|
3755
|
+
* The project has an active release composition (#850) — a declared
|
|
3756
|
+
* campaign plus the exact PRs allowed into it — and this PR is neither,
|
|
3757
|
+
* with no operator override recorded for it. Merging would smuggle
|
|
3758
|
+
* unrelated work into an assembling release. Refused before the merge lock
|
|
3759
|
+
* and any tracker call; the guard clears only by completing or cancelling
|
|
3760
|
+
* the release, or recording an explicit per-PR override through
|
|
3761
|
+
* `omp-conductor release-composition`, never through the merge reason.
|
|
3762
|
+
*/
|
|
3763
|
+
"outside-active-release",
|
|
3071
3764
|
/** The label is not in this project's own vocabulary. */
|
|
3072
3765
|
"label-not-in-vocabulary",
|
|
3073
3766
|
/** The label is a lifecycle label; those transitions stay the daemon's (#26). */
|