omp-conductor 0.12.0 → 0.14.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 +253 -84
- package/package.json +1 -1
- package/src/availability.ts +165 -0
- package/src/board.ts +1 -1
- package/src/briefs/orchestrator.md +95 -28
- package/src/briefs/policy.md +44 -32
- package/src/cli.ts +226 -37
- package/src/config.ts +123 -7
- package/src/daemon.ts +1018 -145
- package/src/diff-flags.ts +77 -4
- package/src/digest-schedule.ts +92 -24
- package/src/escalate.ts +46 -19
- package/src/failure-class.ts +7 -5
- package/src/fleet.ts +39 -3
- package/src/omp.ts +8 -4
- package/src/orchestrator-tick.ts +471 -39
- package/src/orchestrator.ts +3 -2
- package/src/plugin.ts +166 -14
- package/src/release-policy.ts +66 -6
- package/src/reports.ts +202 -5
- package/src/session-host.ts +4 -3
- package/src/setup.ts +197 -35
- package/src/store.ts +785 -112
- package/src/tracker/github.ts +299 -56
- package/src/types.ts +289 -37
- package/src/verbs/actions.ts +245 -15
- package/src/verbs/protocol.ts +7 -6
- package/src/verbs/server.ts +94 -13
- package/src/worker.ts +51 -11
- package/src/worktree.ts +5 -0
package/src/types.ts
CHANGED
|
@@ -61,6 +61,9 @@ export interface Caps {
|
|
|
61
61
|
/** Turn ceiling for one worker — catches loops that are burning tokens
|
|
62
62
|
* without converging. */
|
|
63
63
|
workerMaxTurns: number;
|
|
64
|
+
/** Maximum turn budget an operator may assign to one issue's next attempt.
|
|
65
|
+
* Defaults to twice that issue's effective `workerMaxTurns`. */
|
|
66
|
+
workerMaxTurnsCeiling: number;
|
|
64
67
|
/** Wall-clock ceiling for one worker (90 min): a session that is merely
|
|
65
68
|
* stuck spends no turns, so turns alone cannot detect it. */
|
|
66
69
|
workerWallClockMs: number;
|
|
@@ -133,8 +136,8 @@ export interface RepoTarget {
|
|
|
133
136
|
* `escalations` interrupts only for a tier-2 decision. `material` reports every
|
|
134
137
|
* material event as it happens. `decisions` sits between them: a decision the
|
|
135
138
|
* session needs, or a condition that stops the fleet, interrupts immediately;
|
|
136
|
-
* every other material event accumulates and ships with the
|
|
137
|
-
*
|
|
139
|
+
* every other material event accumulates and ships with the configured digest
|
|
140
|
+
* instead of as a ping per merge (#138).
|
|
138
141
|
*/
|
|
139
142
|
export const REPORT_SCOPES = ["escalations", "decisions", "material"] as const;
|
|
140
143
|
|
|
@@ -174,6 +177,24 @@ export const INTERRUPT_CATEGORIES = [
|
|
|
174
177
|
|
|
175
178
|
export type InterruptCategory = (typeof INTERRUPT_CATEGORIES)[number];
|
|
176
179
|
|
|
180
|
+
/** Operator-local weekdays used by a weekly availability window. */
|
|
181
|
+
export const WEEKDAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] as const;
|
|
182
|
+
|
|
183
|
+
export type Weekday = (typeof WEEKDAYS)[number];
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* One operator-local interruption window. A day names the day the window
|
|
187
|
+
* opens; when `start` is later than `end`, it closes on the following day.
|
|
188
|
+
*/
|
|
189
|
+
export interface WeeklyAvailability {
|
|
190
|
+
timezone: string;
|
|
191
|
+
days: Weekday[];
|
|
192
|
+
start: string;
|
|
193
|
+
end: string;
|
|
194
|
+
/** Categories that may still interrupt outside the window. */
|
|
195
|
+
bypass: InterruptCategory[];
|
|
196
|
+
}
|
|
197
|
+
|
|
177
198
|
/** When the daily/report rollup is due. */
|
|
178
199
|
export const DIGEST_CADENCES = ["none", "per-tick", "daily"] as const;
|
|
179
200
|
|
|
@@ -187,6 +208,8 @@ export type DigestCadence = (typeof DIGEST_CADENCES)[number];
|
|
|
187
208
|
export interface ReportingPolicy {
|
|
188
209
|
interruptOn: InterruptCategory[];
|
|
189
210
|
digest: { cadence: DigestCadence; at?: string; timezone?: string };
|
|
211
|
+
/** Absent preserves the legacy 24-hour interruption behaviour. */
|
|
212
|
+
availability?: WeeklyAvailability;
|
|
190
213
|
/** Set when the policy came from a legacy `scope` preset. */
|
|
191
214
|
scopePreset?: ReportScope;
|
|
192
215
|
}
|
|
@@ -358,6 +381,8 @@ export const RELEASE_REQUIREMENTS = [
|
|
|
358
381
|
"no-open-prs",
|
|
359
382
|
/** Nothing still carries the queue label: the batch is finished, not paused. */
|
|
360
383
|
"queue-drained",
|
|
384
|
+
/** The newest observed post-merge check for the released repo's base branch is green. */
|
|
385
|
+
"base-branch-green",
|
|
361
386
|
/** The epic this release closes has no open children left. */
|
|
362
387
|
"epic-children-closed",
|
|
363
388
|
] as const;
|
|
@@ -661,6 +686,8 @@ export type PrState = "merged" | "closed" | "open";
|
|
|
661
686
|
/** Result of independently checking a worker's claimed green pull request. */
|
|
662
687
|
export interface PrVerification {
|
|
663
688
|
status: "green" | "pending" | "failed";
|
|
689
|
+
/** Full live head SHA read in the same tracker request as the verdict. */
|
|
690
|
+
headSha: string;
|
|
664
691
|
reason: string;
|
|
665
692
|
}
|
|
666
693
|
|
|
@@ -703,6 +730,10 @@ export const SETTLEMENT_FLAG_KINDS = [
|
|
|
703
730
|
"changed-line-missing",
|
|
704
731
|
/** `changed:` named a path the PR never touched. The weaker direction. */
|
|
705
732
|
"unmatched-claim",
|
|
733
|
+
/** The same file appears as both claimed-but-untouched and
|
|
734
|
+
* touched-but-unclaimed — the `changed:` line's format defeated the parser;
|
|
735
|
+
* read the diff directly. */
|
|
736
|
+
"report-format-unparsed",
|
|
706
737
|
/** A test file left the tree and no rename in the PR accounts for it. */
|
|
707
738
|
"test-file-deleted",
|
|
708
739
|
/** A skip/only/focus marker appears on a line the PR added. */
|
|
@@ -711,6 +742,10 @@ export const SETTLEMENT_FLAG_KINDS = [
|
|
|
711
742
|
"assertions-removed",
|
|
712
743
|
/** A named timeout in a test file went up. */
|
|
713
744
|
"test-timeout-raised",
|
|
745
|
+
/** A terminal run recovered an OPEN PR by exact issue, repo and branch identity. */
|
|
746
|
+
"pr-adopted",
|
|
747
|
+
/** A workflow on the merge commit's base branch failed after settlement. */
|
|
748
|
+
"base-branch-red",
|
|
714
749
|
] as const;
|
|
715
750
|
|
|
716
751
|
export type SettlementFlagKind = (typeof SETTLEMENT_FLAG_KINDS)[number];
|
|
@@ -722,7 +757,8 @@ export type SettlementFlagKind = (typeof SETTLEMENT_FLAG_KINDS)[number];
|
|
|
722
757
|
*/
|
|
723
758
|
export interface SettlementFlag {
|
|
724
759
|
kind: SettlementFlagKind;
|
|
725
|
-
/** Repo-relative path,
|
|
760
|
+
/** Repo-relative path, `(report)` for report evidence, or `(base branch)` for
|
|
761
|
+
* a post-merge workflow result. */
|
|
726
762
|
file: string;
|
|
727
763
|
/** 1-based line in the side of the diff the evidence came from: the
|
|
728
764
|
* post-image for an added line, the pre-image for a removed one. */
|
|
@@ -736,24 +772,52 @@ export interface SettlementFlag {
|
|
|
736
772
|
/** Tracker lifecycle state for an issue. Undefined means the adapter could not tell. */
|
|
737
773
|
export type IssueState = "open" | "closed";
|
|
738
774
|
|
|
775
|
+
/** Read-after-write issue facts used to validate an eventually-consistent queue candidate. */
|
|
776
|
+
export interface IssueSnapshot {
|
|
777
|
+
readonly state: IssueState;
|
|
778
|
+
readonly labels: readonly string[];
|
|
779
|
+
}
|
|
780
|
+
|
|
739
781
|
/**
|
|
740
782
|
* An OPEN pull request that already closes an issue, as admission sees it.
|
|
741
783
|
*
|
|
742
|
-
* The
|
|
743
|
-
*
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
784
|
+
* The repository travels with the URL because recovery has to prove three
|
|
785
|
+
* independent identities before adopting a PR onto a terminal run: closing
|
|
786
|
+
* issue, retained branch, and routed repository (#245). Admission needs only
|
|
787
|
+
* the URL/branch pair, but weakening the shared answer would make recovery
|
|
788
|
+
* parse provenance back out of a presentation URL.
|
|
789
|
+
*
|
|
790
|
+
* The head branch recognises a continuation the store never saw a PR for: on
|
|
791
|
+
* 2026-08-09T00:47Z veltro#324 attempt 1 was killed at the turns cap before its
|
|
792
|
+
* worker opened one, and the PR that later appeared on that exact branch
|
|
793
|
+
* (chad#438) could never be matched by URL equality against a `prUrl` the
|
|
794
|
+
* terminal run never recorded (#50).
|
|
750
795
|
*/
|
|
751
796
|
export interface OpenCloser {
|
|
752
797
|
url: string;
|
|
753
798
|
/** Head branch of the open PR, for retained-continuation identity. */
|
|
754
799
|
headRefName: string;
|
|
800
|
+
/** Canonical `owner/repo` identity supplied by the tracker. */
|
|
801
|
+
repo: string;
|
|
802
|
+
}
|
|
803
|
+
/** The merge identity needed to observe the exact base-branch push it produced. */
|
|
804
|
+
export interface MergedPrInfo {
|
|
805
|
+
mergeSha: string;
|
|
806
|
+
baseRef: string;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/** One GitHub Actions workflow run, normalised before it crosses the tracker port. */
|
|
810
|
+
export interface WorkflowRun {
|
|
811
|
+
id: number;
|
|
812
|
+
workflowId: number;
|
|
813
|
+
name: string;
|
|
814
|
+
status: string;
|
|
815
|
+
conclusion?: string;
|
|
816
|
+
url: string;
|
|
817
|
+
createdAt: string;
|
|
755
818
|
}
|
|
756
819
|
|
|
820
|
+
|
|
757
821
|
/**
|
|
758
822
|
* Deliberately narrow so a Gitea or local-file tracker can drop in later.
|
|
759
823
|
* Nothing here is GitHub-shaped; the GitHub adapter owns `gh` entirely.
|
|
@@ -786,44 +850,70 @@ export interface Tracker {
|
|
|
786
850
|
*/
|
|
787
851
|
parentOf(issue: number): Promise<number | undefined>;
|
|
788
852
|
/**
|
|
789
|
-
* The OPEN pull request that already closes `issue`, or undefined when
|
|
790
|
-
* does.
|
|
853
|
+
* The first OPEN pull request that already closes `issue`, or undefined when
|
|
854
|
+
* none does.
|
|
791
855
|
*
|
|
792
|
-
* Admission
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
*
|
|
796
|
-
* present pushed-and-open work as an untouched queue item. The tracker is the
|
|
797
|
-
* only party that remembers across all of those.
|
|
798
|
-
*
|
|
799
|
-
* Returns {@link OpenCloser} rather than a bare URL because the branch is the
|
|
800
|
-
* half admission needs to recognise a continuation the store never saw a PR
|
|
801
|
-
* for; see that type for the veltro#324 case that forced the widening.
|
|
856
|
+
* Admission asks this any-open question because the store only knows work
|
|
857
|
+
* recorded in this database. A migrated daemon, restored host, or younger
|
|
858
|
+
* database can otherwise dispatch duplicate work onto an issue whose PR is
|
|
859
|
+
* already open.
|
|
802
860
|
*/
|
|
803
861
|
openCloserFor(issue: number): Promise<OpenCloser | undefined>;
|
|
862
|
+
/**
|
|
863
|
+
* Every OPEN pull request that closes `issue`.
|
|
864
|
+
*
|
|
865
|
+
* Salvage recovery must inspect the full answer: an unrelated closer may be
|
|
866
|
+
* listed before the PR whose branch and repository exactly match the
|
|
867
|
+
* recovered run. An empty array means no OPEN closer; a thrown lookup remains
|
|
868
|
+
* fail-closed.
|
|
869
|
+
*/
|
|
870
|
+
openClosersFor(issue: number): Promise<OpenCloser[]>;
|
|
804
871
|
/**
|
|
805
872
|
* Whether an issue is still open, or undefined when tracker/network state is
|
|
806
873
|
* ambiguous. Cleanup must never interpret undefined as permission to delete.
|
|
807
874
|
*/
|
|
808
875
|
issueState(issue: number): Promise<IssueState | undefined>;
|
|
876
|
+
/**
|
|
877
|
+
* Direct REST state and labels for final admission validation, or undefined
|
|
878
|
+
* when either fact cannot be read safely.
|
|
879
|
+
*/
|
|
880
|
+
issueSnapshot(issue: number): Promise<IssueSnapshot | undefined>;
|
|
809
881
|
/**
|
|
810
882
|
* The state of one specific pull request, or undefined when this adapter
|
|
811
883
|
* could not tell — a network failure, a deleted PR, a URL it cannot parse.
|
|
812
884
|
* Undefined never means "no".
|
|
813
885
|
*
|
|
814
|
-
* Deliberately separate from {@link Tracker.openCloserFor}, which
|
|
815
|
-
*
|
|
816
|
-
*
|
|
817
|
-
* PR alike, because admission treats all four the same. This one asks "did
|
|
886
|
+
* Deliberately separate from {@link Tracker.openCloserFor}, which asks
|
|
887
|
+
* whether any OPEN closing PR exists and answers undefined for merged,
|
|
888
|
+
* closed-unmerged, no PR at all and a deleted PR alike. This one asks "did
|
|
818
889
|
* *this* run's PR land", where those answers are opposites. Collapsing them
|
|
819
890
|
* is how a PR a human rejected gets recorded as merged.
|
|
820
891
|
*/
|
|
821
892
|
prState(url: string): Promise<PrState | undefined>;
|
|
893
|
+
/** The merge commit and base ref for a merged pull request. */
|
|
894
|
+
mergedPrInfo(url: string): Promise<MergedPrInfo | undefined>;
|
|
895
|
+
/** Workflow runs GitHub associated with one exact commit SHA. */
|
|
896
|
+
workflowRunsAt(
|
|
897
|
+
repo: string,
|
|
898
|
+
sha: string,
|
|
899
|
+
opts?: { event?: string; branch?: string },
|
|
900
|
+
): Promise<WorkflowRun[] | undefined>;
|
|
901
|
+
/** The live commit at one repository branch, or undefined when unreadable. */
|
|
902
|
+
branchHead(repo: string, branch: string): Promise<string | undefined>;
|
|
903
|
+
/** The newest run of this workflow on the base branch before `before`.
|
|
904
|
+
* `null` means no earlier run; `undefined` means the history could not be read. */
|
|
905
|
+
previousWorkflowRun(
|
|
906
|
+
repo: string,
|
|
907
|
+
workflowId: number,
|
|
908
|
+
branch: string,
|
|
909
|
+
before: string,
|
|
910
|
+
): Promise<WorkflowRun | null | undefined>;
|
|
822
911
|
/**
|
|
823
|
-
* Verify that a
|
|
824
|
-
*
|
|
912
|
+
* Verify that a pull request is open, ready, and has a non-empty
|
|
913
|
+
* terminal-success check rollup. When `expectedHead` is supplied, also
|
|
914
|
+
* require the live head to match it.
|
|
825
915
|
*/
|
|
826
|
-
verifyPr(url: string, expectedHead
|
|
916
|
+
verifyPr(url: string, expectedHead?: string): Promise<PrVerification | undefined>;
|
|
827
917
|
/**
|
|
828
918
|
* The pull request's diff, or undefined when this adapter could not produce
|
|
829
919
|
* one — an unparseable URL, a deleted PR, a flaky network.
|
|
@@ -899,6 +989,9 @@ export const FAILURE_CLASSES = [
|
|
|
899
989
|
"settlement-stuck",
|
|
900
990
|
"provider-credit",
|
|
901
991
|
"provider-transient",
|
|
992
|
+
/** A reviewer closed pushed-green or pushed-pending work without merging it:
|
|
993
|
+
* a review decision, not a worker failure. */
|
|
994
|
+
"returned-for-revision",
|
|
902
995
|
"unknown",
|
|
903
996
|
] as const;
|
|
904
997
|
|
|
@@ -923,11 +1016,34 @@ export const RECOVERY_ACTIONS = [
|
|
|
923
1016
|
|
|
924
1017
|
export type RecoveryAction = (typeof RECOVERY_ACTIONS)[number];
|
|
925
1018
|
|
|
1019
|
+
/** What the daemon learned about workflows on a mediated merge's base commit. */
|
|
1020
|
+
export const BASE_CHECK_STATES = [
|
|
1021
|
+
"pending",
|
|
1022
|
+
"green",
|
|
1023
|
+
"red",
|
|
1024
|
+
"red-preexisting",
|
|
1025
|
+
"unknown",
|
|
1026
|
+
] as const;
|
|
1027
|
+
|
|
1028
|
+
export type BaseCheckState = (typeof BASE_CHECK_STATES)[number];
|
|
1029
|
+
|
|
1030
|
+
/** Current push-workflow health at a routed repository's live base head. */
|
|
1031
|
+
export interface BaseHealth {
|
|
1032
|
+
repo: string;
|
|
1033
|
+
branch: string;
|
|
1034
|
+
headSha: string;
|
|
1035
|
+
verdict: "green" | "red" | "pending" | "unknown";
|
|
1036
|
+
runsCount: number;
|
|
1037
|
+
detail?: string;
|
|
1038
|
+
checkedAt: number;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
926
1041
|
/**
|
|
927
1042
|
* Execution state is separate from the tracker's own labels on purpose: labels
|
|
928
1043
|
* are coarse and human-editable, while the loop needs to distinguish "pushed
|
|
929
1044
|
* and green, waiting on merge" from "merged" to decide what to do on restart.
|
|
930
1045
|
*/
|
|
1046
|
+
|
|
931
1047
|
export type RunState =
|
|
932
1048
|
| "claimed"
|
|
933
1049
|
| "running"
|
|
@@ -940,6 +1056,9 @@ export type RunState =
|
|
|
940
1056
|
| "blocked"
|
|
941
1057
|
| "failed"
|
|
942
1058
|
| "killed"
|
|
1059
|
+
/** Terminal operator action. The worker cannot resume and the attempt does
|
|
1060
|
+
* not consume implementation-failure or continuation budgets. */
|
|
1061
|
+
| "stopped"
|
|
943
1062
|
/** In flight when its daemon process died; reconciled at the next startup. */
|
|
944
1063
|
| "orphaned";
|
|
945
1064
|
|
|
@@ -967,6 +1086,12 @@ export interface RunRecord {
|
|
|
967
1086
|
prUrl?: string;
|
|
968
1087
|
/** Pull request head the worker observed after its deterministic CI watcher exited. */
|
|
969
1088
|
headSha?: string;
|
|
1089
|
+
/** Merge commit and actual base ref read from the merged pull request. */
|
|
1090
|
+
mergeSha?: string;
|
|
1091
|
+
baseRef?: string;
|
|
1092
|
+
/** Post-merge workflow verdict. Pending is revisited by the tick sweep. */
|
|
1093
|
+
baseCheck?: BaseCheckState;
|
|
1094
|
+
baseCheckAt?: number;
|
|
970
1095
|
/** Commit this run's uncommitted work was preserved as before its worktree
|
|
971
1096
|
* was removed, on the run's own branch. Absent means the daemon found
|
|
972
1097
|
* nothing to save, or never looked — see {@link RunRecord.salvageError}. */
|
|
@@ -1005,11 +1130,22 @@ export interface RunRecord {
|
|
|
1005
1130
|
recoveredAt?: number;
|
|
1006
1131
|
}
|
|
1007
1132
|
|
|
1133
|
+
/** A one-shot turn budget waiting for one issue's next claimed attempt. */
|
|
1134
|
+
export interface TurnOverride {
|
|
1135
|
+
project: string;
|
|
1136
|
+
issue: number;
|
|
1137
|
+
maxTurns: number;
|
|
1138
|
+
setAt: number;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1008
1141
|
export type AdmissionHoldReason =
|
|
1009
1142
|
| "capacity"
|
|
1010
1143
|
| "issue-active"
|
|
1011
1144
|
| "failed-attempts"
|
|
1012
1145
|
| "continuations"
|
|
1146
|
+
| "issue-closed"
|
|
1147
|
+
| "issue-state-lookup-error"
|
|
1148
|
+
| "issue-dequeued"
|
|
1013
1149
|
| "parent-lookup-error"
|
|
1014
1150
|
| "sibling-active"
|
|
1015
1151
|
| "repo-active"
|
|
@@ -1049,6 +1185,7 @@ export type FrictionAdmissionReason =
|
|
|
1049
1185
|
| "failed-attempts"
|
|
1050
1186
|
| "continuations"
|
|
1051
1187
|
| "parent-lookup-error"
|
|
1188
|
+
| "issue-state-lookup-error"
|
|
1052
1189
|
| "open-pr-lookup-error"
|
|
1053
1190
|
| "unroutable:no-repo-label"
|
|
1054
1191
|
| "unroutable:multiple-repo-labels"
|
|
@@ -1173,6 +1310,43 @@ export interface ReportEnqueue {
|
|
|
1173
1310
|
deduped: boolean;
|
|
1174
1311
|
}
|
|
1175
1312
|
|
|
1313
|
+
/** Maximum ledger rows of each kind placed in one digest prompt. Older rows
|
|
1314
|
+
* stay first, so a busy day drains deterministically over later digests. */
|
|
1315
|
+
export const DIGEST_BACKLOG_LIMIT = 20;
|
|
1316
|
+
|
|
1317
|
+
/** One ordinary material outcome waiting for a rendered digest (#274). */
|
|
1318
|
+
export interface MaterialEvent {
|
|
1319
|
+
id: string;
|
|
1320
|
+
project: string;
|
|
1321
|
+
category: string;
|
|
1322
|
+
summary: string;
|
|
1323
|
+
evidence: string;
|
|
1324
|
+
occurredAt: number;
|
|
1325
|
+
recordedAt: number;
|
|
1326
|
+
/** The accepted outbox row that took responsibility for this event. */
|
|
1327
|
+
digestReportId?: string;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
/** What the orchestrator records when an outcome happens. */
|
|
1331
|
+
export interface MaterialEventDraft {
|
|
1332
|
+
project: string;
|
|
1333
|
+
category: string;
|
|
1334
|
+
summary: string;
|
|
1335
|
+
evidence: string;
|
|
1336
|
+
occurredAt: number;
|
|
1337
|
+
recordedAt: number;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/** Counts and oldest timestamps for the two distinct digest sources. */
|
|
1341
|
+
export interface DigestBacklog {
|
|
1342
|
+
materialCount: number;
|
|
1343
|
+
materialOldestAt?: number;
|
|
1344
|
+
heldNoticeCount: number;
|
|
1345
|
+
/** Subset held only for the next availability window. */
|
|
1346
|
+
availabilityHeldNoticeCount?: number;
|
|
1347
|
+
heldNoticeOldestAt?: number;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1176
1350
|
/**
|
|
1177
1351
|
* Where one operator decision stands (#136).
|
|
1178
1352
|
*
|
|
@@ -1251,6 +1425,7 @@ export interface LabelOp {
|
|
|
1251
1425
|
* the two disagree.
|
|
1252
1426
|
*/
|
|
1253
1427
|
export interface Store {
|
|
1428
|
+
/** A claimed run atomically consumes and applies its issue's pending turn override. */
|
|
1254
1429
|
createRun(r: Omit<RunRecord, "id">): RunRecord;
|
|
1255
1430
|
updateRun(id: string, patch: Partial<RunRecord>): void;
|
|
1256
1431
|
getRun(id: string): RunRecord | undefined;
|
|
@@ -1263,6 +1438,17 @@ export interface Store {
|
|
|
1263
1438
|
/** Newest attempt per issue for the live board. Non-merged work remains
|
|
1264
1439
|
* visible; merged rows are bounded by the supplied recent-history cutoff. */
|
|
1265
1440
|
recentRuns(project: string, mergedSinceEpochMs: number): RunRecord[];
|
|
1441
|
+
/** Merged rows whose post-merge workflow verdict is still pending, oldest first. */
|
|
1442
|
+
runsNeedingBaseCheck(project: string, limit?: number): RunRecord[];
|
|
1443
|
+
/** Replace the current live-head health row for one routed repository. */
|
|
1444
|
+
upsertBaseHealth(project: string, row: BaseHealth): void;
|
|
1445
|
+
/** Current live-head health, ordered by routed repository name. */
|
|
1446
|
+
baseHealth(project: string): BaseHealth[];
|
|
1447
|
+
/** Recently merged routed repository/branch pairs that establish health scope. */
|
|
1448
|
+
mergedRepoBranches(
|
|
1449
|
+
project: string,
|
|
1450
|
+
sinceEpochMs: number,
|
|
1451
|
+
): { repo: string; baseRef?: string }[];
|
|
1266
1452
|
/** Newest attempt per issue that preserved work or failed to, so `status`
|
|
1267
1453
|
* can name every WIP tip a re-claim would build on and every tree that is
|
|
1268
1454
|
* still the only copy. */
|
|
@@ -1281,6 +1467,17 @@ export interface Store {
|
|
|
1281
1467
|
* tail` resolves an issue number to a transcript through this; the number is
|
|
1282
1468
|
* what an operator has, the run id is not. */
|
|
1283
1469
|
latestRun(project: string, issue: number): RunRecord | undefined;
|
|
1470
|
+
/** Persist a one-shot ceiling and append the operator action to its audit ledger. */
|
|
1471
|
+
setTurnOverride(project: string, issue: number, maxTurns: number, setAt?: number): void;
|
|
1472
|
+
turnOverride(project: string, issue: number): number | undefined;
|
|
1473
|
+
/** Administrative deletion; claimed runs consume inside `createRun`'s transaction. */
|
|
1474
|
+
clearTurnOverride(project: string, issue: number): void;
|
|
1475
|
+
listTurnOverrides(project: string): TurnOverride[];
|
|
1476
|
+
/** Durable override actions, newest first; consumption never removes these rows. */
|
|
1477
|
+
turnOverrideLedger(
|
|
1478
|
+
project: string,
|
|
1479
|
+
opts?: { issue?: number; limit?: number },
|
|
1480
|
+
): TurnOverride[];
|
|
1284
1481
|
/** Every attempt of one issue that settled with a report, in attempt order.
|
|
1285
1482
|
* The settlement audit pools these as prior disclosures when a later
|
|
1286
1483
|
* attempt is reconciled (#199). Rows with no report (pre-#199, or killed
|
|
@@ -1296,12 +1493,26 @@ export interface Store {
|
|
|
1296
1493
|
/** The newest `digest:` dedupe key a project has run toward without ending in
|
|
1297
1494
|
* failure — what the off-schedule-digest refusal compares against (#229). */
|
|
1298
1495
|
lastDigestDedupeKey(project: string): string | undefined;
|
|
1299
|
-
/** Hold a tier-2 escalation the interrupt policy
|
|
1496
|
+
/** Hold a tier-2 escalation the interrupt policy or availability window deferred. */
|
|
1300
1497
|
addHeldNotice(notice: HeldNoticeDraft): void;
|
|
1301
|
-
/**
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1498
|
+
/**
|
|
1499
|
+
* Held notices still owed, oldest first and bounded. `availabilityOnly`
|
|
1500
|
+
* selects notices that may be released at the next configured opening.
|
|
1501
|
+
*/
|
|
1502
|
+
undigestedNotices(
|
|
1503
|
+
project: string,
|
|
1504
|
+
limit?: number,
|
|
1505
|
+
availabilityOnly?: boolean,
|
|
1506
|
+
categories?: readonly InterruptCategory[],
|
|
1507
|
+
urgent?: boolean,
|
|
1508
|
+
): HeldNotice[];
|
|
1509
|
+
/** Persist one ordinary material outcome without sending it. */
|
|
1510
|
+
recordMaterialEvent(event: MaterialEventDraft): MaterialEvent;
|
|
1511
|
+
getMaterialEvent(id: string): MaterialEvent | undefined;
|
|
1512
|
+
/** Ordinary material outcomes still owed, oldest first and bounded. */
|
|
1513
|
+
undigestedMaterialEvents(project: string, limit?: number): MaterialEvent[];
|
|
1514
|
+
/** Count and age source for status and digest prompt bounds. */
|
|
1515
|
+
digestBacklog(project: string): DigestBacklog;
|
|
1305
1516
|
/** Add one bounded observation to the per-day friction rollup. */
|
|
1306
1517
|
recordFriction(project: string, observation: FrictionObservation): void;
|
|
1307
1518
|
/** Repeated signals not surfaced within the supplied cooldown window. */
|
|
@@ -1331,7 +1542,35 @@ export interface Store {
|
|
|
1331
1542
|
* suppressed without asking the model what it sent yesterday.
|
|
1332
1543
|
*/
|
|
1333
1544
|
enqueueReport(draft: ReportDraft): ReportEnqueue;
|
|
1545
|
+
/** Atomically hand a digest to the outbox and associate only the named
|
|
1546
|
+
* material events and deferred escalations. A handoff deduplicated by its
|
|
1547
|
+
* optional daily key consumes nothing new. */
|
|
1548
|
+
enqueueDigestReport(
|
|
1549
|
+
draft: ReportDraft,
|
|
1550
|
+
materialEventIds: readonly string[],
|
|
1551
|
+
heldNoticeIds: readonly string[],
|
|
1552
|
+
): ReportEnqueue;
|
|
1553
|
+
/** Atomically enqueue a working-hours catch-up unless a due digest currently
|
|
1554
|
+
* owns the handoff window. `undefined` leaves every notice unowned. */
|
|
1555
|
+
enqueueAvailabilityReport(
|
|
1556
|
+
draft: ReportDraft,
|
|
1557
|
+
heldNoticeIds: readonly string[],
|
|
1558
|
+
): ReportEnqueue | undefined;
|
|
1334
1559
|
getReport(id: string): ReportRecord | undefined;
|
|
1560
|
+
/** Atomically remove an unsent material report from the outbox and preserve
|
|
1561
|
+
* its body as a held notice after policy or availability changes. */
|
|
1562
|
+
deferPendingReportToNotice(id: string, notice: HeldNoticeDraft): boolean;
|
|
1563
|
+
/** Remove a still-pending working-hours catch-up and make its associated
|
|
1564
|
+
* notices digest-eligible again after a live policy change. */
|
|
1565
|
+
releasePendingAvailabilityReport(id: string, project: string, possibleRepeat?: boolean): boolean;
|
|
1566
|
+
/** Give one due digest a bounded, once-per-day lease on availability-held
|
|
1567
|
+
* notices. Pending catch-ups are released inside the same transaction. */
|
|
1568
|
+
reserveAvailabilityDigest(
|
|
1569
|
+
project: string,
|
|
1570
|
+
cycleKey: string,
|
|
1571
|
+
at: number,
|
|
1572
|
+
expiresAt: number,
|
|
1573
|
+
): boolean;
|
|
1335
1574
|
/** `pending` rows whose backoff has elapsed, oldest first. */
|
|
1336
1575
|
dueReports(project: string, now: number, limit: number): ReportRecord[];
|
|
1337
1576
|
/** Atomically take a due `pending` row into `sending` under `attemptId`, so
|
|
@@ -1468,24 +1707,36 @@ export interface Escalation {
|
|
|
1468
1707
|
* escalation pages now or is held for the digest under the project's
|
|
1469
1708
|
* `reporting.interruptOn` policy (#229). */
|
|
1470
1709
|
category?: InterruptCategory;
|
|
1710
|
+
/** Deliver now even when policy would defer this category to the digest.
|
|
1711
|
+
* Used only when the digest's own orchestrator loop is unavailable. */
|
|
1712
|
+
urgent?: true;
|
|
1471
1713
|
}
|
|
1472
1714
|
|
|
1473
|
-
/** A tier-2 escalation the project's interrupt policy deferred to the digest. */
|
|
1474
1715
|
export interface HeldNotice {
|
|
1475
1716
|
id: string;
|
|
1476
1717
|
category: InterruptCategory;
|
|
1477
1718
|
summary: string;
|
|
1478
1719
|
detail: string;
|
|
1479
1720
|
createdAt: number;
|
|
1721
|
+
/**
|
|
1722
|
+
* This notice was otherwise interruptible and was held only by the
|
|
1723
|
+
* availability window. It may be surfaced when the current policy opens.
|
|
1724
|
+
*/
|
|
1725
|
+
releaseOnAvailable?: true;
|
|
1726
|
+
/** Urgent recovery may bypass category batching, but never availability. */
|
|
1727
|
+
urgent?: true;
|
|
1480
1728
|
}
|
|
1481
1729
|
|
|
1482
|
-
/** What it takes to persist one held notice. */
|
|
1483
1730
|
export interface HeldNoticeDraft {
|
|
1731
|
+
/** Caller-supplied identity when the handoff must print or deduplicate it. */
|
|
1732
|
+
id?: string;
|
|
1484
1733
|
project: string;
|
|
1485
1734
|
category: InterruptCategory;
|
|
1486
1735
|
summary: string;
|
|
1487
1736
|
detail: string;
|
|
1488
1737
|
createdAt: number;
|
|
1738
|
+
releaseOnAvailable?: true;
|
|
1739
|
+
urgent?: true;
|
|
1489
1740
|
}
|
|
1490
1741
|
|
|
1491
1742
|
/**
|
|
@@ -1501,6 +1752,7 @@ export const DEFAULT_CAPS: Caps = {
|
|
|
1501
1752
|
// guard silently watches the wrong meter (#110).
|
|
1502
1753
|
planUsage: null,
|
|
1503
1754
|
workerMaxTurns: 120,
|
|
1755
|
+
workerMaxTurnsCeiling: 240,
|
|
1504
1756
|
workerWallClockMs: 90 * 60 * 1000,
|
|
1505
1757
|
maxAttemptsPerIssue: 2,
|
|
1506
1758
|
maxContinuationsPerIssue: 2,
|