omp-conductor 0.18.1 → 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 +106 -41
- package/REFERENCE.md +866 -31
- package/agents/to-spec.md +6 -2
- package/package.json +1 -1
- package/schema/config.schema.json +32 -1
- package/src/admission.ts +212 -26
- package/src/arm-challenge.ts +250 -57
- package/src/ask.ts +288 -1
- package/src/briefs/orchestrator.md +27 -13
- package/src/briefs/to-spec.md +6 -2
- package/src/cli.ts +127 -2
- package/src/command-help.ts +9 -1
- package/src/command-manifest.ts +52 -8
- package/src/commands/arm.ts +6 -2
- package/src/commands/context.ts +2 -0
- package/src/commands/intake.ts +4 -19
- 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/commands/watch.ts +4 -17
- package/src/config-schema.ts +38 -6
- package/src/config.ts +103 -8
- package/src/credential-class.ts +366 -0
- package/src/daemon.ts +1368 -529
- 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/decisions.ts +19 -11
- package/src/doctor.ts +431 -148
- package/src/escalate.ts +22 -11
- package/src/failure-class.ts +59 -0
- package/src/fleet.ts +587 -230
- package/src/host.ts +6 -455
- package/src/omp-settings.ts +19 -0
- package/src/omp.ts +40 -56
- package/src/orchestrator-tick.ts +564 -121
- package/src/pause.ts +233 -0
- package/src/session-host.ts +6 -41
- package/src/settlement.ts +159 -2
- package/src/setup-answers.ts +97 -0
- package/src/setup-host.ts +343 -1160
- package/src/setup-install.ts +204 -27
- package/src/setup-wizard.ts +252 -51
- package/src/setup.ts +87 -4
- package/src/spend-telemetry.ts +117 -0
- package/src/stats.ts +35 -0
- package/src/status-render.ts +485 -19
- package/src/store.ts +1229 -55
- package/src/telegram-freshness.ts +269 -0
- package/src/to-spec.ts +50 -2
- package/src/types.ts +759 -10
- 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 +485 -11
- package/src/wake.ts +48 -0
- package/src/worker.ts +401 -14
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
|
|
@@ -638,26 +661,175 @@ export const REVIEW_MAX_ROUNDS_MAX = 6;
|
|
|
638
661
|
* for real correction cycles, and a hard stop against endless polishing. */
|
|
639
662
|
export const DEFAULT_REVIEW_MAX_ROUNDS = 3;
|
|
640
663
|
|
|
664
|
+
/**
|
|
665
|
+
* The one spelling a review policy may ask the adjudicator for: a token naming
|
|
666
|
+
* an OMP model role (`"task"`, `"worker"`, a project's own `modelRoles` key).
|
|
667
|
+
* A role name is never a provider/model — OMP owns provider selection and
|
|
668
|
+
* named model roles, and conductor stores only the role. Used by the config
|
|
669
|
+
* grammar and by the setup dialog's availability check; they must read the
|
|
670
|
+
* same shape or one of them accepts an answer the other rejects.
|
|
671
|
+
*/
|
|
672
|
+
export const REVIEW_ADJUDICATOR_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Which OMP model role carries a PR's terminal review-ceiling adjudication
|
|
676
|
+
* (#875) when no project policy names one. The deterministic migration default
|
|
677
|
+
* every existing project loads until its operator answers setup: the general
|
|
678
|
+
* OMP session role each install can launch without a provider or model being
|
|
679
|
+
* pinned here.
|
|
680
|
+
*/
|
|
681
|
+
export const DEFAULT_REVIEW_ADJUDICATOR_ROLE = "task";
|
|
682
|
+
|
|
641
683
|
/**
|
|
642
684
|
* 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
|
-
*
|
|
685
|
+
* when deciding whether a green PR is returned to its worker, the hard
|
|
686
|
+
* ceiling on how many such rounds one PR lifecycle may consume, and which OMP
|
|
687
|
+
* model role carries the final adjudication once that ceiling is reached
|
|
688
|
+
* (#875). The ceiling is enforced by the dispatch side, never left to the
|
|
689
|
+
* session's reading of the brief: at the ceiling `conductor_pr_review`
|
|
690
|
+
* refuses, the orchestrator leaves the PR open, records the unresolved
|
|
691
|
+
* findings and escalates once.
|
|
648
692
|
*/
|
|
649
693
|
export interface ReviewPolicy {
|
|
650
694
|
strictness: ReviewStrictness;
|
|
651
695
|
maxRounds: number;
|
|
696
|
+
/**
|
|
697
|
+
* The OMP model role that runs a PR's terminal review-ceiling adjudication:
|
|
698
|
+
* a named role from OMP's configured model roles (`"task"`, `"worker"`, or a
|
|
699
|
+
* project's own `modelRoles` key), in omp's model-role syntax — never a
|
|
700
|
+
* provider/model, which OMP owns. Absent in config loads as
|
|
701
|
+
* {@link DEFAULT_REVIEW_ADJUDICATOR_ROLE}, the deterministic default every
|
|
702
|
+
* existing install upgrades to.
|
|
703
|
+
*/
|
|
704
|
+
adjudicator: string;
|
|
652
705
|
}
|
|
653
706
|
|
|
654
|
-
/** The documented migration default: what a config written before the
|
|
707
|
+
/** The documented migration default: what a config written before the fields
|
|
655
708
|
* existed loads as, deterministically, until the operator answers setup. */
|
|
656
709
|
export const DEFAULT_REVIEW_POLICY: ReviewPolicy = {
|
|
657
710
|
strictness: DEFAULT_REVIEW_STRICTNESS,
|
|
658
711
|
maxRounds: DEFAULT_REVIEW_MAX_ROUNDS,
|
|
712
|
+
adjudicator: DEFAULT_REVIEW_ADJUDICATOR_ROLE,
|
|
659
713
|
};
|
|
660
714
|
|
|
715
|
+
/**
|
|
716
|
+
* The resolved launch provenance of one review-ceiling adjudication (#875):
|
|
717
|
+
* what the policy named and what the role actually resolved to when the
|
|
718
|
+
* adjudicator session launched. OMP owns named roles and exact provider
|
|
719
|
+
* selection, so this record is where a launch's real model/provider lands —
|
|
720
|
+
* the typed fields later status/ledger code reads to show what genuinely
|
|
721
|
+
* adjudicated, not what a config said it should.
|
|
722
|
+
*/
|
|
723
|
+
export interface ReviewAdjudicationProvenance {
|
|
724
|
+
/** The policy's adjudicator role, verbatim from {@link ReviewPolicy.adjudicator}. */
|
|
725
|
+
readonly role: string;
|
|
726
|
+
/** The model pattern the role resolved to at launch, in omp's model syntax. */
|
|
727
|
+
readonly model: string;
|
|
728
|
+
/** The provider OMP selected, when the harness could name one at launch. */
|
|
729
|
+
readonly provider?: string;
|
|
730
|
+
/** Epoch ms when this launch resolution was recorded. */
|
|
731
|
+
readonly resolvedAt: number;
|
|
732
|
+
}
|
|
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
|
+
|
|
661
833
|
/**
|
|
662
834
|
* One durable review-revision request (#677): the orchestrator returned a
|
|
663
835
|
* green, run-owned pull request to its worker with blocking findings, and
|
|
@@ -688,6 +860,12 @@ export interface ReviewRevisionRecord {
|
|
|
688
860
|
dispatchedAt?: number;
|
|
689
861
|
settledAt?: number;
|
|
690
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;
|
|
691
869
|
}
|
|
692
870
|
|
|
693
871
|
/**
|
|
@@ -705,6 +883,72 @@ export type ReviewRevisionEnqueue =
|
|
|
705
883
|
| { kind: "appended"; record: ReviewRevisionRecord }
|
|
706
884
|
| { kind: "refused"; block: "pending-different-head" };
|
|
707
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
|
+
|
|
708
952
|
/**
|
|
709
953
|
* A model-supplied justification: one value out of a closed set, plus prose that
|
|
710
954
|
* is written down and read by nothing that decides.
|
|
@@ -811,6 +1055,36 @@ export interface ProjectConfig {
|
|
|
811
1055
|
* when absent or unusable.
|
|
812
1056
|
*/
|
|
813
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[];
|
|
814
1088
|
/**
|
|
815
1089
|
* An opaque omp settings map layered into every worker session this project
|
|
816
1090
|
* dispatches (#537). At dispatch it is materialised verbatim to a fleet-owned
|
|
@@ -1417,9 +1691,23 @@ export type FailureClass = (typeof FAILURE_CLASSES)[number];
|
|
|
1417
1691
|
/**
|
|
1418
1692
|
* What the daemon does about a class.
|
|
1419
1693
|
*
|
|
1420
|
-
*
|
|
1421
|
-
*
|
|
1422
|
-
*
|
|
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).
|
|
1423
1711
|
*/
|
|
1424
1712
|
export const RECOVERY_ACTIONS = [
|
|
1425
1713
|
"requeue",
|
|
@@ -1479,6 +1767,48 @@ export interface BaseFreeze {
|
|
|
1479
1767
|
clearedReason?: string;
|
|
1480
1768
|
}
|
|
1481
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
|
+
|
|
1482
1812
|
/**
|
|
1483
1813
|
* Execution state is separate from the tracker's own labels on purpose: labels
|
|
1484
1814
|
* are coarse and human-editable, while the loop needs to distinguish "pushed
|
|
@@ -1599,6 +1929,18 @@ export interface RunRecord {
|
|
|
1599
1929
|
/** Effective turn ceiling for this run; operators may only raise it. */
|
|
1600
1930
|
maxTurns: number;
|
|
1601
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;
|
|
1602
1944
|
/** How many in-session HTTP 429 responses the harness retried before the run
|
|
1603
1945
|
* ended, counted by the worker from the session's `message_end` events while
|
|
1604
1946
|
* the run was live (#573). Absent means the column predates the count or no
|
|
@@ -1634,6 +1976,42 @@ export interface RunRecord {
|
|
|
1634
1976
|
autoCompactionCount?: number;
|
|
1635
1977
|
/** omp session transcript, so a human can read what the worker actually did. */
|
|
1636
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;
|
|
1637
2015
|
/** The run id of the orphan-clean attempt whose session this row resumes
|
|
1638
2016
|
* (#536, #567). Set only when the dispatch-time resume verdict fired;
|
|
1639
2017
|
* absent means a fresh dispatch, never "resumed from unknown". Readable
|
|
@@ -1746,6 +2124,25 @@ export interface TurnOverride {
|
|
|
1746
2124
|
setAt: number;
|
|
1747
2125
|
}
|
|
1748
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
|
+
|
|
1749
2146
|
export type AdmissionHoldReason =
|
|
1750
2147
|
| "capacity"
|
|
1751
2148
|
| "issue-active"
|
|
@@ -1765,7 +2162,20 @@ export type AdmissionHoldReason =
|
|
|
1765
2162
|
| "open-pr"
|
|
1766
2163
|
| "unsalvaged-wip"
|
|
1767
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"
|
|
1768
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"
|
|
1769
2179
|
| "shutting-down"
|
|
1770
2180
|
| "stale-base"
|
|
1771
2181
|
/** A ready issue whose lifecycle state label is residual: its newest run is
|
|
@@ -1992,6 +2402,42 @@ export interface ReportEnqueue {
|
|
|
1992
2402
|
* stay first, so a busy day drains deterministically over later digests. */
|
|
1993
2403
|
export const DIGEST_BACKLOG_LIMIT = 20;
|
|
1994
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
|
+
|
|
1995
2441
|
/** One ordinary material outcome waiting for a rendered digest (#274). */
|
|
1996
2442
|
export interface MaterialEvent {
|
|
1997
2443
|
id: string;
|
|
@@ -2146,6 +2592,21 @@ export interface DecisionRecord {
|
|
|
2146
2592
|
blocks?: string;
|
|
2147
2593
|
askedAt: number;
|
|
2148
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;
|
|
2149
2610
|
/** Raw machine-checkable precondition, e.g. `pr-merged:<url>`. Stored as
|
|
2150
2611
|
* written and parsed on read, so a grammar this build does not know is a row
|
|
2151
2612
|
* that still lists rather than a row that fails to load. */
|
|
@@ -2176,6 +2637,10 @@ export interface DecisionDraft {
|
|
|
2176
2637
|
kind?: DecisionKind;
|
|
2177
2638
|
blocks?: string;
|
|
2178
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;
|
|
2179
2644
|
at: number;
|
|
2180
2645
|
}
|
|
2181
2646
|
|
|
@@ -2231,6 +2696,19 @@ export interface Store {
|
|
|
2231
2696
|
activeRuns(project: string): RunRecord[];
|
|
2232
2697
|
/** Runs backed by a worker process — what capacity counts. Subset of {@link Store.activeRuns}. */
|
|
2233
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[];
|
|
2234
2712
|
/** Failed/killed/orphaned rows whose retained tree has not been reaped. */
|
|
2235
2713
|
retainedRuns(project: string): RunRecord[];
|
|
2236
2714
|
/** Newest attempt per issue for the live board. Non-merged work remains
|
|
@@ -2275,11 +2753,107 @@ export interface Store {
|
|
|
2275
2753
|
* marker) so the next dispatch pass wakes it again — the reopen half of
|
|
2276
2754
|
* restart recovery, paired with restoring its run to `pushed-green`. */
|
|
2277
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;
|
|
2278
2763
|
/** Atomically take a settled green run out of reviewable state:
|
|
2279
2764
|
* `pushed-green` → `running`, and only from `pushed-green`. False when the
|
|
2280
2765
|
* row already moved (a prior revision was dispatched, the PR settled), so
|
|
2281
2766
|
* two concurrent dispatchers cannot both wake one run. */
|
|
2282
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[];
|
|
2283
2857
|
/** Merged rows whose post-merge workflow verdict is still pending, oldest first. */
|
|
2284
2858
|
runsNeedingBaseCheck(project: string, limit?: number): RunRecord[];
|
|
2285
2859
|
/** Replace the current live-head health row for one routed repository. */
|
|
@@ -2320,6 +2894,44 @@ export interface Store {
|
|
|
2320
2894
|
reason: string,
|
|
2321
2895
|
at?: number,
|
|
2322
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[];
|
|
2323
2935
|
/** Newest attempt per issue that preserved work or failed to, so `status`
|
|
2324
2936
|
* can name every WIP tip a re-claim would build on and every tree that is
|
|
2325
2937
|
* still the only copy. */
|
|
@@ -2346,6 +2958,29 @@ export interface Store {
|
|
|
2346
2958
|
/** Every attempt for one issue, oldest first, so an escalation can group the
|
|
2347
2959
|
* continuation budget by failure class (#439). */
|
|
2348
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;
|
|
2349
2984
|
/** Persist a one-shot ceiling and append the operator action to its audit ledger. */
|
|
2350
2985
|
setTurnOverride(project: string, issue: number, maxTurns: number, setAt?: number): void;
|
|
2351
2986
|
turnOverride(project: string, issue: number): number | undefined;
|
|
@@ -2359,11 +2994,26 @@ export interface Store {
|
|
|
2359
2994
|
): TurnOverride[];
|
|
2360
2995
|
runsStartedSince(project: string, sinceEpochMs: number): number;
|
|
2361
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;
|
|
2362
3003
|
/** The rows one stats window needs (#282): every run settling at or after
|
|
2363
3004
|
* `sinceEpochMs`, plus the full attempt chain of each issue whose merge
|
|
2364
3005
|
* settled there (so continuation chains collapse into one journey), ordered
|
|
2365
3006
|
* by issue then startedAt. Read-only. */
|
|
2366
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 }[];
|
|
2367
3017
|
/** Total tracked `gh` calls between two UTC day keys, inclusive (#198). */
|
|
2368
3018
|
ghCallsBetween(sinceDay: string, untilDay: string): number;
|
|
2369
3019
|
/** Idempotence guard so a retry loop cannot page a human repeatedly for the
|
|
@@ -2393,6 +3043,14 @@ export interface Store {
|
|
|
2393
3043
|
categories?: readonly InterruptCategory[],
|
|
2394
3044
|
urgent?: boolean,
|
|
2395
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;
|
|
2396
3054
|
/** Persist one ordinary material outcome without sending it. */
|
|
2397
3055
|
recordMaterialEvent(event: MaterialEventDraft): MaterialEvent;
|
|
2398
3056
|
getMaterialEvent(id: string): MaterialEvent | undefined;
|
|
@@ -2427,6 +3085,21 @@ export interface Store {
|
|
|
2427
3085
|
project: string,
|
|
2428
3086
|
holds: readonly { issue: number; reason: AdmissionHoldReason; detail?: string }[],
|
|
2429
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[];
|
|
2430
3103
|
/** Count and age source for status and digest prompt bounds. */
|
|
2431
3104
|
digestBacklog(project: string): DigestBacklog;
|
|
2432
3105
|
/** Add one bounded observation to the per-day friction rollup. */
|
|
@@ -2594,8 +3267,24 @@ export interface Store {
|
|
|
2594
3267
|
* into recovery. False when the row was no longer `ci-deterministic`: a
|
|
2595
3268
|
* no-op, exactly what a second repair pass must be. */
|
|
2596
3269
|
reclassifyInfra(id: string): boolean;
|
|
2597
|
-
/**
|
|
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
|
+
*/
|
|
2598
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 }[];
|
|
2599
3288
|
/** Rows whose recovery ran at or after `since`, newest first — the tick's
|
|
2600
3289
|
* "auto-recovered since last tick" line, and its count. */
|
|
2601
3290
|
recoveredSince(project: string, since: number): RunRecord[];
|
|
@@ -2605,6 +3294,17 @@ export interface Store {
|
|
|
2605
3294
|
* remembering things.
|
|
2606
3295
|
*/
|
|
2607
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[];
|
|
2608
3308
|
/** Everything still owed an answer, oldest first — what a tick digest reads. */
|
|
2609
3309
|
openDecisions(project: string): DecisionRecord[];
|
|
2610
3310
|
/** One row by id, whatever its state — what a bounded ask polls while waiting. */
|
|
@@ -2839,6 +3539,10 @@ export const DEFAULT_CAPS: Caps = {
|
|
|
2839
3539
|
maxConcurrentWorkers: 2,
|
|
2840
3540
|
maxConcurrentWorkersPerRepo: 1,
|
|
2841
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,
|
|
2842
3546
|
// Off unless an operator names a window. A default threshold would need a
|
|
2843
3547
|
// default window id, and guessing which allowance a fleet lives on is how a
|
|
2844
3548
|
// guard silently watches the wrong meter (#110).
|
|
@@ -2873,6 +3577,11 @@ export const VERB_NAMES = [
|
|
|
2873
3577
|
* OMP session on the existing branch and PR. No close, no reopen, no
|
|
2874
3578
|
* redispatch. */
|
|
2875
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",
|
|
2876
3585
|
/** The orchestrator-only, settled-run recovery operation (#806): open (or
|
|
2877
3586
|
* adopt) the missing pull request for a terminal run whose stored branch
|
|
2878
3587
|
* exists at its exact recorded head. The recovery proves everything a
|
|
@@ -2974,6 +3683,21 @@ export const VERB_REFUSALS = [
|
|
|
2974
3683
|
/** The routed repository a terminal run recorded no longer has an entry in
|
|
2975
3684
|
* this project's routing, so the daemon cannot create a PR for it. */
|
|
2976
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",
|
|
3695
|
+
/** The run's durable `prUrl` is a non-URL value the tracker cannot address
|
|
3696
|
+
* (other than the normalized `pending` settlement sentinel, #866). There is
|
|
3697
|
+
* no PR to read and none to guess at — a corrupt record, not an unreadable
|
|
3698
|
+
* one, so it refuses by name instead of reading as a never-clearing
|
|
3699
|
+
* `head-unresolvable`. */
|
|
3700
|
+
"recorded-pr-corrupt",
|
|
2977
3701
|
/** The run has no pull request to act on. */
|
|
2978
3702
|
"pr-missing",
|
|
2979
3703
|
/**
|
|
@@ -3012,6 +3736,31 @@ export const VERB_REFUSALS = [
|
|
|
3012
3736
|
* repos are unaffected.
|
|
3013
3737
|
*/
|
|
3014
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",
|
|
3015
3764
|
/** The label is not in this project's own vocabulary. */
|
|
3016
3765
|
"label-not-in-vocabulary",
|
|
3017
3766
|
/** The label is a lifecycle label; those transitions stay the daemon's (#26). */
|